├── .gitignore ├── LICENSE.md ├── Netiquette.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── Netiquette.xcscheme ├── Netiquette ├── 3rd-party │ ├── OrderedDictionary.h │ └── OrderedDictionary.m ├── AboutWindowController.h ├── AboutWindowController.m ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ ├── Contract.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── ContractAlternate.imageset │ │ ├── Contents.json │ │ └── alternate.png │ ├── Expand.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── ExpandAlternate.imageset │ │ ├── Contents.json │ │ └── alternate.png │ ├── Friends1Password.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── FriendsFleet.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── FriendsHuntress.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── FriendsJamf.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── FriendsKandji.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── FriendsMacPaw.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── FriendsMalwarebytes.imageset │ │ ├── Contents.json │ │ └── malwarebytes.png │ ├── FriendsPANW.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── FriendsiVerify.imageset │ │ ├── Contents.json │ │ └── iVerify.png │ ├── Logo.imageset │ │ ├── Contents.json │ │ └── logo.png │ ├── LogoBG.imageset │ │ ├── Contents.json │ │ └── logoBG.png │ ├── LogoOver.imageset │ │ ├── Contents.json │ │ └── logoOver.png │ ├── Love.imageset │ │ ├── Contents.json │ │ ├── heart_dark.pdf │ │ ├── heart_light-1.pdf │ │ └── heart_light.pdf │ ├── Preferences.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── PreferencesAlternate.imageset │ │ ├── Contents.json │ │ └── preferences.png │ ├── PrefsUpdate.imageset │ │ ├── Contents.json │ │ ├── prefsUpdate Any.pdf │ │ ├── prefsUpdate Dark.pdf │ │ └── prefsUpdate Light.pdf │ ├── Save.imageset │ │ ├── Contents.json │ │ ├── darkMode.png │ │ └── lightMode.png │ ├── SaveAlternate.imageset │ │ ├── Contents.json │ │ └── save.png │ ├── niqLogo.imageset │ │ ├── Contents.json │ │ └── niqLogo.png │ └── niqText.imageset │ │ ├── Contents.json │ │ └── niqText.png ├── Base.lproj │ ├── AboutWindow.xib │ ├── MainMenu.xib │ ├── Preferences.xib │ └── UpdateWindow.xib ├── CustomRow.h ├── CustomRow.m ├── Event.h ├── Event.m ├── Info.plist ├── InfoPlist.xcstrings ├── Item.h ├── Item.m ├── Localizable.xcstrings ├── Monitor.h ├── Monitor.m ├── NSApplicationKeyEvents.h ├── NSApplicationKeyEvents.m ├── Netiquette.entitlements ├── PrefsWindowController.h ├── PrefsWindowController.m ├── TableViewController.h ├── TableViewController.m ├── Update.h ├── Update.m ├── UpdateWindowController.h ├── UpdateWindowController.m ├── consts.h ├── images │ ├── logoApple.png │ ├── logoAppleBG.png │ ├── logoAppleOver.png │ ├── saveIcon.png │ ├── saveIconBG.png │ └── saveIconOver.png ├── main.h ├── main.m ├── mul.lproj │ ├── AboutWindow.xcstrings │ ├── MainMenu.xcstrings │ ├── Preferences.xcstrings │ └── UpdateWindow.xcstrings ├── patrons.txt ├── procInfo │ ├── libprocInfo.a │ └── procInfo.h ├── sort.h ├── sort.m ├── utilities.h └── utilities.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Carthage/* 3 | DerivedData/* 4 | *.xcscheme 5 | *.xcuserstate 6 | Netiquette.xcodeproj/xcuserdata/* 7 | Netiquette.xcodeproj/project.xcworkspace/xcuserdata/* 8 | Netiquette.xcodeproj/project.xcworkspace/xcuserdata/patrick.xcuserdatad/UserInterfaceState.xcuserstate 9 | -------------------------------------------------------------------------------- /Netiquette.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Netiquette.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Netiquette.xcodeproj/xcshareddata/xcschemes/Netiquette.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Netiquette/3rd-party/OrderedDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // OrderedDictionary.h 3 | // OrderedDictionary 4 | // 5 | // Created by Matt Gallagher on 19/12/08. 6 | // Copyright 2008 Matt Gallagher. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import 25 | 26 | @interface OrderedDictionary : NSMutableDictionary 27 | { 28 | NSMutableDictionary *dictionary; 29 | NSMutableArray *array; 30 | } 31 | 32 | /* METHODS */ 33 | - (void)insertObject:(id)anObject forKey:(id)aKey atIndex:(NSUInteger)anIndex; 34 | - (id)keyAtIndex:(NSUInteger)anIndex; 35 | - (NSUInteger)indexOfKey:(id)aKey; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Netiquette/3rd-party/OrderedDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // OrderedDictionary.m 3 | // OrderedDictionary 4 | // 5 | // Created by Matt Gallagher on 19/12/08. 6 | // Copyright 2008 Matt Gallagher. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. Permission is granted to anyone to 11 | // use this software for any purpose, including commercial applications, and to 12 | // alter it and redistribute it freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 2. Altered source versions must be plainly marked as such, and must not be 19 | // misrepresented as being the original software. 20 | // 3. This notice may not be removed or altered from any source 21 | // distribution. 22 | // 23 | 24 | #import "Consts.h" 25 | #import "OrderedDictionary.h" 26 | 27 | 28 | NSString *DescriptionForObject(NSObject *object, id locale, NSUInteger indent) 29 | { 30 | NSString *objectString; 31 | if ([object isKindOfClass:[NSString class]]) 32 | { 33 | objectString = (NSString *)object; 34 | } 35 | else if ([object respondsToSelector:@selector(descriptionWithLocale:indent:)]) 36 | { 37 | objectString = [(NSDictionary *)object descriptionWithLocale:locale indent:indent]; 38 | } 39 | else if ([object respondsToSelector:@selector(descriptionWithLocale:)]) 40 | { 41 | objectString = [(NSSet *)object descriptionWithLocale:locale]; 42 | } 43 | else 44 | { 45 | objectString = [object description]; 46 | } 47 | return objectString; 48 | } 49 | 50 | @implementation OrderedDictionary 51 | 52 | //@synthesize array; 53 | //@synthesize dictionary; 54 | 55 | -(id)init 56 | { 57 | self = [super init]; 58 | if (self != nil) 59 | { 60 | dictionary = [NSMutableDictionary dictionary]; 61 | array = [NSMutableArray array]; 62 | } 63 | return self; 64 | 65 | } 66 | 67 | -(id)copy 68 | { 69 | return [self mutableCopy]; 70 | } 71 | 72 | -(id)mutableCopy 73 | { 74 | OrderedDictionary* copy = nil; 75 | 76 | copy = [[OrderedDictionary alloc] init]; 77 | copy->array = [array mutableCopy]; 78 | copy->dictionary = [dictionary mutableCopy]; 79 | 80 | return copy; 81 | } 82 | 83 | -(void)setObject:(id)anObject forKey:(id)aKey 84 | { 85 | if(![dictionary objectForKey:aKey]) 86 | { 87 | // 88 | [array addObject:aKey]; 89 | } 90 | [dictionary setObject:anObject forKey:aKey]; 91 | } 92 | 93 | -(void)removeObjectForKey:(id)aKey 94 | { 95 | [dictionary removeObjectForKey:aKey]; 96 | [array removeObject:aKey]; 97 | } 98 | 99 | - (NSUInteger)count 100 | { 101 | return [dictionary count]; 102 | } 103 | 104 | -(id)objectForKey:(id)aKey 105 | { 106 | return [dictionary objectForKey:aKey]; 107 | } 108 | 109 | -(NSEnumerator *)keyEnumerator 110 | { 111 | return [array objectEnumerator]; 112 | } 113 | 114 | 115 | -(void)insertObject:(id)anObject forKey:(id)aKey atIndex:(NSUInteger)anIndex 116 | { 117 | //remove old object 118 | if([dictionary objectForKey:aKey]) 119 | { 120 | //remove 121 | [self removeObjectForKey:aKey]; 122 | } 123 | 124 | //adding at end? 125 | // just append item 126 | if(array.count == anIndex) 127 | { 128 | //add 129 | [array addObject:aKey]; 130 | } 131 | //insert object 132 | else 133 | { 134 | //insert 135 | [array insertObject:aKey atIndex:anIndex]; 136 | } 137 | 138 | //add to dictionary 139 | [dictionary setObject:anObject forKey:aKey]; 140 | } 141 | 142 | -(id)keyAtIndex:(NSUInteger)anIndex 143 | { 144 | //object 145 | id item = nil; 146 | 147 | if((nil == array) || 148 | (anIndex >= array.count)) 149 | { 150 | //bail 151 | goto bail; 152 | } 153 | 154 | //extract item 155 | item = [array objectAtIndex:anIndex]; 156 | 157 | bail: 158 | 159 | return item; 160 | } 161 | 162 | //given a key 163 | // return its index 164 | -(NSUInteger)indexOfKey:(id)aKey 165 | { 166 | return [array indexOfObject:aKey]; 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /Netiquette/AboutWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AboutWindowController.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | //support us button tag 12 | #define BUTTON_SUPPORT_US 100 13 | 14 | //more info button tag 15 | #define BUTTON_MORE_INFO 101 16 | 17 | @interface AboutWindowController : NSWindowController 18 | { 19 | 20 | } 21 | 22 | /* PROPERTIES */ 23 | 24 | //version label/string 25 | @property (weak) IBOutlet NSTextField *versionLabel; 26 | 27 | //patrons 28 | @property (unsafe_unretained) IBOutlet NSTextView *patrons; 29 | 30 | /* METHODS */ 31 | 32 | //automatically invoked when user clicks any of the buttons 33 | // perform actions, such as loading patreon or products URL 34 | -(IBAction)buttonHandler:(id)sender; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Netiquette/AboutWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AboutWindowController.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | 10 | #import "consts.h" 11 | #import "utilities.h" 12 | #import "AboutWindowController.h" 13 | 14 | @implementation AboutWindowController 15 | 16 | @synthesize patrons; 17 | @synthesize versionLabel; 18 | 19 | //automatically called when nib is loaded 20 | // ->center window 21 | -(void)awakeFromNib 22 | { 23 | //center 24 | [self.window center]; 25 | 26 | return; 27 | } 28 | 29 | //automatically invoked when window is loaded 30 | // set to window to white, set app version, patrons, etc 31 | -(void)windowDidLoad 32 | { 33 | //version 34 | NSString* version = nil; 35 | 36 | //super 37 | [super windowDidLoad]; 38 | 39 | //not in dark mode? 40 | // make window white 41 | if(YES != isDarkMode()) 42 | { 43 | //make white 44 | self.window.backgroundColor = NSColor.whiteColor; 45 | } 46 | 47 | //grab app version 48 | version = getAppVersion(); 49 | if(nil == version) 50 | { 51 | //default 52 | version = NSLocalizedString(@"unknown", @"unknown"); 53 | } 54 | 55 | //set version sting 56 | self.versionLabel.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Version: %@", @"Version: %@"), version]; 57 | 58 | //load patrons 59 | // <3 you guys & girls 60 | self.patrons.string = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"patrons" ofType:@"txt"] encoding:NSUTF8StringEncoding error:NULL]; 61 | if(nil == self.patrons.string) 62 | { 63 | //default 64 | self.patrons.string = @"error: failed to load patrons :/"; 65 | } 66 | 67 | //make 'Support Us' button first responder 68 | [self.window makeFirstResponder:[self.window.contentView viewWithTag:BUTTON_SUPPORT_US]]; 69 | 70 | return; 71 | } 72 | 73 | //automatically invoked when window is closing 74 | // ->make window unmodal 75 | -(void)windowWillClose:(NSNotification *)notification 76 | { 77 | //make un-modal 78 | [NSApplication.sharedApplication stopModal]; 79 | 80 | return; 81 | } 82 | 83 | //automatically invoked when user clicks any of the buttons 84 | // ->perform actions, such as loading patreon or products URL 85 | -(IBAction)buttonHandler:(id)sender 86 | { 87 | //support us button 88 | if(((NSButton*)sender).tag == BUTTON_SUPPORT_US) 89 | { 90 | //open URL 91 | // ->invokes user's default browser 92 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PATREON_URL]]; 93 | } 94 | 95 | //more info button 96 | else if(((NSButton*)sender).tag == BUTTON_MORE_INFO) 97 | { 98 | //open URL 99 | // ->invokes user's default browser 100 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PRODUCT_URL]]; 101 | } 102 | 103 | return; 104 | } 105 | @end 106 | -------------------------------------------------------------------------------- /Netiquette/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/1/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "Monitor.h" 10 | #import "TableViewController.h" 11 | #import "AboutWindowController.h" 12 | #import "PrefsWindowController.h" 13 | #import "UpdateWindowController.h" 14 | 15 | #import 16 | 17 | //not first run 18 | #define NOT_FIRST_TIME @"notFirstTime" 19 | 20 | 21 | @interface AppDelegate : NSObject 22 | 23 | /* PROPERTIES */ 24 | 25 | //friends of objective-see window 26 | @property (weak) IBOutlet NSWindow *friends; 27 | 28 | //menu items 29 | @property (weak) IBOutlet NSMenuItem *zoomInMenuItem; 30 | @property (weak) IBOutlet NSMenuItem *zoomOutMenuItem; 31 | 32 | //update window controller 33 | @property(nonatomic, retain)UpdateWindowController* updateWindowController; 34 | 35 | //about window controller 36 | @property(nonatomic, retain)AboutWindowController* aboutWindowController; 37 | 38 | //preferences window controller 39 | @property(nonatomic, retain)PrefsWindowController* prefsWindowController; 40 | 41 | //connection monitor 42 | @property (nonatomic, retain)Monitor* monitor; 43 | 44 | //table view controller 45 | @property (weak) IBOutlet TableViewController *tableViewController; 46 | 47 | /* METHODS */ 48 | 49 | //show prefs 50 | -(IBAction)showPreferences:(id)sender; 51 | 52 | //menu handler 53 | -(IBAction)viewMenuHandler:(id)sender; 54 | 55 | //toggle menu item 56 | -(void)toggleMenuItem:(NSMenuItem*)menuItem state:(NSControlStateValue)state; 57 | 58 | @end 59 | 60 | -------------------------------------------------------------------------------- /Netiquette/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/1/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "sort.h" 10 | #import "Event.h" 11 | #import "Update.h" 12 | #import "utilities.h" 13 | #import "AppDelegate.h" 14 | #import "3rd-party/OrderedDictionary.h" 15 | 16 | @interface AppDelegate () 17 | 18 | @property (weak) IBOutlet NSWindow *window; 19 | @end 20 | 21 | @implementation AppDelegate 22 | 23 | @synthesize friends; 24 | @synthesize monitor; 25 | @synthesize zoomInMenuItem; 26 | @synthesize zoomOutMenuItem; 27 | @synthesize tableViewController; 28 | @synthesize aboutWindowController; 29 | @synthesize prefsWindowController; 30 | @synthesize updateWindowController; 31 | 32 | //(re)set toolbar style 33 | -(void)awakeFromNib 34 | { 35 | //bs's default toolbar style isn't good for us 36 | if(@available(macOS 11,*)) 37 | { 38 | //set style 39 | self.window.toolbarStyle = NSWindowToolbarStyleExpanded; 40 | } 41 | 42 | //hide tab bar 43 | if (@available(macOS 10.12, *)) { 44 | [NSClassFromString(@"NSWindow") setAllowsAutomaticWindowTabbing:NO]; 45 | } 46 | 47 | return; 48 | } 49 | 50 | //main app interface 51 | // init UI and kick off monitoring 52 | -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { 53 | 54 | //first listing? 55 | __block BOOL isFirstEnumeration = YES; 56 | 57 | //sorted events 58 | __block OrderedDictionary* sortedEvents = nil; 59 | 60 | //init monitor 61 | self.monitor = [[Monitor alloc] init]; 62 | 63 | //automatically check for updates? 64 | // note: don't do this if running via LuLu 65 | if( (YES != [NSProcessInfo.processInfo.arguments containsObject:ARGS_LULU]) && 66 | (YES != [NSUserDefaults.standardUserDefaults boolForKey:PREFS_NO_UPDATE]) ) 67 | { 68 | //after a a few seconds 69 | // check for updates in background 70 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 71 | ^{ 72 | 73 | //check 74 | [self check4Update:nil]; 75 | 76 | }); 77 | } 78 | 79 | //make main window active/front 80 | [self.window makeKeyAndOrderFront:self]; 81 | 82 | //first time run (and not via LuLu) 83 | // show support/friends of objective-see window 84 | if( (YES != [NSProcessInfo.processInfo.arguments containsObject:ARGS_LULU]) && 85 | (YES != [[NSUserDefaults standardUserDefaults] boolForKey:NOT_FIRST_TIME]) ) 86 | { 87 | //set key 88 | [[NSUserDefaults standardUserDefaults] setBool:YES forKey:NOT_FIRST_TIME]; 89 | 90 | //front 91 | [self.friends makeKeyAndOrderFront:self]; 92 | 93 | //make first responder 94 | // calling this without a timeout sometimes fails :/ 95 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (100 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ 96 | 97 | //and make it first responder 98 | [self.friends makeFirstResponder:[self.friends.contentView viewWithTag:1]]; 99 | }); 100 | } 101 | 102 | //make app front 103 | [NSApp activateIgnoringOtherApps:YES]; 104 | 105 | //start (connection) monitor 106 | // auto-refreshes ever 5 seconds 107 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 108 | 109 | //start (connection) monitor 110 | // auto-refreshes ever 5 seconds 111 | [self.monitor start:5 callback:^(NSMutableDictionary* events) 112 | { 113 | //sync 114 | @synchronized (self) { 115 | 116 | //keep memory in check 117 | @autoreleasepool { 118 | 119 | //column 120 | __block NSUInteger column = 0; 121 | 122 | //direction 123 | __block BOOL ascending = 0; 124 | 125 | //on main thread 126 | // what to sort on? 127 | dispatch_sync(dispatch_get_main_queue(), 128 | ^{ 129 | //sort descriptors 130 | NSArray *sortDescriptors = nil; 131 | 132 | //grab 133 | sortDescriptors = self.tableViewController.outlineView.sortDescriptors; 134 | 135 | //no sort? 136 | // default to first column and ascending 137 | if(0 == sortDescriptors.count) 138 | { 139 | //default column 140 | column = 0; 141 | 142 | //default direction 143 | ascending = YES; 144 | } 145 | 146 | //what was sorted (already)? 147 | else 148 | { 149 | //column to sort on 150 | column = [self.tableViewController columnIDToIndex:sortDescriptors.firstObject.key]; 151 | 152 | //ascending? 153 | ascending = sortDescriptors.firstObject.ascending; 154 | } 155 | 156 | }); 157 | 158 | //combine 159 | // and then sort events 160 | sortedEvents = sortEvents(combineEvents(events), column, ascending); 161 | 162 | //update table on main thread 163 | dispatch_async(dispatch_get_main_queue(), 164 | ^{ 165 | 166 | //refresh table? 167 | if( (YES == isFirstEnumeration) || 168 | (YES == [NSUserDefaults.standardUserDefaults boolForKey:PREFS_AUTO_REFRESH]) ) 169 | { 170 | //update 171 | isFirstEnumeration = NO; 172 | 173 | //update table 174 | [self.tableViewController update:sortedEvents expand:YES reset:NO]; 175 | } 176 | 177 | }); 178 | } 179 | } 180 | }]; 181 | 182 | }); 183 | 184 | return; 185 | } 186 | 187 | //automatically close when user closes last window 188 | -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 189 | { 190 | return YES; 191 | } 192 | 193 | //app exit 194 | // stop/cleanup monitor 195 | -(void)applicationWillTerminate:(NSApplication *)application 196 | { 197 | //stop/cleanup monitor 198 | if(nil != self.monitor) 199 | { 200 | //stop 201 | [self.monitor stop]; 202 | 203 | //deinit 204 | [self.monitor deinit]; 205 | 206 | //unset 207 | self.monitor = nil; 208 | } 209 | 210 | return; 211 | } 212 | 213 | 214 | //hide friends view 215 | -(IBAction)hideFriends:(id)sender 216 | { 217 | //once 218 | static dispatch_once_t onceToken; 219 | 220 | //close and launch main window 221 | dispatch_once (&onceToken, ^{ 222 | 223 | //close 224 | [self.friends close]; 225 | 226 | }); 227 | 228 | return; 229 | } 230 | 231 | //call into Update obj 232 | // check to see if there an update? 233 | -(IBAction)check4Update:(id)sender 234 | { 235 | //update obj 236 | Update* update = nil; 237 | 238 | //init update obj 239 | update = [[Update alloc] init]; 240 | 241 | //check for update 242 | // 'updateResponse newVersion:' method will be called when check is done 243 | [update checkForUpdate:^(NSUInteger result, NSString* newVersion) { 244 | 245 | //show update window? 246 | if( (nil != sender) || 247 | (UPDATE_NEW_VERSION == result) ) 248 | { 249 | //process response 250 | [self updateResponse:result newVersion:newVersion]; 251 | } 252 | }]; 253 | 254 | return; 255 | } 256 | 257 | //process update response 258 | // error, no update, update/new version 259 | -(void)updateResponse:(NSInteger)result newVersion:(NSString*)newVersion 260 | { 261 | //details 262 | NSString* details = nil; 263 | 264 | //action 265 | NSString* action = nil; 266 | 267 | //new version? 268 | // configure ui, and add 'update' button 269 | if(UPDATE_NEW_VERSION == result) 270 | { 271 | //set details 272 | details = [NSString stringWithFormat:NSLocalizedString(@"a new version (%@) is available!", @"a new version (%@) is available!"), newVersion]; 273 | 274 | //set action 275 | action = NSLocalizedString(@"Update",@"Update"); 276 | 277 | //alloc update window 278 | updateWindowController = [[UpdateWindowController alloc] initWithWindowNibName:@"UpdateWindow"]; 279 | 280 | //configure 281 | [self.updateWindowController configure:details buttonTitle:action]; 282 | 283 | //center window 284 | [[self.updateWindowController window] center]; 285 | 286 | //show it 287 | [self.updateWindowController showWindow:self]; 288 | 289 | //invoke function in background that will make window modal 290 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 291 | 292 | //make modal 293 | makeModal(self.updateWindowController); 294 | 295 | }); 296 | } 297 | 298 | //no new version 299 | // configure ui, and add 'close' button 300 | else 301 | { 302 | //set details 303 | details = NSLocalizedString(@"No new versions available", @"No new versions available"); 304 | 305 | //set action 306 | action = NSLocalizedString(@"Close", @"Close"); 307 | 308 | //alloc update window 309 | updateWindowController = [[UpdateWindowController alloc] initWithWindowNibName:@"UpdateWindow"]; 310 | 311 | //configure 312 | [self.updateWindowController configure:details buttonTitle:action]; 313 | 314 | //center window 315 | [[self.updateWindowController window] center]; 316 | 317 | //show it 318 | [self.updateWindowController showWindow:self]; 319 | 320 | //invoke function in background that will make window modal 321 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 322 | 323 | //make modal 324 | makeModal(self.updateWindowController); 325 | 326 | }); 327 | } 328 | 329 | return; 330 | } 331 | 332 | //menu handler for 'view' menu 333 | // actions include, refresh, zoom in/out, etc 334 | -(IBAction)viewMenuHandler:(id)sender 335 | { 336 | 337 | //tag 338 | NSInteger tag = 0; 339 | 340 | //init tag 341 | tag = ((NSButton*)sender).tag; 342 | 343 | //handle each 344 | switch (tag) { 345 | 346 | //expand 347 | case VIEW_EXPAND: 348 | [self.tableViewController expandAll]; 349 | break; 350 | 351 | //collapse 352 | case VIEW_COLLAPSE: 353 | [self.tableViewController collapseAll]; 354 | break; 355 | 356 | //zoom in 357 | case VIEW_ZOOM_IN: 358 | [self.tableViewController zoomIn]; 359 | break; 360 | 361 | //zoom out 362 | case VIEW_ZOOM_OUT: 363 | [self.tableViewController zoomOut]; 364 | break; 365 | 366 | default: 367 | break; 368 | } 369 | 370 | return; 371 | } 372 | 373 | //toggle menu item 374 | -(void)toggleMenuItem:(NSMenuItem*)menuItem state:(NSControlStateValue)state 375 | { 376 | //off? 377 | if(state == NSControlStateValueOff) 378 | { 379 | //disable 380 | [menuItem setTarget:nil]; 381 | [menuItem setAction:NULL]; 382 | } 383 | 384 | //on? 385 | else 386 | { 387 | //enable 388 | [menuItem setTarget:self]; 389 | [menuItem setAction:@selector(viewMenuHandler:)]; 390 | } 391 | 392 | return; 393 | } 394 | 395 | //menu handler: 'Preferences' 396 | // alloc and show Preferences window 397 | -(IBAction)showPreferences:(id)sender 398 | { 399 | //alloc prefs window controller 400 | if(nil == self.prefsWindowController) 401 | { 402 | //alloc 403 | prefsWindowController = [[PrefsWindowController alloc] initWithWindowNibName:@"Preferences"]; 404 | } 405 | 406 | //make active 407 | [self makeActive:self.prefsWindowController]; 408 | 409 | return; 410 | } 411 | 412 | //menu handler: about 413 | // alloc and show 'about' Window 414 | -(IBAction)about:(id)sender 415 | { 416 | //alloc/init settings window 417 | if(nil == self.aboutWindowController) 418 | { 419 | //alloc/init 420 | aboutWindowController = [[AboutWindowController alloc] initWithWindowNibName:@"AboutWindow"]; 421 | } 422 | 423 | //center window 424 | [[self.aboutWindowController window] center]; 425 | 426 | //show it 427 | [self.aboutWindowController showWindow:self]; 428 | 429 | return; 430 | } 431 | 432 | //support handler 433 | // open support (patreon) url 434 | - (IBAction)supportUs:(id)sender 435 | { 436 | //open URL 437 | [NSWorkspace.sharedWorkspace openURL:[NSURL URLWithString:PATREON_URL]]; 438 | 439 | //close window 440 | [self.friends close]; 441 | 442 | return; 443 | } 444 | 445 | //don't support handler 446 | //just close friends window 447 | -(IBAction)closeSupportWindow:(id)sender 448 | { 449 | //close window 450 | [self.friends close]; 451 | 452 | return; 453 | } 454 | 455 | //make a window control/window front/active 456 | -(void)makeActive:(NSWindowController*)windowController 457 | { 458 | //make foreground 459 | [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 460 | 461 | //center 462 | [windowController.window center]; 463 | 464 | //show it 465 | [windowController showWindow:self]; 466 | 467 | //make it key window 468 | [[windowController window] makeKeyAndOrderFront:self]; 469 | 470 | //make window front 471 | [NSApp activateIgnoringOtherApps:YES]; 472 | 473 | return; 474 | } 475 | 476 | //menu handler: quit 477 | -(IBAction)quit:(id)sender 478 | { 479 | //exit 480 | [NSApp terminate:self]; 481 | 482 | return; 483 | } 484 | 485 | @end 486 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Contract.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Contract.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Contract.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Contract.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Contract.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/ContractAlternate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "alternate.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/ContractAlternate.imageset/alternate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/ContractAlternate.imageset/alternate.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Expand.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Expand.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Expand.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Expand.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Expand.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/ExpandAlternate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "alternate.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/ExpandAlternate.imageset/alternate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/ExpandAlternate.imageset/alternate.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Friends1Password.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "darkMode.png", 5 | "idiom" : "mac" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "light" 12 | } 13 | ], 14 | "filename" : "lightMode.png", 15 | "idiom" : "mac" 16 | }, 17 | { 18 | "appearances" : [ 19 | { 20 | "appearance" : "luminosity", 21 | "value" : "dark" 22 | } 23 | ], 24 | "filename" : "darkMode.png", 25 | "idiom" : "mac" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Friends1Password.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Friends1Password.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Friends1Password.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Friends1Password.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsFleet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsFleet.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsFleet.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsFleet.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsFleet.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsHuntress.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "darkMode.png", 5 | "idiom" : "mac" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "light" 12 | } 13 | ], 14 | "filename" : "lightMode.png", 15 | "idiom" : "mac" 16 | }, 17 | { 18 | "appearances" : [ 19 | { 20 | "appearance" : "luminosity", 21 | "value" : "dark" 22 | } 23 | ], 24 | "filename" : "darkMode.png", 25 | "idiom" : "mac" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsHuntress.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsHuntress.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsHuntress.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsHuntress.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsJamf.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsJamf.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsJamf.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsJamf.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsJamf.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsKandji.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsKandji.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsKandji.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsKandji.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsKandji.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsMacPaw.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsMacPaw.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsMacPaw.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsMacPaw.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsMacPaw.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsMalwarebytes.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "malwarebytes.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsMalwarebytes.imageset/malwarebytes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsMalwarebytes.imageset/malwarebytes.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsPANW.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "darkMode.png", 5 | "idiom" : "mac" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "light" 12 | } 13 | ], 14 | "filename" : "lightMode.png", 15 | "idiom" : "mac" 16 | }, 17 | { 18 | "appearances" : [ 19 | { 20 | "appearance" : "luminosity", 21 | "value" : "dark" 22 | } 23 | ], 24 | "filename" : "darkMode.png", 25 | "idiom" : "mac" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsPANW.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsPANW.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsPANW.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsPANW.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsiVerify.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "iVerify.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/FriendsiVerify.imageset/iVerify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/FriendsiVerify.imageset/iVerify.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "logo.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Logo.imageset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Logo.imageset/logo.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/LogoBG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "logoBG.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/LogoBG.imageset/logoBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/LogoBG.imageset/logoBG.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/LogoOver.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "logoOver.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/LogoOver.imageset/logoOver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/LogoOver.imageset/logoOver.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Love.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "heart_light-1.pdf" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "heart_light.pdf", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "heart_dark.pdf", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Love.imageset/heart_dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Love.imageset/heart_dark.pdf -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Love.imageset/heart_light-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Love.imageset/heart_light-1.pdf -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Love.imageset/heart_light.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Love.imageset/heart_light.pdf -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Preferences.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Preferences.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Preferences.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Preferences.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Preferences.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/PreferencesAlternate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "preferences.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/PreferencesAlternate.imageset/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/PreferencesAlternate.imageset/preferences.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/PrefsUpdate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "prefsUpdate Any.pdf" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "prefsUpdate Light.pdf", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "prefsUpdate Dark.pdf", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/PrefsUpdate.imageset/prefsUpdate Any.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/PrefsUpdate.imageset/prefsUpdate Any.pdf -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/PrefsUpdate.imageset/prefsUpdate Dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/PrefsUpdate.imageset/prefsUpdate Dark.pdf -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/PrefsUpdate.imageset/prefsUpdate Light.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/PrefsUpdate.imageset/prefsUpdate Light.pdf -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Save.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "darkMode.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "lightMode.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "darkMode.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Save.imageset/darkMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Save.imageset/darkMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/Save.imageset/lightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/Save.imageset/lightMode.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/SaveAlternate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "save.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/SaveAlternate.imageset/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/SaveAlternate.imageset/save.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/niqLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "niqLogo.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "niqLogo.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "niqLogo.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/niqLogo.imageset/niqLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/niqLogo.imageset/niqLogo.png -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/niqText.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "niqText.png" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "niqText.png", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "mac", 19 | "filename" : "niqText.png", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | }, 32 | "properties" : { 33 | "preserves-vector-representation" : true 34 | } 35 | } -------------------------------------------------------------------------------- /Netiquette/Assets.xcassets/niqText.imageset/niqText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/Assets.xcassets/niqText.imageset/niqText.png -------------------------------------------------------------------------------- /Netiquette/Base.lproj/AboutWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 100 | 101 | 102 | 103 | 104 | 105 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /Netiquette/Base.lproj/Preferences.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /Netiquette/Base.lproj/UpdateWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 57 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Netiquette/CustomRow.h: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryRow.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 4/4/15. 6 | // Copyright (c) 2015 Objective-See. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CustomRow : NSTableRowView 12 | 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Netiquette/CustomRow.m: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryRow.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 4/4/15. 6 | // Copyright (c) 2015 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "CustomRow.h" 10 | #import "utilities.h" 11 | 12 | @implementation CustomRow 13 | 14 | //custom row selection 15 | -(void)drawSelectionInRect:(NSRect)dirtyRect 16 | { 17 | //selection rect 18 | NSRect selectionRect = {0}; 19 | 20 | //selection path 21 | NSBezierPath *selectionPath = nil; 22 | 23 | //highlight selected rows 24 | if(self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) 25 | { 26 | //make selection rect 27 | selectionRect = NSInsetRect(self.bounds, 2.5, 2.5); 28 | 29 | //dark mode highlight 30 | if(YES == isDarkMode()) 31 | { 32 | //set stroke 33 | [[NSColor colorWithCalibratedWhite:1.0 alpha:1.0] setStroke]; 34 | 35 | //set fill 36 | [[NSColor colorWithCalibratedWhite:.50 alpha:1.0] setFill]; 37 | } 38 | //light mode highlight 39 | else 40 | { 41 | //set stroke 42 | [[NSColor colorWithCalibratedWhite:.65 alpha:1.0] setStroke]; 43 | 44 | //set fill 45 | [[NSColor colorWithCalibratedWhite:.82 alpha:1.0] setFill]; 46 | } 47 | 48 | //create selection path 49 | // ...with rounded corners 50 | selectionPath = [NSBezierPath bezierPathWithRoundedRect:selectionRect xRadius:5 yRadius:5]; 51 | 52 | //fill 53 | [selectionPath fill]; 54 | 55 | //stroke 56 | [selectionPath stroke]; 57 | } 58 | 59 | return; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Netiquette/Event.h: -------------------------------------------------------------------------------- 1 | // 2 | // event.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/6/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "consts.h" 10 | #import "procInfo/procInfo.h" 11 | 12 | #import 13 | #import 14 | #import 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | extern NSConstantString* kNStatSrcKeyPID; 19 | extern NSConstantString* kNStatSrcKeyUUID; 20 | extern NSConstantString* kNStatSrcKeyLocal; 21 | extern NSConstantString* kNStatSrcKeyRemote; 22 | extern NSConstantString* kNStatSrcKeyTxBytes; 23 | extern NSConstantString* kNStatSrcKeyRxBytes; 24 | extern NSConstantString* kNStatSrcKeyProvider; 25 | extern NSConstantString* kNStatSrcKeyTCPState; 26 | extern NSConstantString* kNStatSrcKeyInterface; 27 | 28 | @interface Event : NSObject 29 | 30 | /* PROPERTIES */ 31 | 32 | @property (nonatomic, retain) Process* process; 33 | 34 | @property (nonatomic, retain) NSString* interface; 35 | 36 | @property (nonatomic, retain) NSMutableDictionary* localAddress; 37 | @property (nonatomic, retain) NSMutableDictionary* remoteAddress; 38 | 39 | @property (nonatomic, retain) NSString* provider; 40 | @property (nonatomic, retain) NSString* tcpState; 41 | 42 | @property unsigned long bytesUp; 43 | @property unsigned long bytesDown; 44 | 45 | /* METHODS */ 46 | 47 | //init with event 48 | // process raw event, adding process info, etc 49 | -(id)init:(NSDictionary*)event process:(Process*)process; 50 | 51 | //given a search string 52 | // check if path, pid, ips, etc match? 53 | -(BOOL)matches:(NSString*)search; 54 | 55 | //convert to JSON 56 | -(NSString*)toJSON; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END 61 | -------------------------------------------------------------------------------- /Netiquette/Event.m: -------------------------------------------------------------------------------- 1 | // 2 | // event.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/6/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "Event.h" 10 | #import "Monitor.h" 11 | #import "utilities.h" 12 | 13 | @implementation Event 14 | 15 | //init with event 16 | // process raw event, adding process info, etc 17 | -(id)init:(NSDictionary*)event process:(Process*)process 18 | { 19 | //flag 20 | BOOL resolveName = NO; 21 | 22 | //interface name 23 | char interfaceName[IF_NAMESIZE+1] = {0}; 24 | 25 | //super 26 | self = [super init]; 27 | if(self != nil) 28 | { 29 | //terminal exec 30 | // should resolve flags? 31 | if(YES == [NSProcessInfo.processInfo.arguments containsObject:@"-list"]) 32 | { 33 | //set 34 | resolveName = [NSProcessInfo.processInfo.arguments containsObject:@"-names"]; 35 | } 36 | //non-terminal exec 37 | // should resolve flags? 38 | else 39 | { 40 | //set 41 | resolveName = [NSUserDefaults.standardUserDefaults boolForKey:PREFS_RESOLVE_NAMES]; 42 | } 43 | 44 | //monitor provided cache'd process? 45 | if( (nil != process) && 46 | (process.pid == [event[kNStatSrcKeyPID] intValue]) ) 47 | { 48 | //use 49 | self.process = process; 50 | } 51 | //generate (new) process 52 | else 53 | { 54 | //generate 55 | self.process = [[Process alloc] init:[event[kNStatSrcKeyPID] intValue]]; 56 | 57 | //generate code signing info 58 | [self.process generateSigningInfo:kSecCSDefaultFlags]; 59 | } 60 | 61 | //extract provider 62 | self.provider = event[kNStatSrcKeyProvider]; 63 | 64 | //extract state 65 | // note: nil, unless provider is TCP 66 | self.tcpState = event[kNStatSrcKeyTCPState]; 67 | 68 | //convert local address 69 | if(nil != event[kNStatSrcKeyLocal]) 70 | { 71 | //save 72 | self.localAddress = [self parseAddress:event[kNStatSrcKeyLocal]]; 73 | } 74 | 75 | //convert remote address 76 | // and resolve remote name if necessary 77 | if(nil != event[kNStatSrcKeyRemote]) 78 | { 79 | //save 80 | self.remoteAddress = [self parseAddress:event[kNStatSrcKeyRemote]]; 81 | } 82 | 83 | //in background 84 | // resolve host name 85 | if(YES == resolveName) 86 | { 87 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 88 | 89 | //local name 90 | NSString* localName = nil; 91 | 92 | //remote name 93 | NSString* remoteName = nil; 94 | 95 | //resolve /save local 96 | if(nil != event[kNStatSrcKeyLocal]) 97 | { 98 | //resolve 99 | localName = [self resolveName:(struct sockaddr *)[(NSData*)(event[kNStatSrcKeyLocal]) bytes]]; 100 | if(0 != localName.length) 101 | { 102 | self.localAddress[KEY_HOST_NAME] = localName; 103 | } 104 | } 105 | 106 | //resolve / save remove 107 | if(nil != event[kNStatSrcKeyRemote]) 108 | { 109 | //resolve 110 | remoteName = [self resolveName:(struct sockaddr *)[(NSData*)(event[kNStatSrcKeyRemote]) bytes]]; 111 | if(0 != remoteName.length) 112 | { 113 | self.remoteAddress[KEY_HOST_NAME] = remoteName; 114 | } 115 | } 116 | 117 | }); 118 | } 119 | 120 | //extract and convert interface (number) to name 121 | if(NULL != if_indextoname([event[kNStatSrcKeyInterface] intValue], (char*)&interfaceName)) 122 | { 123 | //save/convert 124 | self.interface = [NSString stringWithUTF8String:interfaceName]; 125 | } 126 | 127 | //extract bytes up 128 | self.bytesUp = [event[kNStatSrcKeyTxBytes] unsignedLongValue]; 129 | 130 | //extract bytes down 131 | self.bytesDown = [event[kNStatSrcKeyRxBytes] unsignedLongValue]; 132 | 133 | } 134 | 135 | return self; 136 | } 137 | 138 | //parse/extract addr, port, etc... 139 | -(NSMutableDictionary*)parseAddress:(NSData*)data 140 | { 141 | //address 142 | NSMutableDictionary* address = nil; 143 | 144 | //ipv4 struct 145 | struct sockaddr_in *ipv4 = NULL; 146 | 147 | //ipv6 struct 148 | struct sockaddr_in6 *ipv6 = NULL; 149 | 150 | //init 151 | address = [NSMutableDictionary dictionary]; 152 | 153 | //parse 154 | // for now, only support IPv4 and IPv6 155 | switch(((struct sockaddr *)data.bytes)->sa_family) 156 | { 157 | //IPv4 158 | case AF_INET: 159 | 160 | //typecast 161 | ipv4 = (struct sockaddr_in *)data.bytes; 162 | 163 | //add family 164 | address[KEY_FAMILY] = [NSNumber numberWithInt:AF_INET]; 165 | 166 | //add port 167 | address[KEY_PORT] = [NSNumber numberWithUnsignedShort:ntohs(ipv4->sin_port)]; 168 | 169 | //format/add address 170 | address[KEY_ADDRRESS] = convertIPAddr((unsigned char*)&ipv4->sin_addr, AF_INET); 171 | 172 | break; 173 | 174 | //IPv6 175 | case AF_INET6: 176 | { 177 | //typecast 178 | ipv6 = (struct sockaddr_in6 *)data.bytes; 179 | 180 | //add family 181 | address[KEY_FAMILY] = [NSNumber numberWithInt:AF_INET6]; 182 | 183 | //add port 184 | address[KEY_PORT] = [NSNumber numberWithUnsignedShort:ntohs(ipv6->sin6_port)]; 185 | 186 | //format/add address 187 | address[KEY_ADDRRESS] = convertIPAddr((unsigned char*)&ipv6->sin6_addr, AF_INET6); 188 | 189 | } 190 | } 191 | 192 | return address; 193 | } 194 | 195 | //given a search string 196 | // check if path, pid, ips, etc match? 197 | -(BOOL)matches:(NSString*)search 198 | { 199 | //flag 200 | BOOL matches = NO; 201 | 202 | //name match 203 | if(YES == [self.process.binary.name localizedCaseInsensitiveContainsString:search]) 204 | { 205 | //match 206 | matches = YES; 207 | 208 | //bail 209 | goto bail; 210 | } 211 | 212 | //pid match? 213 | if(YES == [[NSString stringWithFormat:@"%d", self.process.pid] containsString:search]) 214 | { 215 | //match 216 | matches = YES; 217 | 218 | //bail 219 | goto bail; 220 | } 221 | 222 | //path match? 223 | if(YES == [self.process.path localizedCaseInsensitiveContainsString:search]) 224 | { 225 | //match 226 | matches = YES; 227 | 228 | //bail 229 | goto bail; 230 | } 231 | 232 | 233 | //local ip match? 234 | if(YES == [self.localAddress[KEY_ADDRRESS] containsString:search]) 235 | { 236 | //match 237 | matches = YES; 238 | 239 | //bail 240 | goto bail; 241 | } 242 | 243 | //local host name match? 244 | if( (0 != [self.localAddress[KEY_HOST_NAME] length]) && 245 | (YES == [self.localAddress[KEY_HOST_NAME] localizedCaseInsensitiveContainsString:search]) ) 246 | { 247 | //match 248 | matches = YES; 249 | 250 | //bail 251 | goto bail; 252 | } 253 | 254 | //local port match? 255 | if(YES == [[self.localAddress[KEY_PORT] stringValue] containsString:search]) 256 | { 257 | //match 258 | matches = YES; 259 | 260 | //bail 261 | goto bail; 262 | } 263 | 264 | //remote ip match? 265 | if(YES == [self.remoteAddress[KEY_ADDRRESS] containsString:search]) 266 | { 267 | //match 268 | matches = YES; 269 | 270 | //bail 271 | goto bail; 272 | } 273 | 274 | //remote host name match? 275 | if( (0 != [self.remoteAddress[KEY_HOST_NAME] length]) && 276 | (YES == [self.remoteAddress[KEY_HOST_NAME] localizedCaseInsensitiveContainsString:search]) ) 277 | { 278 | //match 279 | matches = YES; 280 | 281 | //bail 282 | goto bail; 283 | } 284 | 285 | //remote port match? 286 | if(YES == [[self.remoteAddress[KEY_PORT] stringValue] containsString:search]) 287 | { 288 | //match 289 | matches = YES; 290 | 291 | //bail 292 | goto bail; 293 | } 294 | 295 | //protocol match? 296 | if(YES == [self.provider localizedCaseInsensitiveContainsString:search]) 297 | { 298 | //match 299 | matches = YES; 300 | 301 | //bail 302 | goto bail; 303 | } 304 | 305 | //interface match? 306 | if(YES == [self.interface localizedCaseInsensitiveContainsString:search]) 307 | { 308 | //match 309 | matches = YES; 310 | 311 | //bail 312 | goto bail; 313 | } 314 | 315 | //state 316 | if( (0 != self.tcpState.length) && 317 | (YES == [self.tcpState localizedCaseInsensitiveContainsString:search]) ) 318 | { 319 | //match 320 | matches = YES; 321 | 322 | //bail 323 | goto bail; 324 | } 325 | 326 | //bytes up 327 | if(YES == [[NSByteCountFormatter stringFromByteCount:self.bytesUp countStyle:NSByteCountFormatterCountStyleFile] containsString:search]) 328 | { 329 | //match 330 | matches = YES; 331 | 332 | //bail 333 | goto bail; 334 | } 335 | 336 | //bytes down 337 | if(YES == [[NSByteCountFormatter stringFromByteCount:self.bytesDown countStyle:NSByteCountFormatterCountStyleFile] containsString:search]) 338 | { 339 | //match 340 | matches = YES; 341 | 342 | //bail 343 | goto bail; 344 | } 345 | 346 | bail: 347 | 348 | return matches; 349 | } 350 | 351 | //resolve name via (reverse) dns 352 | // though it checks a cache first... 353 | -(NSString*)resolveName:(struct sockaddr *)sockAddr 354 | { 355 | //name 356 | NSString* resolvedName = nil; 357 | 358 | //host 359 | char host[NI_MAXHOST+1] = {0}; 360 | 361 | //cache 362 | static NSCache* cache = nil; 363 | 364 | //once 365 | static dispatch_once_t once = 0; 366 | 367 | //key 368 | NSData* key = nil; 369 | 370 | //init cache 371 | dispatch_once (&once, ^{ 372 | 373 | //init cache 374 | cache = [[NSCache alloc] init]; 375 | 376 | //set cache limit 377 | cache.countLimit = 2048; 378 | 379 | }); 380 | 381 | //init ipv4 cache key 382 | if(AF_INET == sockAddr->sa_family) 383 | { 384 | //key 385 | key = [NSData dataWithBytes:(unsigned char*)&((struct sockaddr_in *)sockAddr)->sin_addr length:sizeof(struct in_addr)]; 386 | } 387 | //init ipv6 cache key 388 | else if(AF_INET6 == sockAddr->sa_family) 389 | { 390 | key = [NSData dataWithBytes:(unsigned char*)&((struct sockaddr_in6 *)sockAddr)->sin6_addr length:sizeof(struct in6_addr)]; 391 | } 392 | 393 | //in cache? 394 | resolvedName = [cache objectForKey:key]; 395 | if(0 != resolvedName.length) 396 | { 397 | //done 398 | goto bail; 399 | } 400 | 401 | //resolve name 402 | if(0 == getnameinfo(sockAddr, sockAddr->sa_len, host, NI_MAXHOST, NULL, 0, 0)) 403 | { 404 | //convert 405 | resolvedName = [NSString stringWithUTF8String:host]; 406 | 407 | //save to cache 408 | if(0 != resolvedName.length) 409 | { 410 | //cache 411 | [cache setObject:resolvedName forKey:key]; 412 | } 413 | } 414 | 415 | bail: 416 | 417 | return resolvedName; 418 | } 419 | 420 | //convert to JSON 421 | -(NSString*)toJSON 422 | { 423 | //state 424 | NSString* state = @"n/a"; 425 | 426 | //interface 427 | NSString* interface = @""; 428 | 429 | //tcp state 430 | if(0 != self.tcpState.length) 431 | { 432 | //set 433 | state = self.tcpState; 434 | } 435 | 436 | //set interface 437 | if(0 != self.interface.length) 438 | { 439 | //set 440 | interface = self.interface; 441 | } 442 | 443 | return [NSString stringWithFormat:@"{\"%@\": \"%@\", \"%@\": \"%@\", \"%@\": \"%@\", \"%@\": \"%@\", \"%@\": \"%@\", \"%@\": \"%@\", \"%@\": \"%@\", \"%@\": \"%@\", \"%@\": \"%lu\", \"%@\": \"%lu\"}", INTERFACE, interface, PROTOCOL, self.provider, LOCAL_ADDRESS, self.localAddress[KEY_ADDRRESS], LOCAL_PORT, self.localAddress[KEY_PORT], REMOTE_ADDRESS, self.remoteAddress[KEY_ADDRRESS], REMOTE_PORT, self.remoteAddress[KEY_PORT], REMOTE_HOST, self.remoteAddress[KEY_HOST_NAME], CONNECTION_STATE, state, BYTES_UP, self.bytesUp, BYTES_DOWN, self.bytesDown]; 444 | } 445 | 446 | @end 447 | -------------------------------------------------------------------------------- /Netiquette/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright (c) 2024 Objective-See. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplicationKeyEvents 31 | 32 | 33 | -------------------------------------------------------------------------------- /Netiquette/InfoPlist.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "CFBundleName" : { 5 | "comment" : "Bundle name", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "en" : { 9 | "stringUnit" : { 10 | "state" : "new", 11 | "value" : "Netiquette" 12 | } 13 | }, 14 | "es" : { 15 | "stringUnit" : { 16 | "state" : "translated", 17 | "value" : "Netiquette" 18 | } 19 | } 20 | } 21 | }, 22 | "NSHumanReadableCopyright" : { 23 | "comment" : "Copyright (human-readable)", 24 | "extractionState" : "extracted_with_value", 25 | "localizations" : { 26 | "en" : { 27 | "stringUnit" : { 28 | "state" : "new", 29 | "value" : "Copyright (c) 2024 Objective-See. All rights reserved." 30 | } 31 | }, 32 | "es" : { 33 | "stringUnit" : { 34 | "state" : "translated", 35 | "value" : "Copyright (c) 2024 Objective-See. Todos los derechos reservados." 36 | } 37 | } 38 | } 39 | } 40 | }, 41 | "version" : "1.0" 42 | } -------------------------------------------------------------------------------- /Netiquette/Item.h: -------------------------------------------------------------------------------- 1 | // 2 | // Item.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/20/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "procInfo/procInfo.h" 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface Item : NSObject 15 | 16 | //process 17 | @property(nonatomic, retain)Process* process; 18 | 19 | //connections 20 | @property(nonatomic, retain)NSMutableArray* connections; 21 | 22 | /* METHODS */ 23 | 24 | //init 25 | -(id)init:(Process*)process; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /Netiquette/Item.m: -------------------------------------------------------------------------------- 1 | // 2 | // Item.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/20/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "Item.h" 10 | 11 | @implementation Item 12 | 13 | //init 14 | -(id)init:(Process*)process 15 | { 16 | //super 17 | self = [super init]; 18 | if(self != nil) 19 | { 20 | //init 21 | self.connections = [NSMutableArray array]; 22 | 23 | //save 24 | self.process = process; 25 | } 26 | 27 | return self; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Netiquette/Localizable.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "\"connections\": [" : { 5 | "comment" : "\"connections\": [", 6 | "localizations" : { 7 | "es" : { 8 | "stringUnit" : { 9 | "state" : "translated", 10 | "value" : "\"conexiones\": [" 11 | } 12 | } 13 | } 14 | }, 15 | "{\"ERROR\" : \"failed to convert output to JSON\"}" : { 16 | "comment" : "{\"ERROR\" : \"failed to convert output to JSON\"}", 17 | "localizations" : { 18 | "es" : { 19 | "stringUnit" : { 20 | "state" : "translated", 21 | "value" : "{\"ERROR\" : \"fallo al convertir la salida a JSON\"}" 22 | } 23 | } 24 | } 25 | }, 26 | "{\"process\": {\"pid\": \"%d\", \"path\": \"%@\", \"signature(s)\": %@}," : { 27 | "comment" : "{\"process\": {\"pid\": \"%d\", \"path\": \"%@\", \"signature(s)\": %@},", 28 | "localizations" : { 29 | "en" : { 30 | "stringUnit" : { 31 | "state" : "new", 32 | "value" : "{\"process\": {\"pid\": \"%1$d\", \"path\": \"%2$@\", \"signature(s)\": %3$@}," 33 | } 34 | }, 35 | "es" : { 36 | "stringUnit" : { 37 | "state" : "translated", 38 | "value" : "{\"proceso\": {\"pid\": \"%1$d\", \"ruta\": \"%2$@\", \"firma(s)\": %3$@}," 39 | } 40 | } 41 | } 42 | }, 43 | "a new version (%@) is available!" : { 44 | "comment" : "a new version (%@) is available!", 45 | "localizations" : { 46 | "es" : { 47 | "stringUnit" : { 48 | "state" : "translated", 49 | "value" : "¡una nueva versión (%@) está disponible!" 50 | } 51 | } 52 | } 53 | }, 54 | "Close" : { 55 | "comment" : "Close", 56 | "localizations" : { 57 | "es" : { 58 | "stringUnit" : { 59 | "state" : "translated", 60 | "value" : "Cerrar" 61 | } 62 | } 63 | } 64 | }, 65 | "Details: %@" : { 66 | "comment" : "Details: %@", 67 | "localizations" : { 68 | "es" : { 69 | "stringUnit" : { 70 | "state" : "translated", 71 | "value" : "Detalles: %@" 72 | } 73 | } 74 | } 75 | }, 76 | "Enumerating Network Connections..." : { 77 | "comment" : "Enumerating Network Connections...", 78 | "localizations" : { 79 | "es" : { 80 | "stringUnit" : { 81 | "state" : "translated", 82 | "value" : "Enumerando Conexiones de Red..." 83 | } 84 | } 85 | } 86 | }, 87 | "ERROR: Failed To Save Output" : { 88 | "comment" : "ERROR: Failed To Save Output", 89 | "localizations" : { 90 | "es" : { 91 | "stringUnit" : { 92 | "state" : "translated", 93 | "value" : "ERROR: Fallo al Guardar la Salida" 94 | } 95 | } 96 | } 97 | }, 98 | "error: update check failed" : { 99 | "comment" : "error: update check failed", 100 | "localizations" : { 101 | "es" : { 102 | "stringUnit" : { 103 | "state" : "translated", 104 | "value" : "error: falló la verificación de actualización" 105 | } 106 | } 107 | } 108 | }, 109 | "Installed version (%@) is the latest." : { 110 | "comment" : "Installed version (%@) is the latest.", 111 | "localizations" : { 112 | "es" : { 113 | "stringUnit" : { 114 | "state" : "translated", 115 | "value" : "La versión instalada (%@) es la más reciente." 116 | } 117 | } 118 | } 119 | }, 120 | "No (3rd-party) Network Connections Detected" : { 121 | "comment" : "No (3rd-party) Network Connections Detected", 122 | "localizations" : { 123 | "es" : { 124 | "stringUnit" : { 125 | "state" : "translated", 126 | "value" : "No se Detectaron Conexiones de Red (de terceros)" 127 | } 128 | } 129 | } 130 | }, 131 | "No Network Connections Detected" : { 132 | "comment" : "No Network Connections Detected", 133 | "localizations" : { 134 | "es" : { 135 | "stringUnit" : { 136 | "state" : "translated", 137 | "value" : "No se Detectaron Conexiones de Red" 138 | } 139 | } 140 | } 141 | }, 142 | "No new versions available" : { 143 | "comment" : "No new versions available", 144 | "localizations" : { 145 | "es" : { 146 | "stringUnit" : { 147 | "state" : "translated", 148 | "value" : "No hay nuevas versiones disponibles" 149 | } 150 | } 151 | } 152 | }, 153 | "unknown" : { 154 | "comment" : "unknown", 155 | "localizations" : { 156 | "es" : { 157 | "stringUnit" : { 158 | "state" : "translated", 159 | "value" : "desconocido" 160 | } 161 | } 162 | } 163 | }, 164 | "Update" : { 165 | "comment" : "Update", 166 | "localizations" : { 167 | "es" : { 168 | "stringUnit" : { 169 | "state" : "translated", 170 | "value" : "Actualizar" 171 | } 172 | } 173 | } 174 | }, 175 | "Version: %@" : { 176 | "comment" : "Version: %@", 177 | "localizations" : { 178 | "es" : { 179 | "stringUnit" : { 180 | "state" : "translated", 181 | "value" : "Versión: %@" 182 | } 183 | } 184 | } 185 | } 186 | }, 187 | "version" : "1.0" 188 | } -------------------------------------------------------------------------------- /Netiquette/Monitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Monitor.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/6/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | //wish there was a `NetworkStatistics.h` 14 | // mahalo J. Levin: 15 | // https://twitter.com/Morpheus______ 16 | // http://newosxbook.com/src.jl?tree=listings&file=netbottom.c 17 | 18 | typedef void *NStatSourceRef; 19 | typedef NSObject* NStatManagerRef; 20 | 21 | NStatManagerRef NStatManagerCreate (const struct __CFAllocator *, dispatch_queue_t, void (^)(void *, void *)); 22 | 23 | void NStatSourceSetDescriptionBlock (NStatSourceRef arg, void (^)(NSDictionary*)); 24 | void NStatSourceSetRemovedBlock (NStatSourceRef arg, void (^)(void)); 25 | 26 | void NStatManagerAddAllTCP(NStatManagerRef manager); 27 | void NStatManagerAddAllUDP(NStatManagerRef manager); 28 | 29 | void NStatManagerQueryAllSources(NStatManagerRef manager, void (^)(void) ); 30 | void NStatManagerQueryAllSourcesDescriptions(NStatManagerRef manager, void (^)(void) ); 31 | 32 | void NStatManagerDestroy(NStatManagerRef manager); 33 | int NStatManagerSetFlags(NStatManagerRef, int Flags); 34 | 35 | //block for library 36 | typedef void (^NetworkCallbackBlock)(NSMutableDictionary* _Nonnull); 37 | 38 | @interface Monitor : NSObject 39 | 40 | /* PROPERTIES */ 41 | 42 | @property (nullable) dispatch_queue_t queue; 43 | @property (nullable) dispatch_source_t timer; 44 | @property (nullable) NStatManagerRef manager; 45 | @property (nonatomic, retain)NSCache* processCache; 46 | @property (nonatomic, retain)NSMutableDictionary* events; 47 | 48 | 49 | /* METHODS */ 50 | 51 | //start (network) monitoring 52 | -(void)start:(NSUInteger)refreshRate callback:(NetworkCallbackBlock)callback; 53 | 54 | //stop 55 | -(void)stop; 56 | 57 | //deinit 58 | -(void)deinit; 59 | 60 | @end 61 | 62 | NS_ASSUME_NONNULL_END 63 | -------------------------------------------------------------------------------- /Netiquette/Monitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // Monitor.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/6/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "Event.h" 10 | #import "Monitor.h" 11 | 12 | @implementation Monitor 13 | 14 | @synthesize queue; 15 | @synthesize timer; 16 | @synthesize events; 17 | @synthesize manager; 18 | @synthesize processCache; 19 | 20 | -(id)init 21 | { 22 | //super 23 | self = [super init]; 24 | if(self != nil) 25 | { 26 | //init queue 27 | self.queue = dispatch_queue_create("netiquette", NULL); 28 | 29 | //init timer 30 | self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); 31 | 32 | //init dictionary for events 33 | self.events = [NSMutableDictionary dictionary]; 34 | 35 | //init cache 36 | processCache = [[NSCache alloc] init]; 37 | 38 | //set cache limit 39 | self.processCache.countLimit = 2048; 40 | 41 | //create manager 42 | self.manager = NStatManagerCreate(kCFAllocatorDefault, self.queue, 43 | ^(NStatSourceRef source, void *unknown) 44 | { 45 | //set description block 46 | NStatSourceSetDescriptionBlock(source, ^(NSDictionary* description) 47 | { 48 | //event (conenction) 49 | Event* event = nil; 50 | 51 | //event (UUID) 52 | NSString* uuid = nil; 53 | 54 | //cached process 55 | Process* process = nil; 56 | 57 | //extract uuid 58 | uuid = description[kNStatSrcKeyUUID]; 59 | if(0 != uuid.length) 60 | { 61 | //try grab cached process 62 | process = [self.processCache objectForKey:uuid]; 63 | } 64 | 65 | //init event 66 | event = [[Event alloc] init:description process:process]; 67 | 68 | //ignore pid 0 69 | if(0 == event.process.pid) 70 | { 71 | //igore 72 | return; 73 | } 74 | 75 | //update process cache 76 | if(nil != uuid) 77 | { 78 | //update 79 | [self.processCache setObject:event.process forKey:uuid]; 80 | } 81 | 82 | //sync 83 | @synchronized(self.events) { 84 | 85 | //update 86 | self.events[[NSValue valueWithPointer:source]] = event; 87 | } 88 | 89 | }); 90 | 91 | //set removed block 92 | NStatSourceSetRemovedBlock(source, ^() 93 | { 94 | //sync 95 | @synchronized(self) { 96 | 97 | //remove 98 | self.events[[NSValue valueWithPointer:source]] = nil; 99 | } 100 | 101 | }); 102 | 103 | }); 104 | 105 | NStatManagerSetFlags(self.manager, 0); 106 | } 107 | 108 | return self; 109 | } 110 | 111 | //start (network) monitoring 112 | -(void)start:(NSUInteger)refreshRate callback:(NetworkCallbackBlock)callback 113 | { 114 | //watch UDP 115 | NStatManagerAddAllUDP(self.manager); 116 | 117 | //watch TCP 118 | NStatManagerAddAllTCP(self.manager); 119 | 120 | //query block 121 | // sync and call user-specified call back 122 | void (^queryCallback)(void) = ^(void) { 123 | 124 | //sync 125 | @synchronized(self.events) 126 | { 127 | //invoke user callback 128 | // pass copy to prevent access issues 129 | callback([self.events mutableCopy]); 130 | } 131 | }; 132 | 133 | //query all to start 134 | NStatManagerQueryAllSourcesDescriptions(manager, queryCallback); 135 | 136 | //query all to start 137 | NStatManagerQueryAllSources(manager, queryCallback); 138 | 139 | //refresh? 140 | // call in timer/loop 141 | if(0 != refreshRate) 142 | { 143 | //set timer 144 | dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, refreshRate * NSEC_PER_SEC, 0); 145 | 146 | //set timer event handler 147 | dispatch_source_set_event_handler(self.timer, ^{ 148 | 149 | //(re) query for connections 150 | NStatManagerQueryAllSourcesDescriptions(self.manager, queryCallback); 151 | 152 | //(re) all to start 153 | NStatManagerQueryAllSources(self.manager, queryCallback); 154 | 155 | }); 156 | 157 | //go! 158 | dispatch_resume(timer); 159 | } 160 | 161 | return; 162 | } 163 | 164 | //stop 165 | -(void)stop 166 | { 167 | //suspender timer 168 | dispatch_suspend(self.timer); 169 | 170 | return; 171 | } 172 | 173 | //deinit 174 | // stop q 175 | // destroy manager 176 | -(void)deinit 177 | { 178 | //stop queueu 179 | if(nil != self.queue) 180 | { 181 | //stop 182 | dispatch_suspend(self.queue); 183 | 184 | //unset 185 | self.queue = nil; 186 | } 187 | 188 | //destroy monitor 189 | if(nil != self.manager) 190 | { 191 | //destroy 192 | NStatManagerDestroy(self.manager); 193 | 194 | //unset 195 | self.manager = nil; 196 | } 197 | 198 | return; 199 | } 200 | 201 | @end 202 | -------------------------------------------------------------------------------- /Netiquette/NSApplicationKeyEvents.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSApplicationKeyEvents.h 3 | // ReiKey 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | @interface NSApplicationKeyEvents : NSApplication 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Netiquette/NSApplicationKeyEvents.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSApplicationKeyEvents.h 3 | // ReiKey 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "NSApplicationKeyEvents.h" 11 | 12 | @implementation NSApplicationKeyEvents 13 | 14 | //to enable copy/paste etc even though we don't have an 'Edit' menu 15 | // details: http://stackoverflow.com/questions/970707/cocoa-keyboard-shortcuts-in-dialog-without-an-edit-menu 16 | -(void)sendEvent:(NSEvent *)event 17 | { 18 | //only care about key down + command 19 | if( (NSEventTypeKeyDown != event.type) || 20 | (NSEventModifierFlagCommand != (event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask)) ) 21 | { 22 | //bail 23 | goto bail; 24 | } 25 | 26 | //+c (copy) 27 | if(YES == [[event charactersIgnoringModifiers] isEqualToString:@"c"]) 28 | { 29 | //copy 30 | if(YES == [self sendAction:@selector(copy:) to:nil from:self]) 31 | { 32 | return; 33 | } 34 | } 35 | 36 | //+v (paste) 37 | else if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) 38 | { 39 | //paste 40 | if(YES == [self sendAction:@selector(paste:) to:nil from:self]) 41 | { 42 | return; 43 | } 44 | } 45 | 46 | //+x (cut) 47 | else if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) 48 | { 49 | //cut 50 | if(YES == [self sendAction:@selector(cut:) to:nil from:self]) 51 | { 52 | return; 53 | } 54 | } 55 | 56 | //+a (select all) 57 | else if([[event charactersIgnoringModifiers] isEqualToString:@"a"]) 58 | { 59 | //select 60 | if(YES == [self sendAction:@selector(selectAll:) to:nil from:self]) 61 | { 62 | return; 63 | } 64 | } 65 | 66 | //+h (hide window) 67 | else if([[event charactersIgnoringModifiers] isEqualToString:@"h"]) 68 | { 69 | //hide 70 | if(YES == [self sendAction:@selector(hide:) to:nil from:self]) 71 | { 72 | return; 73 | } 74 | } 75 | 76 | //+m (minimize window) 77 | else if([[event charactersIgnoringModifiers] isEqualToString:@"m"]) 78 | { 79 | //minimize 80 | [NSApplication.sharedApplication.keyWindow miniaturize:nil]; 81 | return; 82 | } 83 | 84 | //+w (close window) 85 | else if([[event charactersIgnoringModifiers] isEqualToString:@"w"]) 86 | { 87 | //close 88 | [NSApplication.sharedApplication.keyWindow close]; 89 | return; 90 | } 91 | 92 | //+f (find, but only in rules window) 93 | else if([[event charactersIgnoringModifiers] isEqualToString:@"f"]) 94 | { 95 | //iterate over all toolbar items 96 | // ...find rule search field, and select 97 | for(NSToolbarItem* item in NSApplication.sharedApplication.keyWindow.toolbar.items) 98 | { 99 | //not search field? skip 100 | if(0x1 != item.tag) continue; 101 | 102 | //and make it first responder 103 | [NSApplication.sharedApplication.keyWindow makeFirstResponder:item.view]; 104 | 105 | //done 106 | return; 107 | } 108 | } 109 | 110 | //+ (zoom in) 111 | if(YES == [[event charactersIgnoringModifiers] isEqualToString:@"="]) 112 | { 113 | //zoom 114 | [((AppDelegate*)NSApplication.sharedApplication.delegate).tableViewController zoomIn]; 115 | 116 | return; 117 | } 118 | 119 | //- (zoom out) 120 | if(YES == [[event charactersIgnoringModifiers] isEqualToString:@"-"]) 121 | { 122 | //zoom 123 | [((AppDelegate*)NSApplication.sharedApplication.delegate).tableViewController zoomOut]; 124 | 125 | return; 126 | } 127 | 128 | //0 (zoom reset) 129 | if(YES == [[event charactersIgnoringModifiers] isEqualToString:@"0"]) 130 | { 131 | //zoom 132 | [((AppDelegate*)NSApplication.sharedApplication.delegate).tableViewController zoomReset]; 133 | 134 | return; 135 | } 136 | 137 | bail: 138 | 139 | //super 140 | [super sendEvent:event]; 141 | 142 | return; 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /Netiquette/Netiquette.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Netiquette/PrefsWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // file: PrefsWindowController.h 3 | // project: OverSight (main app) 4 | // description: preferences window controller (header) 5 | // 6 | // created by Patrick Wardle 7 | // copyright (c) 2017 Objective-See. All rights reserved. 8 | // 9 | 10 | @import Cocoa; 11 | 12 | #import "UpdateWindowController.h" 13 | 14 | /* CONSTS */ 15 | 16 | //modes view 17 | #define TOOLBAR_GENERAL 0 18 | 19 | //update view 20 | #define TOOLBAR_UPDATE 1 21 | 22 | //to select, need string ID 23 | #define TOOLBAR_GENERAL_ID @"General" 24 | 25 | @interface PrefsWindowController : NSWindowController 26 | 27 | /* PROPERTIES */ 28 | 29 | //preferences 30 | @property(nonatomic, retain)NSDictionary* preferences; 31 | 32 | //toolbar 33 | @property (weak) IBOutlet NSToolbar *toolbar; 34 | 35 | //general prefs view 36 | @property (weak) IBOutlet NSView *generalView; 37 | 38 | //update view 39 | @property (weak) IBOutlet NSView *updateView; 40 | 41 | //update button 42 | @property (weak) IBOutlet NSButton *updateButton; 43 | 44 | //update indicator (spinner) 45 | @property (weak) IBOutlet NSProgressIndicator *updateIndicator; 46 | 47 | //update label 48 | @property (weak) IBOutlet NSTextField *updateLabel; 49 | 50 | //update window controller 51 | @property(nonatomic, retain)UpdateWindowController* updateWindowController; 52 | 53 | /* METHODS */ 54 | 55 | //toolbar button handler 56 | -(IBAction)toolbarButtonHandler:(id)sender; 57 | 58 | //button handler for all preference buttons 59 | -(IBAction)togglePreference:(id)sender; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Netiquette/PrefsWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // file: PrefsWindowController.h 3 | // project: Netiquette 4 | // description: preferences window controller (header) 5 | // 6 | // created by Patrick Wardle 7 | // copyright (c) 2020 Objective-See. All rights reserved. 8 | // 9 | 10 | #import "consts.h" 11 | #import "Update.h" 12 | #import "utilities.h" 13 | #import "AppDelegate.h" 14 | #import "PrefsWindowController.h" 15 | #import "UpdateWindowController.h" 16 | 17 | /* GLOBALS */ 18 | 19 | @implementation PrefsWindowController 20 | 21 | @synthesize toolbar; 22 | @synthesize generalView; 23 | @synthesize updateView; 24 | @synthesize updateWindowController; 25 | 26 | //pref button IDS 27 | #define BUTTON_AUTO_REFRESH 1 28 | #define BUTTON_RESOLVE_NAMES 2 29 | #define BUTTON_HIDE_APPLE 3 30 | #define BUTTON_HIDE_LOCAL 4 31 | #define BUTTON_NO_UPDATE 5 32 | 33 | //init 'general' view 34 | // add it, and make it selected 35 | -(void)awakeFromNib 36 | { 37 | //(un)set button handler 38 | [self toolbarButtonHandler:nil]; 39 | 40 | //set general prefs as default 41 | [self.toolbar setSelectedItemIdentifier:TOOLBAR_GENERAL_ID]; 42 | 43 | return; 44 | } 45 | 46 | //toolbar view handler 47 | // toggle view based on user selection 48 | -(IBAction)toolbarButtonHandler:(id)sender 49 | { 50 | //view 51 | NSView* view = nil; 52 | 53 | //when we've prev added a view 54 | // remove the prev view cuz adding a new one 55 | if(nil != sender) 56 | { 57 | //remove 58 | [[[self.window.contentView subviews] lastObject] removeFromSuperview]; 59 | } 60 | 61 | //assign view 62 | switch(((NSToolbarItem*)sender).tag) 63 | { 64 | //modes 65 | case TOOLBAR_GENERAL: 66 | { 67 | //set view 68 | view = self.generalView; 69 | 70 | //auto refresh 71 | ((NSButton*)[view viewWithTag:BUTTON_AUTO_REFRESH]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREFS_AUTO_REFRESH]; 72 | 73 | //resolve names 74 | ((NSButton*)[view viewWithTag:BUTTON_RESOLVE_NAMES]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREFS_RESOLVE_NAMES]; 75 | 76 | //hide apple 77 | ((NSButton*)[view viewWithTag:BUTTON_HIDE_APPLE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREFS_HIDE_APPLE]; 78 | 79 | //hide local 80 | ((NSButton*)[view viewWithTag:BUTTON_HIDE_LOCAL]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREFS_HIDE_LOCAL]; 81 | 82 | break; 83 | } 84 | 85 | //update 86 | case TOOLBAR_UPDATE: 87 | { 88 | //set view 89 | view = self.updateView; 90 | 91 | //set 'update' button state 92 | ((NSButton*)[view viewWithTag:BUTTON_NO_UPDATE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREFS_NO_UPDATE]; 93 | 94 | //set button as first responder 95 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (100 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ 96 | 97 | //make first responder 98 | [self.window makeFirstResponder:self.updateButton]; 99 | }); 100 | 101 | break; 102 | } 103 | 104 | default: 105 | 106 | //bail 107 | goto bail; 108 | } 109 | 110 | //set frame rect 111 | view.frame = CGRectMake(0, 75, self.window.contentView.frame.size.width, self.window.contentView.frame.size.height-75); 112 | 113 | //add to window 114 | [self.window.contentView addSubview:view]; 115 | 116 | bail: 117 | 118 | return; 119 | } 120 | 121 | 122 | //invoked when user toggles button 123 | // update preferences for that button 124 | -(IBAction)togglePreference:(id)sender 125 | { 126 | //preferences 127 | NSMutableDictionary* updatedPreferences = nil; 128 | 129 | //button state 130 | BOOL state = NO; 131 | 132 | //init 133 | updatedPreferences = [NSMutableDictionary dictionary]; 134 | 135 | //get button state 136 | state = ((NSButton*)sender).state; 137 | 138 | //set appropriate preference 139 | switch(((NSButton*)sender).tag) 140 | { 141 | //auto-refresh 142 | case BUTTON_AUTO_REFRESH: 143 | { 144 | //set 145 | [NSUserDefaults.standardUserDefaults setBool:state forKey:PREFS_AUTO_REFRESH]; 146 | 147 | break; 148 | } 149 | 150 | //resolve names 151 | case BUTTON_RESOLVE_NAMES: 152 | { 153 | //set 154 | [NSUserDefaults.standardUserDefaults setBool:state forKey:PREFS_RESOLVE_NAMES]; 155 | 156 | break; 157 | } 158 | 159 | //hide apple procs 160 | case BUTTON_HIDE_APPLE: 161 | { 162 | //set 163 | [NSUserDefaults.standardUserDefaults setBool:state forKey:PREFS_HIDE_APPLE]; 164 | break; 165 | } 166 | 167 | //hide localhost connections 168 | case BUTTON_HIDE_LOCAL: 169 | { 170 | //set 171 | [NSUserDefaults.standardUserDefaults setBool:state forKey:PREFS_HIDE_LOCAL]; 172 | break; 173 | } 174 | 175 | //no update mode 176 | case BUTTON_NO_UPDATE: 177 | { 178 | //set 179 | [NSUserDefaults.standardUserDefaults setBool:state forKey:PREFS_NO_UPDATE]; 180 | break; 181 | } 182 | 183 | default: 184 | break; 185 | } 186 | 187 | //sync 188 | [NSUserDefaults.standardUserDefaults synchronize]; 189 | 190 | return; 191 | } 192 | 193 | //'check for update' button handler 194 | -(IBAction)check4Update:(id)sender 195 | { 196 | //update obj 197 | Update* update = nil; 198 | 199 | //disable button 200 | self.updateButton.enabled = NO; 201 | 202 | //reset 203 | self.updateLabel.stringValue = @""; 204 | 205 | //show/start spinner 206 | [self.updateIndicator startAnimation:self]; 207 | 208 | //init update obj 209 | update = [[Update alloc] init]; 210 | 211 | //check 212 | // but after a delay for UI 213 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), 214 | ^{ 215 | 216 | //check for update 217 | [update checkForUpdate:^(NSUInteger result, NSString* newVersion) { 218 | 219 | //process response 220 | [self updateResponse:result newVersion:newVersion]; 221 | 222 | }]; 223 | 224 | }); 225 | 226 | return; 227 | } 228 | 229 | //process update response 230 | // error, no update, update/new version 231 | -(void)updateResponse:(NSInteger)result newVersion:(NSString*)newVersion 232 | { 233 | //re-enable button 234 | self.updateButton.enabled = YES; 235 | 236 | //stop/hide spinner 237 | [self.updateIndicator stopAnimation:self]; 238 | 239 | switch(result) 240 | { 241 | //error 242 | case UPDATE_ERROR: 243 | 244 | //set label 245 | self.updateLabel.stringValue = NSLocalizedString(@"error: update check failed",@"error: update check failed"); 246 | 247 | break; 248 | 249 | //no updates 250 | case UPDATE_NOTHING_NEW: 251 | 252 | //set label 253 | self.updateLabel.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Installed version (%@) is the latest.", @"Installed version (%@) is the latest."), getAppVersion()]; 254 | 255 | break; 256 | 257 | 258 | //new version 259 | case UPDATE_NEW_VERSION: 260 | 261 | //alloc update window 262 | updateWindowController = [[UpdateWindowController alloc] initWithWindowNibName:@"UpdateWindow"]; 263 | 264 | //configure 265 | [self.updateWindowController configure:[NSString stringWithFormat:NSLocalizedString(@"a new version (%@) is available!",@"a new version (%@) is available!"), newVersion] buttonTitle:NSLocalizedString(@"Update", @"Update")]; 266 | 267 | //center window 268 | [[self.updateWindowController window] center]; 269 | 270 | //show it 271 | [self.updateWindowController showWindow:self]; 272 | 273 | //invoke function in background that will make window modal 274 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 275 | 276 | //make modal 277 | makeModal(self.updateWindowController); 278 | 279 | }); 280 | 281 | break; 282 | } 283 | 284 | return; 285 | } 286 | 287 | //on window close 288 | // refresh UI (outline view) 289 | -(void)windowWillClose:(NSNotification *)notification 290 | { 291 | //refresh 292 | [((AppDelegate*)NSApplication.sharedApplication.delegate).tableViewController refresh]; 293 | 294 | return; 295 | } 296 | 297 | 298 | @end 299 | -------------------------------------------------------------------------------- /Netiquette/TableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/20/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "3rd-party/OrderedDictionary.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface TableViewController : NSOutlineView 16 | 17 | //scroll view 18 | @property (weak) IBOutlet NSScrollView *scrollView; 19 | 20 | @property (weak) IBOutlet NSView *overlay; 21 | 22 | //outline view 23 | @property (weak) IBOutlet NSOutlineView *outlineView; 24 | 25 | //overlay spinner 26 | @property (weak) IBOutlet NSProgressIndicator *activityIndicator; 27 | 28 | //overlay msg 29 | @property (weak) IBOutlet NSTextField *activityMessage; 30 | 31 | //original connections 32 | @property(nonatomic, retain)OrderedDictionary* items; 33 | 34 | //filtered connections 35 | @property(nonatomic, retain)OrderedDictionary* processedItems; 36 | 37 | //filter box 38 | @property (weak) IBOutlet NSSearchField *filterBox; 39 | 40 | //collapsed items 41 | @property (nonatomic, retain)NSMutableDictionary* collapsedItems; 42 | 43 | //font size 44 | @property CGFloat zoomScale; 45 | 46 | //filter string 47 | @property (nonatomic, retain)NSString* filterString; 48 | 49 | /* METHODS */ 50 | 51 | //update table 52 | -(void)update:(OrderedDictionary*)updatedItems expand:(BOOL)expand reset:(BOOL)reset; 53 | 54 | //map column id to index 55 | -(NSUInteger)columnIDToIndex:(NSString*)columnID; 56 | 57 | //refresh/reload table 58 | -(void)refresh; 59 | 60 | //expand all 61 | -(void)expandAll; 62 | 63 | //collapse all 64 | -(void)collapseAll; 65 | 66 | //zoom in 67 | -(void)zoomIn; 68 | 69 | //zoom out 70 | -(void)zoomOut; 71 | 72 | //zoom reset 73 | -(void)zoomReset; 74 | 75 | @end 76 | 77 | NS_ASSUME_NONNULL_END 78 | -------------------------------------------------------------------------------- /Netiquette/Update.h: -------------------------------------------------------------------------------- 1 | // 2 | // Update.h 3 | // ReiKey 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | #ifndef Update_h 10 | #define Update_h 11 | 12 | @import Cocoa; 13 | @import Foundation; 14 | 15 | @interface Update : NSObject 16 | 17 | //check for an update 18 | // will invoke app delegate method to update UI when check completes 19 | -(void)checkForUpdate:(void (^)(NSUInteger result, NSString* latestVersion))completionHandler; 20 | 21 | @end 22 | 23 | #endif /* Update_h */ 24 | -------------------------------------------------------------------------------- /Netiquette/Update.m: -------------------------------------------------------------------------------- 1 | // 2 | // Update.m 3 | // ReiKey 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "consts.h" 10 | #import "Update.h" 11 | #import "utilities.h" 12 | #import "AppDelegate.h" 13 | 14 | @implementation Update 15 | 16 | //check for an update 17 | // will invoke app delegate method to update UI when check completes 18 | -(void)checkForUpdate:(void (^)(NSUInteger result, NSString* latestVersion))completionHandler 19 | { 20 | //latest version 21 | __block NSString* latestVersion = nil; 22 | 23 | //result 24 | __block NSInteger result = -1; 25 | 26 | //get latest version in background 27 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 28 | 29 | //grab latest version 30 | latestVersion = [self getLatestVersion]; 31 | if(nil != latestVersion) 32 | { 33 | //check 34 | result = (NSOrderedAscending == [getAppVersion() compare:latestVersion options:NSNumericSearch]); 35 | } 36 | 37 | //invoke app delegate method 38 | // ->will update UI/show popup if necessart 39 | dispatch_async(dispatch_get_main_queue(), 40 | ^{ 41 | completionHandler(result, latestVersion); 42 | }); 43 | 44 | }); 45 | 46 | return; 47 | } 48 | 49 | //query interwebz to get latest version 50 | -(NSString*)getLatestVersion 51 | { 52 | //product version(s) data 53 | NSData* productsVersionData = nil; 54 | 55 | //version dictionary 56 | NSDictionary* productsVersionDictionary = nil; 57 | 58 | //latest version 59 | NSString* latestVersion = nil; 60 | 61 | //get version from remote URL 62 | productsVersionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSIONS_URL]]; 63 | if(nil == productsVersionData) 64 | { 65 | //bail 66 | goto bail; 67 | } 68 | 69 | //convert JSON to dictionary 70 | // ->wrap as may throw exception 71 | @try 72 | { 73 | //convert 74 | productsVersionDictionary = [NSJSONSerialization JSONObjectWithData:productsVersionData options:0 error:nil]; 75 | if(nil == productsVersionDictionary) 76 | { 77 | //bail 78 | goto bail; 79 | } 80 | } 81 | @catch(NSException* exception) 82 | { 83 | //bail 84 | goto bail; 85 | } 86 | 87 | //extract latest version 88 | latestVersion = [[productsVersionDictionary objectForKey:PRODUCT_NAME] objectForKey:@"version"]; 89 | 90 | bail: 91 | 92 | return latestVersion; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /Netiquette/UpdateWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateWindowController.m 3 | // ReiKey 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | @interface UpdateWindowController : NSWindowController 12 | { 13 | 14 | } 15 | 16 | /* PROPERTIES */ 17 | 18 | //version label/string 19 | @property(weak)IBOutlet NSTextField *infoLabel; 20 | 21 | //action button 22 | @property(weak)IBOutlet NSButton *actionButton; 23 | 24 | //label string 25 | @property(nonatomic, retain)NSString* infoLabelString; 26 | 27 | //button title 28 | @property(nonatomic, retain)NSString* actionButtonTitle; 29 | 30 | //overlay view 31 | @property(weak)IBOutlet NSView *overlayView; 32 | 33 | //spinner 34 | @property(weak)IBOutlet NSProgressIndicator *progressIndicator; 35 | 36 | /* METHODS */ 37 | 38 | //save the main label's & button title's text 39 | -(void)configure:(NSString*)label buttonTitle:(NSString*)buttonTitle; 40 | 41 | //invoked when user clicks button 42 | // ->trigger action such as opening product website, updating, etc 43 | -(IBAction)buttonHandler:(id)sender; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Netiquette/UpdateWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateWindowController.m 3 | // ReiKey 4 | // 5 | // Created by Patrick Wardle on 12/24/18. 6 | // Copyright © 2018 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "consts.h" 10 | #import "utilities.h" 11 | #import "AppDelegate.h" 12 | #import "UpdateWindowController.h" 13 | 14 | @implementation UpdateWindowController 15 | 16 | @synthesize infoLabel; 17 | @synthesize overlayView; 18 | @synthesize actionButton; 19 | @synthesize infoLabelString; 20 | @synthesize actionButtonTitle; 21 | @synthesize progressIndicator; 22 | 23 | //automatically called when nib is loaded 24 | // ->center window 25 | -(void)awakeFromNib 26 | { 27 | //center 28 | [self.window center]; 29 | 30 | return; 31 | } 32 | 33 | //automatically invoked when window is loaded 34 | -(void)windowDidLoad 35 | { 36 | //super 37 | [super windowDidLoad]; 38 | 39 | //not in dark mode? 40 | // make window white 41 | if(YES != isDarkMode()) 42 | { 43 | //make white 44 | self.window.backgroundColor = NSColor.whiteColor; 45 | } 46 | 47 | //indicated title bar is tranparent (too) 48 | self.window.titlebarAppearsTransparent = YES; 49 | 50 | //set main label 51 | [self.infoLabel setStringValue:self.infoLabelString]; 52 | 53 | //set button text 54 | self.actionButton.title = self.actionButtonTitle; 55 | 56 | //make it key window 57 | [self.window makeKeyAndOrderFront:self]; 58 | 59 | //make window front 60 | [NSApp activateIgnoringOtherApps:YES]; 61 | 62 | //button first responder 63 | [self.window makeFirstResponder:self.actionButton]; 64 | 65 | return; 66 | } 67 | 68 | //automatically invoked when window is closing 69 | // ->make ourselves unmodal 70 | -(void)windowWillClose:(NSNotification *)notification 71 | { 72 | //make un-modal 73 | [[NSApplication sharedApplication] stopModal]; 74 | 75 | return; 76 | } 77 | 78 | //save the main label's & button title's text 79 | // ->invoked before window is loaded (and thus buttons, etc are nil) 80 | -(void)configure:(NSString*)label buttonTitle:(NSString*)buttonTitle 81 | { 82 | //save label's string 83 | self.infoLabelString = label; 84 | 85 | //save button's title 86 | self.actionButtonTitle = buttonTitle; 87 | 88 | return; 89 | } 90 | 91 | //invoked when user clicks button 92 | // trigger action such as opening product website, updating, etc 93 | -(IBAction)buttonHandler:(id)sender 94 | { 95 | //handle 'update' / 'more info', etc 96 | // open product webpage, if they *didn't* click 'Close' 97 | if(YES != [((NSButton*)sender).title isEqualToString:NSLocalizedString(@"Close", @"Close")]) 98 | { 99 | //open URL 100 | // invokes user's default browser 101 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:PRODUCT_URL]]; 102 | } 103 | 104 | //always close window 105 | [[self window] close]; 106 | 107 | return; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /Netiquette/consts.h: -------------------------------------------------------------------------------- 1 | // 2 | // consts.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/14/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #ifndef consts_h 10 | #define consts_h 11 | 12 | #define PROCESS_PID @"pid" 13 | #define PROCESS_PATH @"path" 14 | 15 | #define INTERFACE @"interface" 16 | #define PROTOCOL @"protocol" 17 | 18 | #define LOCAL_PORT @"localPort" 19 | #define LOCAL_ADDRESS @"localAddress" 20 | 21 | #define REMOTE_PORT @"remotePort" 22 | #define REMOTE_HOST @"remoteHostName" 23 | #define REMOTE_ADDRESS @"remoteAddress" 24 | 25 | #define CONNECTION_STATE @"state" 26 | 27 | #define BYTES_UP @"bytesUp" 28 | #define BYTES_DOWN @"bytesDown" 29 | 30 | #define KEY_PORT @"port" 31 | #define KEY_FAMILY @"family" 32 | #define KEY_ADDRRESS @"address" 33 | #define KEY_HOST_NAME @"hostName" 34 | 35 | //product name 36 | // ...for version check 37 | #define PRODUCT_NAME @"Netiquette" 38 | 39 | //product url 40 | #define PRODUCT_URL @"https://objective-see.org/products/netiquette.html" 41 | 42 | //patreon url 43 | #define PATREON_URL @"https://www.patreon.com/bePatron?c=701171" 44 | 45 | //path to CUPS 46 | #define CUPS @"/usr/sbin/cupsd" 47 | 48 | //product version url 49 | #define PRODUCT_VERSIONS_URL @"https://objective-see.org/products.json" 50 | 51 | //update error 52 | #define UPDATE_ERROR -1 53 | 54 | //update no new version 55 | #define UPDATE_NOTHING_NEW 0 56 | 57 | //update new version 58 | #define UPDATE_NEW_VERSION 1 59 | 60 | #define PREFS_HIDE_APPLE @"hideApple" 61 | #define PREFS_AUTO_REFRESH @"autoRefresh" 62 | #define PREFS_RESOLVE_NAMES @"resolveNames" 63 | #define PREFS_HIDE_LOCAL @"hideLocalHost" 64 | #define PREFS_NO_UPDATE @"noUpdateCheck" 65 | 66 | //executed via LuLu 67 | #define ARGS_LULU @"-lulu" 68 | 69 | //view menu 70 | #define VIEW_EXPAND 1 71 | #define VIEW_COLLAPSE 2 72 | #define VIEW_ZOOM_IN 3 73 | #define VIEW_ZOOM_OUT 4 74 | 75 | #endif /* consts_h */ 76 | -------------------------------------------------------------------------------- /Netiquette/images/logoApple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/images/logoApple.png -------------------------------------------------------------------------------- /Netiquette/images/logoAppleBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/images/logoAppleBG.png -------------------------------------------------------------------------------- /Netiquette/images/logoAppleOver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/images/logoAppleOver.png -------------------------------------------------------------------------------- /Netiquette/images/saveIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/images/saveIcon.png -------------------------------------------------------------------------------- /Netiquette/images/saveIconBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/images/saveIconBG.png -------------------------------------------------------------------------------- /Netiquette/images/saveIconOver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/images/saveIconOver.png -------------------------------------------------------------------------------- /Netiquette/main.h: -------------------------------------------------------------------------------- 1 | // 2 | // main.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/14/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #ifndef main_h 10 | #define main_h 11 | 12 | /* FUNCTIONS */ 13 | 14 | //print usage 15 | void usage(void); 16 | 17 | //perform a cmdline scan 18 | void cmdlineInterface(void); 19 | 20 | #endif /* main_h */ 21 | -------------------------------------------------------------------------------- /Netiquette/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/1/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "main.h" 10 | #import "sort.h" 11 | #import "Event.h" 12 | #import "Monitor.h" 13 | #import "utilities.h" 14 | #import "procInfo/procInfo.h" 15 | 16 | #import 17 | 18 | //main 19 | // process cmdline args, show UI, etc 20 | int main(int argc, const char * argv[]) 21 | { 22 | //return var 23 | int status = -1; 24 | 25 | //args 26 | NSArray* arguments = nil; 27 | 28 | //grab args 29 | arguments = NSProcessInfo.processInfo.arguments; 30 | 31 | //disable stderr 32 | // crash reporter dumps info here 33 | disableSTDERR(); 34 | 35 | //handle '-h' or '-help' 36 | if( (YES == [arguments containsObject:@"-h"]) || 37 | (YES == [arguments containsObject:@"-help"]) ) 38 | { 39 | //print usage 40 | usage(); 41 | 42 | //done 43 | goto bail; 44 | } 45 | 46 | //handle '-list' 47 | // cmdline enumeration without UI 48 | else if(YES == [arguments containsObject:@"-list"]) 49 | { 50 | //scan 51 | cmdlineInterface(); 52 | 53 | //happy 54 | status = 0; 55 | 56 | //done 57 | goto bail; 58 | } 59 | 60 | //handle invalid args 61 | // allow -psn_, debug mode, and lulu (launch) 62 | else if( (arguments.count > 1) && 63 | (YES != [arguments[1] hasPrefix:@"-psn_"]) && 64 | (YES != [arguments containsObject:ARGS_LULU]) && 65 | (YES != [arguments containsObject:@"-NSDocumentRevisionsDebugMode"]) ) 66 | { 67 | //print usage 68 | usage(); 69 | 70 | //done 71 | goto bail; 72 | } 73 | 74 | //running non-cmdline mode 75 | // so, make foreground so app has an dock icon, etc 76 | transformApp(kProcessTransformToForegroundApplication); 77 | 78 | //set defaults 79 | [NSUserDefaults.standardUserDefaults registerDefaults:@{PREFS_AUTO_REFRESH:@YES, PREFS_RESOLVE_NAMES:@YES, PREFS_HIDE_APPLE:@YES, PREFS_HIDE_LOCAL:@YES}]; 80 | 81 | //launch app normally 82 | status = NSApplicationMain(argc, argv); 83 | 84 | bail: 85 | 86 | return status; 87 | } 88 | 89 | //print usage 90 | void usage(void) 91 | { 92 | //usage 93 | printf("\nNETIQUETTE USAGE:\n"); 94 | printf(" -h or -help display this usage info\n"); 95 | printf(" -list enumerate all network connections\n"); 96 | printf(" -names resolve remote host names (via DNS)\n"); 97 | printf(" -pretty JSON output is 'pretty-printed' for readability\n"); 98 | printf(" -skipApple ignore connections that belong to Apple processes \n\n"); 99 | 100 | return; 101 | } 102 | 103 | //perform a cmdline scan 104 | void cmdlineInterface(void) 105 | { 106 | //monitor 107 | Monitor* monitor = nil; 108 | 109 | //events 110 | __block OrderedDictionary* connections = nil; 111 | 112 | //output 113 | NSMutableString* output = nil; 114 | 115 | //wait semaphore 116 | dispatch_semaphore_t semaphore = 0; 117 | 118 | //init monitor 119 | monitor = [[Monitor alloc] init]; 120 | 121 | //init wait semaphore 122 | semaphore = dispatch_semaphore_create(0); 123 | 124 | //start 125 | // once... 126 | [monitor start:0 callback:^(NSMutableDictionary* events) { 127 | 128 | //combine/sort 129 | connections = sortEvents(combineEvents(events), 0, YES); 130 | 131 | //trigger wait semaphore 132 | dispatch_semaphore_signal(semaphore); 133 | 134 | }]; 135 | 136 | //wait for request to complete 137 | dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 138 | 139 | //stop monitor 140 | [monitor stop]; 141 | 142 | //cleanup 143 | [monitor deinit]; 144 | 145 | //format results 146 | // convert to JSON 147 | output = formatResults(connections, [[[NSProcessInfo processInfo] arguments] containsObject:@"-skipApple"]); 148 | 149 | //pretty print? 150 | if(YES == [[[NSProcessInfo processInfo] arguments] containsObject:@"-pretty"]) 151 | { 152 | //make me pretty! 153 | printf("%s\n", prettifyJSON(output).UTF8String); 154 | } 155 | else 156 | { 157 | //output 158 | printf("%s\n", output.UTF8String); 159 | } 160 | 161 | return; 162 | } 163 | -------------------------------------------------------------------------------- /Netiquette/mul.lproj/AboutWindow.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "6g3-Pg-x3P.title" : { 5 | "comment" : "Class = \"NSButtonCell\"; title = \"Support Us!\"; ObjectID = \"6g3-Pg-x3P\";", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "en" : { 9 | "stringUnit" : { 10 | "state" : "new", 11 | "value" : "Support Us!" 12 | } 13 | }, 14 | "es" : { 15 | "stringUnit" : { 16 | "state" : "translated", 17 | "value" : "¡Apóyanos!" 18 | } 19 | } 20 | } 21 | }, 22 | "bBK-v0-ypq.title" : { 23 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Version:\"; ObjectID = \"bBK-v0-ypq\";", 24 | "extractionState" : "extracted_with_value", 25 | "localizations" : { 26 | "en" : { 27 | "stringUnit" : { 28 | "state" : "new", 29 | "value" : "Version:" 30 | } 31 | }, 32 | "es" : { 33 | "stringUnit" : { 34 | "state" : "translated", 35 | "value" : "Versión:" 36 | } 37 | } 38 | } 39 | }, 40 | "fJg-qw-wDf.title" : { 41 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Patrons & Friends\"; ObjectID = \"fJg-qw-wDf\";", 42 | "extractionState" : "extracted_with_value", 43 | "localizations" : { 44 | "en" : { 45 | "stringUnit" : { 46 | "state" : "new", 47 | "value" : "Patrons & Friends" 48 | } 49 | }, 50 | "es" : { 51 | "stringUnit" : { 52 | "state" : "translated", 53 | "value" : "Patrons & Friends" 54 | } 55 | } 56 | } 57 | }, 58 | "J9x-sM-h9S.title" : { 59 | "comment" : "Class = \"NSButtonCell\"; title = \"More Information\"; ObjectID = \"J9x-sM-h9S\";", 60 | "extractionState" : "extracted_with_value", 61 | "localizations" : { 62 | "en" : { 63 | "stringUnit" : { 64 | "state" : "new", 65 | "value" : "More Information" 66 | } 67 | }, 68 | "es" : { 69 | "stringUnit" : { 70 | "state" : "translated", 71 | "value" : "Más Información" 72 | } 73 | } 74 | } 75 | } 76 | }, 77 | "version" : "1.0" 78 | } -------------------------------------------------------------------------------- /Netiquette/mul.lproj/Preferences.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "2qr-2x-A3U.title" : { 5 | "comment" : "Class = \"NSButtonCell\"; title = \"Check Now\"; ObjectID = \"2qr-2x-A3U\";", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "en" : { 9 | "stringUnit" : { 10 | "state" : "new", 11 | "value" : "Check Now" 12 | } 13 | }, 14 | "es" : { 15 | "stringUnit" : { 16 | "state" : "translated", 17 | "value" : "Verificar Ahora" 18 | } 19 | } 20 | } 21 | }, 22 | "4EN-j2-enV.title" : { 23 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Resolve Names\"; ObjectID = \"4EN-j2-enV\";", 24 | "extractionState" : "extracted_with_value", 25 | "localizations" : { 26 | "en" : { 27 | "stringUnit" : { 28 | "state" : "new", 29 | "value" : "Resolve Names" 30 | } 31 | }, 32 | "es" : { 33 | "stringUnit" : { 34 | "state" : "translated", 35 | "value" : "Resolver Nombres" 36 | } 37 | } 38 | } 39 | }, 40 | "a5n-tt-65v.title" : { 41 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Hide Apple Processes\"; ObjectID = \"a5n-tt-65v\";", 42 | "extractionState" : "extracted_with_value", 43 | "localizations" : { 44 | "en" : { 45 | "stringUnit" : { 46 | "state" : "new", 47 | "value" : "Hide Apple Processes" 48 | } 49 | }, 50 | "es" : { 51 | "stringUnit" : { 52 | "state" : "translated", 53 | "value" : "Ocultar Procesos de Apple" 54 | } 55 | } 56 | } 57 | }, 58 | "E0T-Ug-P8f.title" : { 59 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Automatically reenumerate and redisplay network connections.\"; ObjectID = \"E0T-Ug-P8f\";", 60 | "extractionState" : "extracted_with_value", 61 | "localizations" : { 62 | "en" : { 63 | "stringUnit" : { 64 | "state" : "new", 65 | "value" : "Automatically reenumerate and redisplay network connections." 66 | } 67 | }, 68 | "es" : { 69 | "stringUnit" : { 70 | "state" : "translated", 71 | "value" : "Enumerar y mostrar de nuevo automáticamente las conexiones de red." 72 | } 73 | } 74 | } 75 | }, 76 | "F0z-JX-Cv5.title" : { 77 | "comment" : "Class = \"NSWindow\"; title = \"Settings\"; ObjectID = \"F0z-JX-Cv5\";", 78 | "extractionState" : "extracted_with_value", 79 | "localizations" : { 80 | "en" : { 81 | "stringUnit" : { 82 | "state" : "new", 83 | "value" : "Settings" 84 | } 85 | }, 86 | "es" : { 87 | "stringUnit" : { 88 | "state" : "translated", 89 | "value" : "Configuratión" 90 | } 91 | } 92 | } 93 | }, 94 | "JV5-cY-ff4.title" : { 95 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Do not automatically check for new versions.\"; ObjectID = \"JV5-cY-ff4\";", 96 | "extractionState" : "extracted_with_value", 97 | "localizations" : { 98 | "en" : { 99 | "stringUnit" : { 100 | "state" : "new", 101 | "value" : "Do not automatically check for new versions." 102 | } 103 | }, 104 | "es" : { 105 | "stringUnit" : { 106 | "state" : "translated", 107 | "value" : "No comprobar automáticamente si hay nuevas versiones." 108 | } 109 | } 110 | } 111 | }, 112 | "JVI-Or-h0u.title" : { 113 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Auto Refresh\"; ObjectID = \"JVI-Or-h0u\";", 114 | "extractionState" : "extracted_with_value", 115 | "localizations" : { 116 | "en" : { 117 | "stringUnit" : { 118 | "state" : "new", 119 | "value" : "Auto Refresh" 120 | } 121 | }, 122 | "es" : { 123 | "stringUnit" : { 124 | "state" : "translated", 125 | "value" : "Actualización Automática" 126 | } 127 | } 128 | } 129 | }, 130 | "k0w-Oi-kwd.label" : { 131 | "comment" : "Class = \"NSToolbarItem\"; label = \"General\"; ObjectID = \"k0w-Oi-kwd\";", 132 | "extractionState" : "extracted_with_value", 133 | "localizations" : { 134 | "en" : { 135 | "stringUnit" : { 136 | "state" : "new", 137 | "value" : "General" 138 | } 139 | }, 140 | "es" : { 141 | "stringUnit" : { 142 | "state" : "translated", 143 | "value" : "General" 144 | } 145 | } 146 | } 147 | }, 148 | "k0w-Oi-kwd.paletteLabel" : { 149 | "comment" : "Class = \"NSToolbarItem\"; paletteLabel = \"General\"; ObjectID = \"k0w-Oi-kwd\";", 150 | "extractionState" : "extracted_with_value", 151 | "localizations" : { 152 | "en" : { 153 | "stringUnit" : { 154 | "state" : "new", 155 | "value" : "General" 156 | } 157 | }, 158 | "es" : { 159 | "stringUnit" : { 160 | "state" : "translated", 161 | "value" : "General" 162 | } 163 | } 164 | } 165 | }, 166 | "NVd-tb-ysH.title" : { 167 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Hide Local Connections\"; ObjectID = \"NVd-tb-ysH\";", 168 | "extractionState" : "extracted_with_value", 169 | "localizations" : { 170 | "en" : { 171 | "stringUnit" : { 172 | "state" : "new", 173 | "value" : "Hide Local Connections" 174 | } 175 | }, 176 | "es" : { 177 | "stringUnit" : { 178 | "state" : "translated", 179 | "value" : "Ocultar Conexiones Locales" 180 | } 181 | } 182 | } 183 | }, 184 | "rrF-xj-cXp.label" : { 185 | "comment" : "Class = \"NSToolbarItem\"; label = \"Update\"; ObjectID = \"rrF-xj-cXp\";", 186 | "extractionState" : "extracted_with_value", 187 | "localizations" : { 188 | "en" : { 189 | "stringUnit" : { 190 | "state" : "new", 191 | "value" : "Update" 192 | } 193 | }, 194 | "es" : { 195 | "stringUnit" : { 196 | "state" : "translated", 197 | "value" : "Actualizar" 198 | } 199 | } 200 | } 201 | }, 202 | "rrF-xj-cXp.paletteLabel" : { 203 | "comment" : "Class = \"NSToolbarItem\"; paletteLabel = \"Update\"; ObjectID = \"rrF-xj-cXp\";", 204 | "extractionState" : "extracted_with_value", 205 | "localizations" : { 206 | "en" : { 207 | "stringUnit" : { 208 | "state" : "new", 209 | "value" : "Update" 210 | } 211 | }, 212 | "es" : { 213 | "stringUnit" : { 214 | "state" : "translated", 215 | "value" : "Actualizar" 216 | } 217 | } 218 | } 219 | }, 220 | "SG5-YM-BoV.title" : { 221 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Resolve (domain) names via reverse lookup.\"; ObjectID = \"SG5-YM-BoV\";", 222 | "extractionState" : "extracted_with_value", 223 | "localizations" : { 224 | "en" : { 225 | "stringUnit" : { 226 | "state" : "new", 227 | "value" : "Resolve (domain) names via reverse lookup." 228 | } 229 | }, 230 | "es" : { 231 | "stringUnit" : { 232 | "state" : "translated", 233 | "value" : "Resolver nombres de (dominio) mediante búsqueda inversa." 234 | } 235 | } 236 | } 237 | }, 238 | "SYJ-Qv-cRq.title" : { 239 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Do not display network activity from to system binaries.\"; ObjectID = \"SYJ-Qv-cRq\";", 240 | "extractionState" : "extracted_with_value", 241 | "localizations" : { 242 | "en" : { 243 | "stringUnit" : { 244 | "state" : "new", 245 | "value" : "Do not display network activity from to system binaries." 246 | } 247 | }, 248 | "es" : { 249 | "stringUnit" : { 250 | "state" : "translated", 251 | "value" : "No mostrar la actividad de red de los binarios del sistema." 252 | } 253 | } 254 | } 255 | }, 256 | "WTz-7B-Wrc.title" : { 257 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Do not display network activity to local host (lo*).\"; ObjectID = \"WTz-7B-Wrc\";", 258 | "extractionState" : "extracted_with_value", 259 | "localizations" : { 260 | "en" : { 261 | "stringUnit" : { 262 | "state" : "new", 263 | "value" : "Do not display network activity to local host (lo*)." 264 | } 265 | }, 266 | "es" : { 267 | "stringUnit" : { 268 | "state" : "translated", 269 | "value" : "No mostrar la actividad de red al host local (lo*)." 270 | } 271 | } 272 | } 273 | }, 274 | "z5b-fA-WCk.title" : { 275 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Disable Update Checks\"; ObjectID = \"z5b-fA-WCk\";", 276 | "extractionState" : "extracted_with_value", 277 | "localizations" : { 278 | "en" : { 279 | "stringUnit" : { 280 | "state" : "new", 281 | "value" : "Disable Update Checks" 282 | } 283 | }, 284 | "es" : { 285 | "stringUnit" : { 286 | "state" : "translated", 287 | "value" : "Deshabilitar Verificación de Actualización" 288 | } 289 | } 290 | } 291 | } 292 | }, 293 | "version" : "1.0" 294 | } -------------------------------------------------------------------------------- /Netiquette/mul.lproj/UpdateWindow.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | 5 | }, 6 | "version" : "1.0" 7 | } -------------------------------------------------------------------------------- /Netiquette/patrons.txt: -------------------------------------------------------------------------------- 1 | Patrons (2^6+): 2 | Jan Koum, Matt Mullenweg, Christian Blümlein, Shain Singh, Andreas Fink, Nuno, Rabi Rob Thomas, Mikhail 3 | 4 | Friends of Objective-See: 5 | Kandji, Jamf, CleanMyMac X, Palo Alto Networks, 1Password, Malwarebytes, iVerify, Huntress, SmugMug, Guardian Mobile Firewall, Halo Privacy, The Mitten Mac 6 | -------------------------------------------------------------------------------- /Netiquette/procInfo/libprocInfo.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objective-see/Netiquette/cea0761b0f9121aa1544d6558ea71bd31a06ab11/Netiquette/procInfo/libprocInfo.a -------------------------------------------------------------------------------- /Netiquette/procInfo/procInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // File: procInfo.h 3 | // Project: Proc Info 4 | // 5 | // Created by: Patrick Wardle 6 | // Copyright: 2017 Objective-See 7 | // License: Creative Commons Attribution-NonCommercial 4.0 International License 8 | // 9 | 10 | #ifndef procInfo_h 11 | #define procInfo_h 12 | 13 | #import 14 | #import 15 | #import 16 | 17 | /* CLASSES */ 18 | 19 | @class Binary; 20 | @class Process; 21 | 22 | /* DEFINES */ 23 | 24 | //from audit_kevents.h 25 | #define EVENT_EXIT 1 26 | #define EVENT_FORK 2 27 | #define EVENT_EXECVE 23 28 | #define EVENT_EXEC 27 29 | #define EVENT_SPAWN 43190 30 | 31 | //signers 32 | enum Signer{None, Apple, AppStore, DevID, AdHoc}; 33 | 34 | //signature status 35 | #define KEY_SIGNATURE_STATUS @"signatureStatus" 36 | 37 | //signer 38 | #define KEY_SIGNATURE_SIGNER @"signatureSigner" 39 | 40 | //signing auths 41 | #define KEY_SIGNATURE_AUTHORITIES @"signatureAuthorities" 42 | 43 | //code signing id 44 | #define KEY_SIGNATURE_IDENTIFIER @"signatureIdentifier" 45 | 46 | //entitlements 47 | #define KEY_SIGNATURE_ENTITLEMENTS @"signatureEntitlements" 48 | 49 | /* TYPEDEFS */ 50 | 51 | //block for library 52 | typedef void (^ProcessCallbackBlock)(Process* _Nonnull); 53 | 54 | /* OBJECT: PROCESS INFO */ 55 | 56 | @interface ProcInfo : NSObject 57 | 58 | //init w/ flag 59 | // flag dictates if CPU-intensive logic (code signing, etc) should be preformed 60 | -(id _Nullable)init:(BOOL)goEasy; 61 | 62 | //start monitoring 63 | -(void)start:(ProcessCallbackBlock _Nonnull )callback; 64 | 65 | //stop monitoring 66 | -(void)stop; 67 | 68 | //get list of running processes 69 | -(NSMutableArray* _Nonnull)currentProcesses; 70 | 71 | @end 72 | 73 | /* OBJECT: PROCESS */ 74 | 75 | @interface Process : NSObject 76 | 77 | /* PROPERTIES */ 78 | 79 | //pid 80 | @property pid_t pid; 81 | 82 | //ppid 83 | @property pid_t ppid; 84 | 85 | //user id 86 | @property uid_t uid; 87 | 88 | //type 89 | // used by process mon 90 | @property u_int16_t type; 91 | 92 | //exit code 93 | @property u_int32_t exit; 94 | 95 | //path 96 | @property(nonatomic, retain)NSString* _Nullable path; 97 | 98 | //args 99 | @property(nonatomic, retain)NSMutableArray* _Nonnull arguments; 100 | 101 | //ancestors 102 | @property(nonatomic, retain)NSMutableArray* _Nonnull ancestors; 103 | 104 | //signing info 105 | @property(nonatomic, retain)NSMutableDictionary* _Nonnull signingInfo; 106 | 107 | //Binary object 108 | // has path, hash, etc 109 | @property(nonatomic, retain)Binary* _Nonnull binary; 110 | 111 | //timestamp 112 | @property(nonatomic, retain)NSDate* _Nonnull timestamp; 113 | 114 | /* METHODS */ 115 | 116 | //init with a pid 117 | // method will then (try) fill out rest of object 118 | -(id _Nullable)init:(pid_t)processID; 119 | 120 | //generate signing info 121 | // also classifies if Apple/from App Store/etc. 122 | -(void)generateSigningInfo:(SecCSFlags)flags; 123 | 124 | //set process's path 125 | -(void)pathFromPid; 126 | 127 | //generate list of ancestors 128 | -(void)enumerateAncestors; 129 | 130 | //class method 131 | // get's parent of arbitrary process 132 | +(pid_t)getParentID:(pid_t)child; 133 | 134 | @end 135 | 136 | /* OBJECT: BINARY */ 137 | 138 | @interface Binary : NSObject 139 | { 140 | 141 | } 142 | 143 | /* PROPERTIES */ 144 | 145 | //path 146 | @property(nonatomic, retain)NSString* _Nonnull path; 147 | 148 | //name 149 | @property(nonatomic, retain)NSString* _Nonnull name; 150 | 151 | //icon 152 | @property(nonatomic, retain)NSImage* _Nonnull icon; 153 | 154 | //file attributes 155 | @property(nonatomic, retain)NSDictionary* _Nullable attributes; 156 | 157 | //spotlight meta data 158 | @property(nonatomic, retain)NSDictionary* _Nullable metadata; 159 | 160 | //bundle 161 | // nil for non-apps 162 | @property(nonatomic, retain)NSBundle* _Nullable bundle; 163 | 164 | //signing info 165 | @property(nonatomic, retain)NSDictionary* _Nonnull signingInfo; 166 | 167 | //hash 168 | @property(nonatomic, retain)NSMutableString* _Nonnull sha256; 169 | 170 | //identifier 171 | // either signing id or sha256 hash 172 | @property(nonatomic, retain)NSString* _Nonnull identifier; 173 | 174 | /* METHODS */ 175 | 176 | //init w/ a path 177 | -(id _Nonnull)init:(NSString* _Nonnull)path; 178 | 179 | /* the following methods are rather CPU-intensive 180 | as such, if the proc monitoring is run with the 'goEasy' option, they aren't automatically invoked 181 | */ 182 | 183 | //get an icon for a process 184 | // for apps, this will be app's icon, otherwise just a standard system one 185 | -(void)getIcon; 186 | 187 | //generate signing info (statically) 188 | -(void)generateSigningInfo:(SecCSFlags)flags; 189 | 190 | /* the following methods are not invoked automatically 191 | as such, if you code has to manually invoke them if you want this info 192 | */ 193 | 194 | //generate hash 195 | // algo: sha256 196 | -(void)generateHash; 197 | 198 | //generate id 199 | // either signing id, or sha256 hash 200 | -(void)generateIdentifier; 201 | 202 | @end 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /Netiquette/sort.h: -------------------------------------------------------------------------------- 1 | // 2 | // sort.h 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 8/19/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #ifndef sort_h 10 | #define sort_h 11 | 12 | #import "Event.h" 13 | #import "3rd-party/OrderedDictionary.h" 14 | 15 | //sort by 16 | enum SortBy{SortByName, SortByProto, SortByInterface, SortByState, SortBytesUp, SortBytesDown}; 17 | 18 | /* FUNCTIONS */ 19 | 20 | //combine events 21 | // create dictionary that combines events via their pid 22 | // : { event id 0: event, event id 1: event } 23 | NSMutableDictionary* combineEvents(NSMutableDictionary* events); 24 | 25 | //sort events 26 | // create an ordered dictionary based on column 27 | OrderedDictionary* sortEvents(NSDictionary* events, NSUInteger column, BOOL ascending); 28 | 29 | //sort events 30 | // create an ordered dictionary sorted by traffic 31 | OrderedDictionary* sortEventsByTraffic(NSDictionary* events, NSUInteger column, BOOL ascending); 32 | 33 | 34 | #endif /* sort_h */ 35 | -------------------------------------------------------------------------------- /Netiquette/sort.m: -------------------------------------------------------------------------------- 1 | // 2 | // sort.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 8/19/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "sort.h" 10 | #import 11 | 12 | //combine events 13 | // create dictionary that combines events via their pid 14 | // : { event id 0: event, event id 1: event } 15 | NSMutableDictionary* combineEvents(NSMutableDictionary* events) 16 | { 17 | //event 18 | Event* event = nil; 19 | 20 | //events (by pid) 21 | NSMutableDictionary* combinedEvents = nil; 22 | 23 | //pid 24 | NSNumber* processID = nil; 25 | 26 | //init dictionary for connections 27 | combinedEvents = [NSMutableDictionary dictionary]; 28 | 29 | //create mapping 30 | // pid: { events } 31 | for(NSValue* key in events) 32 | { 33 | //extract event 34 | event = events[key]; 35 | 36 | //extract pid 37 | processID = [NSNumber numberWithUnsignedInteger:event.process.pid]; 38 | 39 | //first connection for process? 40 | // add with alloc'd list for connections 41 | if(nil == combinedEvents[processID]) 42 | { 43 | //init list 44 | combinedEvents[processID] = [[OrderedDictionary alloc] init]; 45 | } 46 | 47 | //add connection 48 | [combinedEvents[processID] setObject:event forKey:key]; 49 | } 50 | 51 | return combinedEvents; 52 | } 53 | 54 | //sort events 55 | OrderedDictionary* sortEvents(NSDictionary* events, NSUInteger column, BOOL ascending) 56 | { 57 | //sorted pids 58 | NSArray* sortedPids = nil; 59 | 60 | //processed events 61 | OrderedDictionary* sortedEvents = nil; 62 | 63 | //last two columns (bytes up/down) are special 64 | if( (column == 4) || (column == 5) ) 65 | { 66 | //sort traffic 67 | sortedEvents = sortEventsByTraffic(events, column, ascending); 68 | 69 | //done 70 | goto bail; 71 | } 72 | 73 | //init 74 | sortedEvents = [[OrderedDictionary alloc] init]; 75 | 76 | //sort pids by process name 77 | sortedPids = [events keysSortedByValueUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) 78 | { 79 | //first event 80 | Event* firstEvent = nil; 81 | 82 | //second event 83 | Event* secondEvent = nil; 84 | 85 | //ascending 86 | // init first and second 87 | if(YES == ascending) 88 | { 89 | //init first 90 | firstEvent = [[((NSDictionary*)obj1) allValues] firstObject]; 91 | 92 | //init second 93 | secondEvent = [[((NSDictionary*)obj2) allValues] firstObject]; 94 | } 95 | 96 | //descending 97 | // init first and second (flipped) 98 | else 99 | { 100 | //init first 101 | firstEvent = [[((NSDictionary*)obj2) allValues] firstObject]; 102 | 103 | //init second 104 | secondEvent = [[((NSDictionary*)obj1) allValues] firstObject]; 105 | } 106 | 107 | //sort by what? 108 | switch (column) { 109 | 110 | //process name 111 | case 0: 112 | 113 | //compare/return name 114 | return [firstEvent.process.binary.name compare:secondEvent.process.binary.name options:NSCaseInsensitiveSearch]; 115 | break; 116 | 117 | /* 118 | //protocol 119 | case 1: 120 | 121 | //compare/return protocol (provider) 122 | return [firstEvent.provider compare:secondEvent.provider options:NSCaseInsensitiveSearch]; 123 | break; 124 | 125 | //interface 126 | case 2: 127 | 128 | //compare/return interface 129 | return [firstEvent.interface compare:secondEvent.interface options:NSCaseInsensitiveSearch]; 130 | break; 131 | 132 | //state 133 | case 3: 134 | 135 | //compare/return state 136 | return [firstEvent.tcpState compare:secondEvent.tcpState options:NSCaseInsensitiveSearch]; 137 | break; 138 | */ 139 | 140 | default: 141 | return NSOrderedAscending; 142 | } 143 | 144 | }]; 145 | 146 | //sanity check 147 | if(0 == sortedPids.count) 148 | { 149 | //bail 150 | goto bail; 151 | } 152 | 153 | //add sorted events 154 | for(NSInteger i = 0; i 15 | #import 16 | 17 | /* FUNCTIONS */ 18 | 19 | //disable std err 20 | void disableSTDERR(void); 21 | 22 | //init crash reporting 23 | void initCrashReporting(void); 24 | 25 | //get app's version 26 | // extracted from Info.plist 27 | NSString* getAppVersion(void); 28 | 29 | //transform app state 30 | OSStatus transformApp(ProcessApplicationTransformState newState); 31 | 32 | //check if (full) dark mode 33 | BOOL isDarkMode(void); 34 | 35 | //loads a framework 36 | // note: assumes it is in 'Framework' dir 37 | NSBundle* loadFramework(NSString* name); 38 | 39 | //convert IP addr to (ns)string 40 | // from: https://stackoverflow.com/a/29147085/3854841 41 | NSString* convertIPAddr(unsigned char* ipAddr, __uint8_t socketFamily); 42 | 43 | //format results 44 | // convert to JSON 45 | NSMutableString* formatResults(OrderedDictionary* connections, BOOL skipApple); 46 | 47 | //prettify JSON 48 | NSString* prettifyJSON(NSString* output); 49 | 50 | //make a window modal 51 | void makeModal(NSWindowController* windowController); 52 | 53 | #endif /* utilities_h */ 54 | -------------------------------------------------------------------------------- /Netiquette/utilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // utilities.m 3 | // Netiquette 4 | // 5 | // Created by Patrick Wardle on 7/14/19. 6 | // Copyright © 2019 Objective-See. All rights reserved. 7 | // 8 | 9 | #import "Event.h" 10 | #import "utilities.h" 11 | 12 | //disable std err 13 | void disableSTDERR(void) 14 | { 15 | //file handle 16 | int devNull = -1; 17 | 18 | //open /dev/null 19 | devNull = open("/dev/null", O_RDWR); 20 | 21 | //dup 22 | dup2(devNull, STDERR_FILENO); 23 | 24 | //close 25 | close(devNull); 26 | 27 | return; 28 | } 29 | 30 | //get app's version 31 | // extracted from Info.plist 32 | NSString* getAppVersion(void) 33 | { 34 | //read and return 'CFBundleVersion' from bundle 35 | return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; 36 | } 37 | 38 | //transform app state 39 | OSStatus transformApp(ProcessApplicationTransformState newState) 40 | { 41 | //serial number 42 | // init with current process 43 | ProcessSerialNumber psn = { 0, kCurrentProcess }; 44 | 45 | //transform and return 46 | return TransformProcessType(&psn, newState); 47 | } 48 | 49 | //check if (full) dark mode 50 | // meaning, Mojave+ and dark mode enabled 51 | BOOL isDarkMode(void) 52 | { 53 | //flag 54 | BOOL darkMode = NO; 55 | 56 | //not mojave? 57 | // bail, since not true dark mode 58 | if(YES != [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 14, 0}]) 59 | { 60 | //bail 61 | goto bail; 62 | } 63 | 64 | //not dark mode? 65 | if(YES != [[NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"] isEqualToString:@"Dark"]) 66 | { 67 | //bail 68 | goto bail; 69 | } 70 | 71 | //ok, mojave dark mode it is! 72 | darkMode = YES; 73 | 74 | bail: 75 | 76 | return darkMode; 77 | } 78 | 79 | //loads a framework 80 | // note: assumes it is in 'Framework' dir 81 | NSBundle* loadFramework(NSString* name) 82 | { 83 | //handle 84 | NSBundle* framework = nil; 85 | 86 | //framework path 87 | NSString* path = nil; 88 | 89 | //init path 90 | path = [NSString stringWithFormat:@"%@/../Frameworks/%@", [NSProcessInfo.processInfo.arguments[0] stringByDeletingLastPathComponent], name]; 91 | 92 | //standardize path 93 | path = [path stringByStandardizingPath]; 94 | 95 | //init framework (bundle) 96 | framework = [NSBundle bundleWithPath:path]; 97 | if(NULL == framework) 98 | { 99 | //bail 100 | goto bail; 101 | } 102 | 103 | //load framework 104 | if(YES != [framework loadAndReturnError:nil]) 105 | { 106 | //bail 107 | goto bail; 108 | } 109 | 110 | bail: 111 | 112 | return framework; 113 | } 114 | 115 | //convert IP addr to (ns)string 116 | // from: https://stackoverflow.com/a/29147085/3854841 117 | NSString* convertIPAddr(unsigned char* ipAddr, __uint8_t socketFamily) 118 | { 119 | //string 120 | NSString* socketDescription = nil; 121 | 122 | //socket address 123 | unsigned char socketAddress[INET6_ADDRSTRLEN] = {0}; 124 | 125 | //what family? 126 | switch(socketFamily) 127 | { 128 | //IPv4 129 | case AF_INET: 130 | { 131 | //convert 132 | inet_ntop(AF_INET, ipAddr, (char*)&socketAddress, INET_ADDRSTRLEN); 133 | 134 | break; 135 | } 136 | 137 | //IPV6 138 | case AF_INET6: 139 | { 140 | //convert 141 | inet_ntop(AF_INET6, ipAddr, (char*)&socketAddress, INET6_ADDRSTRLEN); 142 | 143 | break; 144 | } 145 | 146 | default: 147 | break; 148 | } 149 | 150 | //convert to obj-c string 151 | if(0 != strlen((const char*)socketAddress)) 152 | { 153 | //convert 154 | socketDescription = [NSString stringWithUTF8String:(const char*)socketAddress]; 155 | } 156 | 157 | return socketDescription; 158 | } 159 | 160 | //wait till window non-nil 161 | // then make that window modal 162 | void makeModal(NSWindowController* windowController) 163 | { 164 | //window 165 | __block NSWindow* window = nil; 166 | 167 | //wait till non-nil 168 | // then make window modal 169 | for(int i=0; i<20; i++) 170 | { 171 | //grab window 172 | dispatch_sync(dispatch_get_main_queue(), ^{ 173 | 174 | //grab 175 | window = windowController.window; 176 | 177 | }); 178 | 179 | //nil? 180 | // nap 181 | if(nil == window) 182 | { 183 | //nap 184 | [NSThread sleepForTimeInterval:0.05f]; 185 | 186 | //next 187 | continue; 188 | } 189 | 190 | //have window? 191 | // make it modal 192 | dispatch_sync(dispatch_get_main_queue(), ^{ 193 | 194 | //modal 195 | [[NSApplication sharedApplication] runModalForWindow:windowController.window]; 196 | 197 | }); 198 | 199 | //done 200 | break; 201 | } 202 | 203 | return; 204 | } 205 | 206 | //prettify JSON 207 | NSString* prettifyJSON(NSString* output) 208 | { 209 | //data 210 | NSData* data = nil; 211 | 212 | //object 213 | id object = nil; 214 | 215 | //pretty data 216 | NSData* prettyData = nil; 217 | 218 | //pretty string 219 | NSString* prettyString = nil; 220 | 221 | //convert to data 222 | data = [output dataUsingEncoding:NSUTF8StringEncoding]; 223 | 224 | //convert to JSON 225 | // wrap since we are serializing JSON 226 | @try 227 | { 228 | //serialize 229 | object = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; 230 | 231 | //convert to pretty data 232 | prettyData = [NSJSONSerialization dataWithJSONObject:object options:NSJSONWritingPrettyPrinted error:nil]; 233 | } 234 | //bail on exception 235 | @catch(NSException *exception) 236 | { 237 | ; 238 | } 239 | 240 | //convert to pretty string 241 | if(nil != prettyData) 242 | { 243 | //convert to string 244 | prettyString = [[NSString alloc] initWithData:prettyData encoding:NSUTF8StringEncoding]; 245 | } 246 | else 247 | { 248 | //error 249 | prettyString = NSLocalizedString(@"{\"ERROR\" : \"failed to convert output to JSON\"}", @"{\"ERROR\" : \"failed to convert output to JSON\"}"); 250 | } 251 | 252 | return prettyString; 253 | } 254 | 255 | //format results 256 | // convert to JSON 257 | NSMutableString* formatResults(OrderedDictionary* connections, BOOL skipApple) 258 | { 259 | //output 260 | NSMutableString* output = nil; 261 | 262 | //process 263 | Process* process = nil; 264 | 265 | //json data 266 | // for intermediate conversions 267 | NSData *jsonData = nil; 268 | 269 | //signing info 270 | NSString* signingInfo = nil; 271 | 272 | //(per process) connections 273 | OrderedDictionary* events = nil; 274 | 275 | //init output string 276 | output = [NSMutableString string]; 277 | 278 | //start JSON 279 | [output appendString:@"["]; 280 | 281 | //add each connection (per process) 282 | for(NSNumber* pid in connections) 283 | { 284 | //events (per process) 285 | events = connections[pid]; 286 | 287 | //grab process from first event 288 | process = ((Event*)[[events allValues] firstObject]).process; 289 | 290 | //skip apple? 291 | if(YES == skipApple) 292 | { 293 | //skip apple signed 294 | if( (noErr == [process.signingInfo[KEY_SIGNATURE_STATUS] intValue]) && 295 | (Apple == [process.signingInfo[KEY_SIGNATURE_SIGNER] intValue]) ) 296 | { 297 | //skip 298 | continue; 299 | } 300 | 301 | //cups is apple, 302 | // but owned by root so we can't check it's signature (but it's SIP protected) 303 | if(YES == [process.binary.path isEqualToString:CUPS]) 304 | { 305 | //skip 306 | continue; 307 | } 308 | } 309 | 310 | //convert process signing info to JSON 311 | if(nil != process.signingInfo) 312 | { 313 | //convert signing dictionary 314 | // wrap since we are serializing JSON 315 | @try 316 | { 317 | //convert 318 | jsonData = [NSJSONSerialization dataWithJSONObject:process.signingInfo options:kNilOptions error:NULL]; 319 | if(nil != jsonData) 320 | { 321 | //convert data to string 322 | signingInfo = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 323 | } 324 | } 325 | //ignore exceptions 326 | // ->file sigs will just be 'unknown' 327 | @catch(NSException *exception) 328 | { 329 | ; 330 | } 331 | } 332 | 333 | //add process info 334 | [output appendFormat:NSLocalizedString(@"{\"process\": {\"pid\": \"%d\", \"path\": \"%@\", \"signature(s)\": %@},",@"{\"process\": {\"pid\": \"%d\", \"path\": \"%@\", \"signature(s)\": %@},"), process.pid, process.path, signingInfo]; 335 | 336 | //add events 337 | [output appendFormat:NSLocalizedString(@"\"connections\": [", @"\"connections\": [")]; 338 | 339 | //add all events 340 | for(Event* event in events.allValues) 341 | { 342 | //add 343 | [output appendFormat:@"%@,", [event toJSON]]; 344 | } 345 | 346 | //remove last ',' 347 | if(YES == [output hasSuffix:@","]) 348 | { 349 | //remove 350 | [output deleteCharactersInRange:NSMakeRange([output length]-1, 1)]; 351 | } 352 | 353 | //terminate list 354 | [output appendString:@"]},"]; 355 | } 356 | 357 | //remove last ',' 358 | if(YES == [output hasSuffix:@","]) 359 | { 360 | //remove 361 | [output deleteCharactersInRange:NSMakeRange([output length]-1, 1)]; 362 | } 363 | 364 | //terminate list 365 | [output appendString:@"]"]; 366 | 367 | return output; 368 | } 369 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netiquette 2 | Netiquette, is lightweight open-source network monitor designed exclusively for macOS. 3 | 4 | Via its simple intuitive UI, you can quickly: 5 | * view all network connections 6 | * uncover any listening sockets 7 | * filter and export network information 8 | 9 |

10 | 11 | **Documentation:** \ 12 | Full details and usage instructions can be found [here](https://objective-see.com/products/netiquette.html). 13 | 14 | **To Build:** 15 | 16 | 1. run in project directory, run: `carthage update --platform macOS` 17 | 2. build in Xcode 18 | 19 | Netiquette should build cleanly in Xcode (though you will have to remove code signing constraints, or replace with your own Apple developer code signing certificate). 20 | 21 | **To Run:** \ 22 | Simply run application: `Netiquette.app` 23 | 24 | **To Support:** \ 25 | ❤  Love this product or want to support it? Please check out my [patreon page](https://www.patreon.com/objective_see) :) 26 | 27 |

28 | 29 | 30 | 31 |

32 | 33 | **Mahalo!** \ 34 | This product is supported by the following "Friends of Objective-See": 35 | 36 | [Sophos](https://www.sophos.com/) 37 | 38 | [CleanMyMac X](https://macpaw.com/cleanmymac) 39 | 40 | [Malwarebytes](https://www.malwarebytes.com) 41 | 42 | [SmugMug](https://www.smugmug.com/) 43 | 44 | [Guardian Mobile Firewall](https://guardianapp.com) 45 | 46 | [SecureMac](https://www.securemac.com/) 47 | --------------------------------------------------------------------------------