I have a global daemon managed by launchd, whose .plist is installed in /Library/LaunchDaemons).
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
/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 - https://github.com/jdspoone/SampleOSXLaunchDaemon
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:
<!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:
#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:
/* 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:
#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
$ 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