To be correctly entitled and code-signed so it can communicate with EndpointSecurity framework, its executable resides in a normal Mac App bundle (main() will run as minimal UI when launched from UI, and as a daemon when launched by launchd).
This means that the ProgramArguments.0 in its .plist looks something like
Code Block plist /Library/PrivilegedHelperTools/MyDaemonApp.app/Contents/MacOS/MyDaemonApp
Now I need this daemon to publish an XPC Service (with few control commands) so that other components of our system (UI app, a user-context launchd-daemon and another launchd global-daemon) will be able to connect to The XPC Service and control it via the published protocol.
I read some answers here, and also found a working order sample code that does just this here
But when I apply its content to my global daemon, word for word - it doesn't work - meaning, clients cannot create a connection to The XPC Service.
The daemon is up and running, and functional. its .plist is quite simple and looks like this:
Code Block <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.mycompany.itm.service</string> <key>KeepAlive</key> <true/> <key>RunAtLoad</key> <true/> <key>MachServices</key> <dict> <key>com.mycompany.itm.service</key> <true/> </dict> <key>ProgramArguments</key> <array> <string>/Library/PrivilegedHelperTools/IMyDaemonApp.app/Contents/MacOS/MyDaemonApp</string> <string>-monitor</string> <string>-protectDeviceProtocol</string> <string>USB</string> </array> </dict> </plist>
It creates and starts an XPC listener in MYXPCListener.h like thus:
Code Block Objective-C #import <Foundation/Foundation.h> #import "MYXPCProtocol.h" NS_ASSUME_NONNULL_BEGIN @interface OITPreventionXPCService : NSObject - (instancetype) init; - (void) start; /* Begin listening for incoming XPC connections */ - (void) stop; /* Stop listening for incoming XPC connections */ @end NS_ASSUME_NONNULL_END
and the implementation is:
Code Block Objective-C /* AppDelegate.m */ @interface MYXPCService () <NSXPCListenerDelegate, OITPreventionXPCProtocol> @property (nonatomic, strong, readwrite) NSXPCListener *listener; @property (nonatomic, readwrite) BOOL started; @end @implementation OITPreventionXPCService - (instancetype) init { if ((self = [super init]) != nil) { _listener = [[NSXPCListener alloc] initWithMachServiceName:@"com.mycompany.itm.service"]; _listener.delegate = self; if (_listener == nil) { os_log_error(myLog, "XPCListener failed to initialize"); } _started = NO; } return self; } - (void) start { assert(_started == NO); [_listener resume]; os_log_info(myLog, "XPCListener resumed"); _started = YES; } - (void) stop { assert(_started == YES); [_listener suspend]; os_log_info(myLog, "XPCListener suspended"); _started = NO; } /* NSXPCListenerDelegate implementation */ - (BOOL) listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { os_log_info(myLog, "Prevention XPCListener is bequsted a new connection"); assert(listener == _listener); assert(newConnection != nil); newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MYXPCProtocol)]; newConnection.exportedObject = self; [newConnection resume]; return YES; } /* Further down this implementation, I have implementations to all the methods in MYXPCProtocol. */ @end
Now the client code (and I tried EVERY kind of client, signed unsigned, daemon, UI, root privileged, or user-scoped - whatever). For example, in the AppDelegate of a UI app:
Code Block Objective-C #import "AppDelegate.h" #import "MYXPCProtocol.h" @interface AppDelegate () @property (strong) IBOutlet NSWindow *window; @property (nonatomic, strong, readwrite) NSXPCConnection *connection; /* lazy initialized */ @end @implementation AppDelegate - (NSXPCConnection *) connection { if (_connection == nil) { _connection = [[NSXPCConnection alloc] initWithMachServiceName:daemonLabel options:NSXPCConnectionPrivileged]; _connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MYXPCProtocol)]; _connection.invalidationHandler = ^{ self->_connection = nil; NSLog(@"connection has been invalidated"); }; [_connection resume]; /* New connections always start suspended */ } return _connection; } - (IBAction) getServiceStatus:(id)sender { [self.connection.remoteObjectProxy getStatus:^(NSString * _Nonnull status) { NSLog(@"MY XPC Service status is: %@", status); }]; } @end
but no matter what I do - I always get the "connection invalidated".
The sample launchDaemon that works - is not code-signed at all!!! but mine, which is both signed and checking of which yields
Code Block bash $ spctl --assess --verbose IMyDaemonApp.app IMyDaemonApp.app: accepted source=Notarized Developer ID
I'm at a loss - and would like to get any advice, or any helpful documentation (I've been reading TN2083, and man launchctl and man launchd.plist and many other pages - to no avail. There seems to be no real "programming guide" for XPC and no reasonable sample code on Apple developer site to fit my needs.
Last - this is MacOS 10.15.7, and latest Xcode 12.3