├── .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 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
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 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
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 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
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 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
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 |
--------------------------------------------------------------------------------