Daemon - Client IPC with Unix sockets - objective-c

I have a launch daemon which I want to ask for status information from a user app. I implemented a client-server model (with the daemon as server) using unix sockets as described here: OS X - Communication between launch daemon and launch agent
In fact it works well, when I run the daemon as a user process (for debugging), but it will fail when it is actually launched as root.
I have read the TN on Daemons and Agents and the Daemon & Services Programming Guide. However, I could not find decent information how the socket must be used in a launch daemon.
I am confused by several things:
Must I specify the socket in the launch daemon plist file? And how?
If the socket is specified in the plist, does that change the way I need to create the socket in code?
What path would be good for the unix socket? The Technical Note recommends /var/run but I guess a user process may not write there, or can it?
Is there maybe a easier way to do IPC between daemon and client?
What is the best way to log the daemon output. I tried NSLog but it seems not work...
I am also unsure if my socket code is correct. Maybe someone more experienced can tell me if I'm on the right track here. I have the following code in the daemon to initialize the unix socket:
#define SOCKETNAME "/var/run/com.company.myApp.socket"
- (void) startServer {
//remove any prev socket
unlink(SOCKETNAME);
CFSocketContext CTX = { 0, (__bridge void *)(self), NULL, NULL, NULL };
CFSocketRef unixSocket = CFSocketCreate(NULL, PF_UNIX, SOCK_STREAM, 0,
kCFSocketAcceptCallBack, (CFSocketCallBack)AcceptCallBack, &CTX);
if (unixSocket == NULL) {/*log and return*/}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, SOCKETNAME);
addr.sun_len = strlen(addr.sun_path) + sizeof (addr.sun_family);
NSData *address = [ NSData dataWithBytes: &addr length: sizeof(addr) ];
if (CFSocketSetAddress(unixSocket, (__bridge CFDataRef) address) != kCFSocketSuccess) {
NSLog(#"CFSocketSetAddress() failed\n");
CFRelease(unixSocket);
}
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, unixSocket, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
CFRelease(sourceRef);
CFRunLoopRun();
}
void AcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
CTServerController* selfServerController = (__bridge CTServerController*) info;
//NSLog(#"acceptCallBack");
//...
}

you should not specify the socket in the plist, you know the concrete place, the server knows, the client knows, that should be enought
the socket specified in the plist is for launching the deamon/agent ondemand, you do not need that in a basic case
you can use any path, if your daemon starts first (usually the case) it has all the privilege to set the correct rights on the socket to let anybody (or a given user, group) read, read and write or any given rights you wish, i'm sure you just forgot to let the client write/read the unix socket
i think on OSX the unix socket IPC is a perfect, easy solution (you have many other choices too of course, xpc, mach messages, etc.)
you can define where to go the stdout and stderr of a daemon in it's plist (StandardOutPath, StandardErrorPath keys)

Related

Per-connection notifications using BlueZ peripheral

I want to use BlueZ as a peripheral / GATT server, and have multiple central devices connect and subscribe to a particular characteristic. Then, I want to send a notification to only 1 of the connected centrals via a Python application. The subscription behavior is in a piece of code I can't control, so having only 1 central be subscribed is not an option. I also need the connection to stay active, so disconnecting from the other devices is also not an option.
I have the GATT server working, notifying all connected centrals via the PropertiesChanged DBus method, by following the example-gatt-server method. The DBus API does not provide a way to deliver a notification to a particular connection handle or connected device, so instead I think my application needs to interact with a BlueZ file descriptor directly. I found that adding the NotifyAcquired property could trigger a call to an implementation of AcquireNotify:
class Characteristic(dbus.service.Object):
"""
org.bluez.GattCharacteristic1 interface implementation
"""
def __init__(self, bus, index, uuid, flags, service):
self.path = service.path + '/char' + str(index)
self.bus = bus
self.uuid = uuid
self.service = service
self.flags = flags
self.descriptors = []
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
return {
GATT_CHRC_IFACE: {
'Service': self.service.get_path(),
'UUID': self.uuid,
'Flags': self.flags,
'Descriptors': dbus.Array(
self.get_descriptor_paths(),
signature='o'),
'NotifyAcquired': False
}
}
...
#dbus.service.method(GATT_CHRC_IFACE, in_signature='a{sv}', out_signature='hq')
def AcquireNotify(self, options):
print('Default AcquireNotify called, returning error')
raise NotSupportedException()
This leaves me with two questions:
How do I generate the fd object that AcquireNotify is expected to return?
How can I use the fd object to deliver a notification to a particular connected device?

Gracefully stop .NET Core server with console or client action

I'm in the process of porting some server application from .NET Framework+WCF to .NET Core, but I'm having trouble managing the server exit. On our current WCF server, we allow quitting both from a WCF request, and from a console input:
static void Main()
{
hExit = new ManualResetEvent(false);
StartServer();
Console.WriteLine("Server started. Press [Enter] to quit.");
char key;
while((key=WaitForKeyOrEvent(hExit)) != '\0' && key != '\r') { }
Console.WriteLine();
StopServer();
}
private static char WaitForKeyOrEvent(System.Threading.WaitHandle hEvent)
{
const int Timeout = 500;
bool dueToEvent = false;
while(!Console.KeyAvailable)
{
if(hEvent.WaitOne(Timeout, false))
{
dueToEvent = true;
break;
}
}
char ret = '\0';
if(!dueToEvent)
{
ret = Console.ReadKey().KeyChar;
if(ret == '\0')
ret = char.MaxValue;
}
return ret;
}
...
class ServerObj : IMyWcfInterface
{
void IMyWcfInterface.ExitServer() { hExit.Set(); }
}
Would this approach also work in .NET Core? (using some other tech than WCF of course, since it was abandoned) I vaguely remember hearing that KeyAvailable/ReadKey might not work for an application in a Docker Container, and use of Containers is one of the "end goals" of migrating to .NET Core...
Generally, when running in a container, you generally don't have access to an input device (think keyboard). So that option is not reliable in a containerized application.
Listening to some sort of network request (eg, HTTP, grpc, protobuf) could work, but you would have to be sure that the source of the request is valid and wasn't a malicious entity attacking your application and forcing it to shutdown.
The idiomatic approach in a container environment (eg, Kubernetes, Docker ) is that the container engine sends your application Linux signals such as SIGTERM. docker stop will do this, as will Kubernetes when stopping your pods. Your application should then handle that and shut down correctly.
The implementation is different depending on whether you are using ASP.NET Core or not.
In ASP.NET Core you can use IApplicationLifetime.ApplicationStopping to register some code to be called when the application is being stopped.
Here's a StackOverflow answer that covers the implementation side: graceful shutdown asp.net core
If you are not using ASP.NET Core, you can handle AppDomain.ProcessExit to register a handler to be called when the application is stopping.
In my case, I had an ASP.NET application that starts up several sub ASP.NET processes. When I shutdown IIS, I wanted the plugin processes to gracefully shutdown, so I added a /shutdown GET route that calls IHostApplicationLifetime.StopApplication(). Whenever I called it, however, it would block and not actually shut down the application.
The fix was that, in my host application, where I start up the processes, I needed to set UseShellExecute = true. This completely solved my problem as my host application could do a GET request to all the plugin processes and they would shutdown almost immediately.
Strange but it is what it is.

RabbitMQ Obj-C client connection status check

I'm using rabbitmq/rabbitmq-objc-client. Is there a way how to check if connection was opened successfully? I see there is RMQConnectionDelegate but there are only methods that are called when an error appears. I'm looking for something like
RMQConnection *conn = [[RMQConnection alloc] /*...*/];
if ([conn isOpen]) {
/* ... */
}
You can do this:
[conn start:^{ // code to execute when connection established }];
But note that the client is async and all channel operations are placed onto a queue for execution after the connection is established.
I've added a note to the issue Michael opened: https://github.com/rabbitmq/rabbitmq-objc-client/issues/101

Proxy server persistent connection closing

I'm implementing proxy support for an osx app.
I've created a custom NSURLProtocol and it is working perfectly for a proxy without authentication. Tried with both CCProxy and FreeProxy on a Windows computer in the local network.
However when proxy authentication is on, any request in the first few seconds works perfectly then the connection goes from ESTABLISHED to CLOSE_WAIT in 5 seconds. The proxy shows 0 connections again and, after that in the app any HTTP request will get a 407, even though the proxy-auth header is pre-set.
My code looks like this :
// set the auth fields
CFStringRef usernameRef = (__bridge CFStringRef)appProxyPrefs.proxyUserName;
CFStringRef passwordRef = (__bridge CFStringRef)appProxyPrefs.proxyPassword;
CFHTTPMessageAddAuthentication(copyOfOriginal, nil, usernameRef, passwordRef, kCFHTTPAuthenticationSchemeBasic, YES);
...
// useless
CFHTTPMessageSetHeaderFieldValue(copyOfOriginal, (__bridge CFStringRef)#"Connection", (CFStringRef)(#"Keep-Alive"));
...
// create stream, callback, schedule
// apply proxy settings to the stream
if (isNoProxyOverride)
CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPProxy, (__bridge CFTypeRef)(noProxyDict));
else
CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPProxy, (__bridge CFTypeRef)(manualProxyDict));
...
CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
if (!CFReadStreamOpen(myReadStream)) { // error }
else
{
// check if there is a prev stream
if (currentStream != nil)
{
[currentStream close];
currentStream = nil;
}
currentStream = (__bridge NSInputStream *)(myReadStream);
}
As you see I tried to store the previous conenction in a static inputstream and releasing it only after I open a new one, but seems useless.
Also tried setting the underlying socket to keep-alive in kCFStreamEventOpenCompleted as suggested in NSStream TCP Keep-alive iOS , still without success.
Why does the connection close ? How could I debug it or make it work ?
Is the connection's fault the proxy goes craxy ?
Thanks.
Edit 1:
Edit 2: It seems it has to do with HTTPS... If I change the the server to be plain http instead of https it will work perfectly.

Authorization with Websphere MQ 6

I have the server side of IBM's WebSphere MQ version 6 on a virtual machine running Windows Server 2003, sitting on a Vista desktop. The desktop has the client installed.
I've got a little test program (from their code samples) that puts a message on a queue and takes it off again. This program worked when run on the server directly with the server binding. However, I can't get it to work from the client side with the client binding.
The error I get is CompCode 2, Reason 2035, which is an authorization failure.
I suspect this has to do with the fact that the program runs under my user by default, which is on a domain that the virtual machine doesn't know about (and can't access).
I have set up a local user on the vm that I'd like to connect as (user: websphere, password: websphere), but I'm not clear on how to get this all to work. I have the code that I'm using below, and I've tried various combinations of security exit settings on the channel and endpoints, but I can't get away from 2035.
Anyone have experience with this? Help would be much appreciated!
Code:
using System;
using System.Collections;
using IBM.WMQ;
class MQSample
{
// The type of connection to use, this can be:-
// MQC.TRANSPORT_MQSERIES_BINDINGS for a server connection.
// MQC.TRANSPORT_MQSERIES_CLIENT for a non-XA client connection
// MQC.TRANSPORT_MQSERIES_XACLIENT for an XA client connection
// MQC.TRANSPORT_MQSERIES_MANAGED for a managed client connection
const String connectionType = MQC.TRANSPORT_MQSERIES_CLIENT;
// Define the name of the queue manager to use (applies to all connections)
const String qManager = "QM_vm_win2003";
// Define the name of your host connection (applies to client connections only)
const String hostName = "vm-win2003";
// Define the name of the channel to use (applies to client connections only)
const String channel = "S_vm_win2003";
/// <summary>
/// Initialise the connection properties for the connection type requested
/// </summary>
/// <param name="connectionType">One of the MQC.TRANSPORT_MQSERIES_ values</param>
static Hashtable init(String connectionType)
{
Hashtable connectionProperties = new Hashtable();
// Add the connection type
connectionProperties.Add(MQC.TRANSPORT_PROPERTY, connectionType);
// Set up the rest of the connection properties, based on the
// connection type requested
switch (connectionType)
{
case MQC.TRANSPORT_MQSERIES_BINDINGS:
break;
case MQC.TRANSPORT_MQSERIES_CLIENT:
connectionProperties.Add(MQC.HOST_NAME_PROPERTY, hostName);
connectionProperties.Add(MQC.CHANNEL_PROPERTY, channel);
connectionProperties.Add(MQC.USER_ID_PROPERTY, "websphere");
connectionProperties.Add(MQC.PASSWORD_PROPERTY, "websphere");
break;
}
return connectionProperties;
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static int Main(string[] args)
{
try
{
Hashtable connectionProperties = init(connectionType);
// Create a connection to the queue manager using the connection
// properties just defined
MQQueueManager qMgr = new MQQueueManager(qManager, connectionProperties);
// Set up the options on the queue we wish to open
int openOptions = MQC.MQOO_INPUT_AS_Q_DEF | MQC.MQOO_OUTPUT;
// Now specify the queue that we wish to open,and the open options
MQQueue system_default_local_queue =
qMgr.AccessQueue("clq_default_vm_sql2000", openOptions);
// Define a WebSphere MQ message, writing some text in UTF format
MQMessage hello_world = new MQMessage();
hello_world.WriteUTF("Hello World!");
// Specify the message options
MQPutMessageOptions pmo = new MQPutMessageOptions();
// accept the defaults,
// same as MQPMO_DEFAULT
// Put the message on the queue
system_default_local_queue.Put(hello_world, pmo);
// Get the message back again
// First define a WebSphere MQ message buffer to receive the message
MQMessage retrievedMessage = new MQMessage();
retrievedMessage.MessageId = hello_world.MessageId;
// Set the get message options
MQGetMessageOptions gmo = new MQGetMessageOptions(); //accept the defaults
//same as MQGMO_DEFAULT
// Get the message off the queue
system_default_local_queue.Get(retrievedMessage, gmo);
// Prove we have the message by displaying the UTF message text
String msgText = retrievedMessage.ReadUTF();
Console.WriteLine("The message is: {0}", msgText);
// Close the queue
system_default_local_queue.Close();
// Disconnect from the queue manager
qMgr.Disconnect();
}
//If an error has occurred in the above,try to identify what went wrong.
//Was it a WebSphere MQ error?
catch (MQException ex)
{
Console.WriteLine("A WebSphere MQ error occurred: {0}", ex.ToString());
}
catch (System.Exception ex)
{
Console.WriteLine("A System error occurred: {0}", ex.ToString());
}
Console.ReadLine();
return 0;
}//end of start
}//end of sample
With Windows-to-Windows connections, WMQ will pass the SID as well as the "short ID" which in this case would be "websphere". This is a little better authorization than you get with non-Windows WMQ which only uses the short ID. The problem is that someone on a non-windows server can connect using the short ID "websphere" and since there is no SID WMQ will accept the connection as thought it were the Windows account.
Two ways to address this. On the QMgr host you can run setmqaut commands to authorize the SID you are actually using to connect. The VM must be able to inquire on the domain where the Windows account lives and the setmqaut command must use -p user#domain syntax.
Alternatively, you can just use the locally defined ID in the MCAUSER of the channel like
ALTER CHL(channel name) CHLTYPE(SVRCONN) MCAUSER('webaphere#vm')
...where 'vm' is the name of the virtual machine and you've authorized the account with setmqaut commands or by putting it into the mqm or administrators group.
Keep in mind this is only for testing! Any channel with a blank or administrative MCAUSER can not only administer WMQ but also execute arbitrary commands on the underlying host server. In the real world you would create accounts with access to queues and the QMgr but not access to administer and you'd put those into all MCAUSER values, then set MCAUSER('nobody') for all the SYSTEM.DEF and SYSTEM.AUTO channels.
Lots more on this available on my web site t-rob.net in the MQ and Links pages. Also, check out:
Comment lines: T.Rob Wyatt: What you didn't know you didn’t know about WebSphere MQ security
Comment lines: T.Rob Wyatt: WebSphere MQ security heats up
I used to have the same problem. the solution, we need to assign user window account to MQA group or administrator group. Then, add user name of the window account to MCA user in the channel.
Hope this helps