1 /* macos-ioloop.c
2  *
3  * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * Simple event dispatcher for DNS.
18  */
19 
20 #define _GNU_SOURCE
21 
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <unistd.h>
26 #include <sys/uio.h>
27 #include <errno.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33 #include <sys/time.h>
34 #include <signal.h>
35 #include <net/if.h>
36 #include <ifaddrs.h>
37 #include <dns_sd.h>
38 
39 #include <dispatch/dispatch.h>
40 
41 #include "srp.h"
42 #include "dns-msg.h"
43 #include "srp-crypto.h"
44 #include "ioloop.h"
45 #include "xpc_client_advertising_proxy.h"
46 
47 static bool connection_write_now(comm_t *NONNULL connection);
48 
49 dispatch_queue_t ioloop_main_queue;
50 
51 // Forward references
52 static void tcp_start(comm_t *NONNULL connection);
53 
54 int64_t
ioloop_timenow(void)55 ioloop_timenow(void)
56 {
57     int64_t now;
58     struct timeval tv;
59     gettimeofday(&tv, 0);
60     now = (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000;
61     return now;
62 }
63 
64 static void
wakeup_event(void * context)65 wakeup_event(void *context)
66 {
67     wakeup_t *wakeup = context;
68 
69     // All ioloop wakeups are one-shot.
70     ioloop_cancel_wake_event(wakeup);
71 
72     // Call the callback, which mustn't be null.
73     wakeup->wakeup(wakeup->context);
74 }
75 
76 static void
wakeup_finalize(void * context)77 wakeup_finalize(void *context)
78 {
79     wakeup_t *wakeup = context;
80     if (wakeup->ref_count == 0) {
81         if (wakeup->dispatch_source != NULL) {
82             dispatch_release(wakeup->dispatch_source);
83             wakeup->dispatch_source = NULL;
84         }
85         if (wakeup->finalize != NULL) {
86             wakeup->finalize(wakeup->context);
87         }
88         free(wakeup);
89     }
90 }
91 
92 void
ioloop_wakeup_retain_(wakeup_t * wakeup,const char * file,int line)93 ioloop_wakeup_retain_(wakeup_t *wakeup, const char *file, int line)
94 {
95     (void)file; (void)line;
96     RETAIN(wakeup);
97 }
98 
99 void
ioloop_wakeup_release_(wakeup_t * wakeup,const char * file,int line)100 ioloop_wakeup_release_(wakeup_t *wakeup, const char *file, int line)
101 {
102     (void)file; (void)line;
103     RELEASE(wakeup, wakeup_finalize);
104 }
105 
106 wakeup_t *
ioloop_wakeup_create(void)107 ioloop_wakeup_create(void)
108 {
109     wakeup_t *ret = calloc(1, sizeof(*ret));
110     if (ret) {
111         RETAIN_HERE(ret);
112     }
113     return ret;
114 }
115 
116 bool
ioloop_add_wake_event(wakeup_t * wakeup,void * context,wakeup_callback_t callback,wakeup_callback_t finalize,int milliseconds)117 ioloop_add_wake_event(wakeup_t *wakeup, void *context, wakeup_callback_t callback, wakeup_callback_t finalize,
118                       int milliseconds)
119 {
120     if (callback == NULL) {
121         ERROR("ioloop_add_wake_event called with null callback");
122         return false;
123     }
124     if (wakeup->dispatch_source != NULL) {
125         ioloop_cancel_wake_event(wakeup);
126     }
127     wakeup->wakeup = callback;
128     wakeup->context = context;
129     wakeup->finalize = finalize;
130 
131     wakeup->dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, ioloop_main_queue);
132     if (wakeup->dispatch_source == NULL) {
133         ERROR("dispatch_source_create failed in ioloop_add_wake_event().");
134         return false;
135     }
136     dispatch_source_set_event_handler_f(wakeup->dispatch_source, wakeup_event);
137     dispatch_set_context(wakeup->dispatch_source, wakeup);
138 
139     // libdispatch doesn't allow events that are scheduled to happen right now. But it is actually useful to be
140     // able to trigger an event to happen immediately, and this is the easiest way to do it from ioloop-we
141     // can't rely on just scheduling an asynchronous event on an event loop because that's specific to Mac.
142     if (milliseconds <= 0) {
143         ERROR("ioloop_add_wake_event: milliseconds = %d", milliseconds);
144         milliseconds = 10;
145     }
146     dispatch_source_set_timer(wakeup->dispatch_source,
147                               dispatch_time(DISPATCH_TIME_NOW, (uint64_t)milliseconds * NSEC_PER_SEC / 1000),
148                               (uint64_t)milliseconds * NSEC_PER_SEC / 1000, NSEC_PER_SEC / 100);
149     dispatch_resume(wakeup->dispatch_source);
150 
151     return true;
152 }
153 
154 void
ioloop_cancel_wake_event(wakeup_t * wakeup)155 ioloop_cancel_wake_event(wakeup_t *wakeup)
156 {
157     if (wakeup->dispatch_source != NULL) {
158         dispatch_source_cancel(wakeup->dispatch_source);
159         dispatch_release(wakeup->dispatch_source);
160         wakeup->dispatch_source = NULL;
161     }
162 }
163 
164 bool
ioloop_init(void)165 ioloop_init(void)
166 {
167     ioloop_main_queue = dispatch_get_main_queue();
168     dispatch_retain(ioloop_main_queue);
169     return true;
170 }
171 
172 int
ioloop(void)173 ioloop(void)
174 {
175     dispatch_main();
176     return 0;
177 }
178 
179 #define connection_cancel(conn) connection_cancel_(conn, __FILE__, __LINE__)
180 static void
connection_cancel_(nw_connection_t connection,const char * file,int line)181 connection_cancel_(nw_connection_t connection, const char *file, int line)
182 {
183     if (connection == NULL) {
184         INFO("connection_cancel: null connection at " PUB_S_SRP ":%d", file, line);
185     } else {
186         INFO("connection_cancel: " PUB_S_SRP ":%d", file, line);
187         nw_connection_cancel(connection);
188     }
189 }
190 
191 static void
comm_finalize(comm_t * comm)192 comm_finalize(comm_t *comm)
193 {
194     ERROR("comm_finalize");
195     if (comm->connection != NULL) {
196         nw_release(comm->connection);
197         comm->connection = NULL;
198     }
199     if (comm->listener != NULL) {
200         nw_release(comm->listener);
201         comm->listener = NULL;
202     }
203     if (comm->parameters) {
204         nw_release(comm->parameters);
205         comm->parameters = NULL;
206     }
207     if (comm->pending_write != NULL) {
208         dispatch_release(comm->pending_write);
209         comm->pending_write = NULL;
210     }
211     // If there is an nw_connection_t or nw_listener_t outstanding, then we will get an asynchronous callback
212     // later on.  So we can't actually free the data structure yet, but the good news is that comm_finalize() will
213     // be called again later when the last outstanding asynchronous cancel is done, and then all of the stuff
214     // that follows this will happen.
215 #ifndef __clang_analyzer__
216     if (comm->ref_count > 0) {
217         return;
218     }
219 #endif
220     if (comm->idle_timer != NULL) {
221         ioloop_cancel_wake_event(comm->idle_timer);
222         RELEASE_HERE(comm->idle_timer, wakeup_finalize);
223     }
224     if (comm->name != NULL) {
225         free(comm->name);
226     }
227     if (comm->finalize != NULL) {
228         comm->finalize(comm->context);
229     }
230     free(comm);
231 }
232 
233 void
ioloop_comm_retain_(comm_t * comm,const char * file,int line)234 ioloop_comm_retain_(comm_t *comm, const char *file, int line)
235 {
236     (void)file; (void)line;
237     RETAIN(comm);
238 }
239 
240 void
ioloop_comm_release_(comm_t * comm,const char * file,int line)241 ioloop_comm_release_(comm_t *comm, const char *file, int line)
242 {
243     (void)file; (void)line;
244     RELEASE(comm, comm_finalize);
245 }
246 
247 static message_t *
message_create(size_t message_size)248 message_create(size_t message_size)
249 {
250     message_t *message;
251 
252     // Never should have a message shorter than this.
253     if (message_size < DNS_HEADER_SIZE) {
254         return NULL;
255     }
256 
257     message = (message_t *)malloc(message_size + (sizeof (message_t)) - (sizeof (dns_wire_t)));
258     if (message) {
259         memset(message, 0, (sizeof (message_t)) - (sizeof (dns_wire_t)));
260         RETAIN_HERE(message);
261     }
262     return message;
263 }
264 
265 void
ioloop_comm_cancel(comm_t * connection)266 ioloop_comm_cancel(comm_t *connection)
267 {
268     if (connection->connection != NULL) {
269         connection_cancel(connection->connection);
270     }
271 }
272 
273 static void
message_finalize(message_t * message)274 message_finalize(message_t *message)
275 {
276     free(message);
277 }
278 
279 void
ioloop_message_retain_(message_t * message,const char * file,int line)280 ioloop_message_retain_(message_t *message, const char *file, int line)
281 {
282     (void)file; (void)line;
283     RETAIN(message);
284 }
285 
286 void
ioloop_message_release_(message_t * message,const char * file,int line)287 ioloop_message_release_(message_t *message, const char *file, int line)
288 {
289     (void)file; (void)line;
290     RELEASE(message, message_finalize);
291 }
292 
293 bool
ioloop_send_message(comm_t * connection,message_t * responding_to,struct iovec * iov,int iov_len)294 ioloop_send_message(comm_t *connection, message_t *responding_to, struct iovec *iov, int iov_len)
295 {
296     dispatch_data_t data = NULL, new_data, combined;
297     int i;
298     uint16_t len = 0;
299 
300     // Not needed on OSX because UDP conversations are treated as "connections."
301     (void)responding_to;
302 
303     if (connection->connection == NULL) {
304         return false;
305     }
306 
307     // Create a dispatch_data_t object that contains the data in the iov.
308     for (i = 0; i < iov_len; i++) {
309         new_data = dispatch_data_create(iov->iov_base, iov->iov_len,
310                                         ioloop_main_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
311         len += iov->iov_len;
312         if (data != NULL) {
313             if (new_data != NULL) {
314                 // Subsequent times through
315                 combined = dispatch_data_create_concat(data, new_data);
316                 dispatch_release(data);
317                 dispatch_release(new_data);
318                 data = combined;
319             } else {
320                 // Fail
321                 dispatch_release(data);
322                 data = NULL;
323             }
324         } else {
325             // First time through
326             data = new_data;
327         }
328         if (data == NULL) {
329             ERROR("ioloop_send_message: no memory.");
330             return false;
331         }
332     }
333 
334     if (len == 0) {
335         if (data) {
336             dispatch_release(data);
337         }
338         return false;
339     }
340 
341     // TCP requires a length as well as the payload.
342     if (connection->tcp_stream) {
343         len = htons(len);
344         new_data = dispatch_data_create(&len, sizeof (len), ioloop_main_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
345         if (new_data == NULL) {
346             if (data != NULL) {
347                 dispatch_release(data);
348             }
349             return false;
350         }
351         // Length is at beginning.
352         combined = dispatch_data_create_concat(new_data, data);
353         dispatch_release(data);
354         dispatch_release(new_data);
355         if (combined == NULL) {
356             return false;
357         }
358         data = combined;
359     }
360 
361     if (connection->pending_write != NULL) {
362         ERROR("Dropping pending write on " PRI_S_SRP, connection->name ? connection->name : "<null>");
363     }
364     connection->pending_write = data;
365     if (connection->connection_ready) {
366         return connection_write_now(connection);
367     }
368     return true;
369 }
370 
371 static bool
connection_write_now(comm_t * connection)372 connection_write_now(comm_t *connection)
373 {
374     // Retain the connection once for each write that's pending, so that it's never finalized while
375     // there's a write in progress.
376     connection->writes_pending++;
377     RETAIN_HERE(connection);
378     nw_connection_send(connection->connection, connection->pending_write, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true,
379                        ^(nw_error_t  _Nullable error) {
380                            if (error != NULL) {
381                                ERROR("ioloop_send_message: write failed: " PUB_S_SRP,
382                                      strerror(nw_error_get_error_code(error)));
383                                connection_cancel(connection->connection);
384                            }
385                            if (connection->writes_pending > 0) {
386                                connection->writes_pending--;
387                                RELEASE_HERE(connection, comm_finalize);
388                            } else {
389                                ERROR("ioloop_send_message: write callback reached with no writes marked pending.");
390                            }
391                        });
392     // nw_connection_send should retain this, so let go of our reference to it.
393     dispatch_release(connection->pending_write);
394     connection->pending_write = NULL;
395     return true;
396 }
397 
398 static bool
datagram_read(comm_t * connection,size_t length,dispatch_data_t content,nw_error_t error)399 datagram_read(comm_t *connection, size_t length, dispatch_data_t content, nw_error_t error)
400 {
401     message_t *message = NULL;
402     bool ret = true, *retp = &ret;
403 
404     if (error != NULL) {
405         ERROR("datagram_read: " PUB_S_SRP, strerror(nw_error_get_error_code(error)));
406         ret = false;
407         goto out;
408     }
409     if (length > UINT16_MAX) {
410         ERROR("datagram_read: oversized datagram length %zd", length);
411         ret = false;
412         goto out;
413     }
414     message = message_create(length);
415     if (message == NULL) {
416         ERROR("datagram_read: unable to allocate message.");
417         ret = false;
418         goto out;
419     }
420     message->length = (uint16_t)length;
421     dispatch_data_apply(content,
422                         ^bool (dispatch_data_t __unused region, size_t offset, const void *buffer, size_t size) {
423             if (message->length < offset + size) {
424                 ERROR("datagram_read: data region %zd:%zd is out of range for message length %d",
425                       offset, size, message->length);
426                 *retp = false;
427                 return false;
428             }
429             memcpy(((uint8_t *)&message->wire) + offset, buffer, size);
430             return true;
431         });
432     if (ret == true) {
433         // Process the message.
434         connection->datagram_callback(connection, message, connection->context);
435     }
436 
437     out:
438     if (message != NULL) {
439         ioloop_message_release(message);
440     }
441     if (!ret) {
442         connection_cancel(connection->connection);
443     }
444     return ret;
445 }
446 
447 static void
tcp_read(comm_t * connection,size_t length,dispatch_data_t content,nw_error_t error)448 tcp_read(comm_t *connection, size_t length, dispatch_data_t content, nw_error_t error)
449 {
450     if (error != NULL) {
451         connection_cancel(connection->connection);
452         return;
453     }
454     if (datagram_read(connection, length, content, error)) {
455         // Wait for the next frame
456         tcp_start(connection);
457     }
458 }
459 
460 static void
tcp_read_length(comm_t * connection,dispatch_data_t content,nw_error_t error)461 tcp_read_length(comm_t *connection, dispatch_data_t content, nw_error_t error)
462 {
463     size_t length;
464     uint32_t bytes_to_read;
465     const uint8_t *lenbuf;
466     dispatch_data_t map;
467 
468     if (error != NULL) {
469         ERROR("tcp_read_length: " PUB_S_SRP, strerror(nw_error_get_error_code(error)));
470     fail:
471         connection_cancel(connection->connection);
472         return;
473     }
474     if (connection->connection == NULL) {
475         return;
476     }
477     if (content == NULL) {
478         INFO("tcp_read_length: remote end closed connection.");
479         goto fail;
480     }
481 
482     map = dispatch_data_create_map(content, (const void **)&lenbuf, &length);
483     if (map == NULL) {
484         ERROR("tcp_read_length: map create failed");
485         goto fail;
486     } else if (length != 2) {
487         ERROR("tcp_read_length: invalid length = %zu", length);
488         goto fail;
489     }
490     bytes_to_read = ((unsigned)(lenbuf[0]) << 8) | ((unsigned)lenbuf[1]);
491     nw_connection_receive(connection->connection, bytes_to_read, bytes_to_read,
492                               ^(dispatch_data_t new_content, nw_content_context_t __unused new_context,
493                                 bool __unused is_complete, nw_error_t new_error) {
494                                   tcp_read(connection, bytes_to_read, new_content, new_error);
495                               });
496 }
497 
498 static void __unused
connection_idle_wakeup_callback(void * context)499 connection_idle_wakeup_callback(void *context)
500 {
501     comm_t *connection = context;
502     ERROR("Connection " PRI_S_SRP " has gone idle", connection->name);
503     connection_cancel(connection->connection);
504 }
505 
506 static void __unused
connection_idle_wakeup_finalize(void * context)507 connection_idle_wakeup_finalize(void *context)
508 {
509     comm_t *connection = context;
510     connection->idle_timer = NULL;
511 }
512 
513 static void
tcp_start(comm_t * connection)514 tcp_start(comm_t *connection)
515 {
516     if (connection->connection == NULL) {
517         return;
518     }
519     // We want to disconnect if the connection is idle for more than a short while.
520     if (connection->idle_timer == NULL) {
521         connection->idle_timer = ioloop_wakeup_create();
522         if (connection->idle_timer == NULL) {
523             // If we can't set up a timer, drop the connection now.
524             connection_cancel(connection->connection);
525             return;
526         }
527     }
528     ioloop_add_wake_event(connection->idle_timer, connection,
529                           connection_idle_wakeup_callback, connection_idle_wakeup_finalize,
530                           60 * 1000); // One minute
531     nw_connection_receive(connection->connection, 2, 2,
532                           ^(dispatch_data_t content, nw_content_context_t __unused context,
533                             bool is_complete, nw_error_t error) {
534                               // For TCP connections, is_complete means the other end closed the connection.
535                               if (is_complete || content == NULL) {
536                                   INFO("tcp_start: remote end closed connection.");
537                                   connection_cancel(connection->connection);
538                               } else {
539                                   tcp_read_length(connection, content, error);
540                               }
541                           });
542 }
543 
544 static void
udp_start(comm_t * connection)545 udp_start(comm_t *connection)
546 {
547     if (connection->connection == NULL) {
548         return;
549     }
550 
551     // UDP is connectionless; the "connection" is just a placeholder that allows us to reply to the source.
552     // In principle, the five-tuple that is represented by the connection object should die as soon as the
553     // client is done retransmitting, since a later transaction should come from a different source port.
554     // Consequently, we set an idle timer: if we don't see any packets on this five-tuple after twenty seconds,
555     // it's unlikely that we will see any more, so it's time to collect the connection.  If another packet
556     // does come in after this, a new connection will be created. The only risk is that if the cancel comes
557     // after a packet has arrived and been consumed by the nw_connection, but before we've called nw_connection_read,
558     // it will be lost. This should never happen for an existing SRP client, since the longest retry interval
559     // by default is 15 seconds; as the retry intervals get longer, it becomes safer to collect the connection
560     // and allow it to be recreated.
561     if (connection->server) {
562         if (connection->idle_timer == NULL) {
563             connection->idle_timer = ioloop_wakeup_create();
564             if (connection->idle_timer == NULL) {
565                 // If we can't set up a timer, drop the connection now.
566                 connection_cancel(connection->connection);
567                 return;
568             }
569         }
570         ioloop_add_wake_event(connection->idle_timer, connection,
571                               connection_idle_wakeup_callback, connection_idle_wakeup_finalize,
572                               20 * 1000); // 20 seconds (15 seconds is the SRP client retry interval)
573     }
574 
575     connection->read_pending = true;    // When a read is pending, we have an extra refcount on the connection
576     RETAIN_HERE(connection);
577     nw_connection_receive_message(connection->connection,
578                           ^(dispatch_data_t content, nw_content_context_t __unused context,
579                             bool __unused is_complete, nw_error_t error) {
580                               bool proceed = true;
581                               if (content != NULL) {
582                                   proceed = datagram_read(connection, dispatch_data_get_size(content),
583                                                           content, error);
584                               }
585                               if (content == NULL || error != NULL) {
586                                   connection_cancel(connection->connection);
587                               }
588                               // Once we have a five-tuple connection, we can't easily get rid of it, so keep
589                               // reading.
590                               else if (proceed) {
591                                   udp_start(connection);
592                               }
593                               RELEASE_HERE(connection, comm_finalize);
594                           });
595 }
596 
597 static void
connection_state_changed(comm_t * connection,nw_connection_state_t state,nw_error_t error)598 connection_state_changed(comm_t *connection, nw_connection_state_t state, nw_error_t error)
599 {
600     (void)error;
601     if (state == nw_connection_state_ready) {
602         INFO("connection_state_changed: " PRI_S_SRP " state is ready; error = %p",
603              connection->name != NULL ? connection->name : "<no name>", error);
604         // Set up a reader.
605         if (connection->tcp_stream) {
606             tcp_start(connection);
607         } else {
608             udp_start(connection);
609         }
610         connection->connection_ready = true;
611         // If there's a write pending, send it now.
612         if (connection->pending_write) {
613             connection_write_now(connection);
614         }
615     } else if (state == nw_connection_state_failed) {
616         INFO("connection_state_changed: " PRI_S_SRP " state is failed; error = %p",
617              connection->name != NULL ? connection->name : "<no name>", error);
618         connection_cancel(connection->connection);
619     } else if (state == nw_connection_state_cancelled) {
620         INFO("connection_state_changed: " PRI_S_SRP " state is canceled; error = %p",
621              connection->name != NULL ? connection->name : "<no name>", error);
622         // This releases the final reference to the connection object, which was held by the nw_connection_t.
623         RELEASE_HERE(connection, comm_finalize);
624     } else {
625         INFO("connection_state_changed: " PRI_S_SRP " state is %d; error = %p",
626              connection->name != NULL ? connection->name : "<no name>", state, error);
627     }
628 }
629 
630 static void
connection_callback(comm_t * listener,nw_connection_t new_connection)631 connection_callback(comm_t *listener, nw_connection_t new_connection)
632 {
633     comm_t *connection = calloc(1, sizeof *connection);
634     if (connection == NULL) {
635         ERROR("Unable to receive connection: no memory.");
636         // Assuming that since we haven't retained the connection, it will be released?
637         // XXX RefCount Check.
638         return;
639     }
640 
641     connection->connection = new_connection;
642     nw_retain(connection->connection);
643 
644     connection->name = nw_connection_copy_description(connection->connection);
645     if (connection->name != NULL) {
646         INFO("Received connection from " PRI_S_SRP, connection->name);
647     } else {
648         ERROR("Unable to get description of new connection.");
649     }
650     connection->datagram_callback = listener->datagram_callback;
651     connection->tcp_stream = listener->tcp_stream;
652     connection->server = true;
653     nw_connection_set_state_changed_handler(connection->connection,
654                                             ^(nw_connection_state_t state, nw_error_t error)
655                                             { connection_state_changed(connection, state, error); });
656     nw_connection_set_queue(connection->connection, ioloop_main_queue);
657     nw_connection_start(connection->connection);
658     // new_connection holds a reference to the connection until it is canceled.
659     RETAIN_HERE(connection);
660     if (listener->connected != NULL) {
661         listener->connected(connection, listener->context);
662     }
663 }
664 
665 static void
listener_finalize(comm_t * listener)666 listener_finalize(comm_t *listener)
667 {
668     if (listener->listener != NULL) {
669         nw_release(listener->listener);
670         listener->listener = NULL;
671     }
672     if (listener->name != NULL) {
673         free(listener->name);
674     }
675     if (listener->parameters) {
676         nw_release(listener->parameters);
677     }
678     if (listener->avoid_ports != NULL) {
679         free(listener->avoid_ports);
680     }
681     if (listener->finalize) {
682         listener->finalize(listener->context);
683     }
684     free(listener);
685 }
686 
687 void
ioloop_listener_retain_(comm_t * listener,const char * file,int line)688 ioloop_listener_retain_(comm_t *listener, const char *file, int line)
689 {
690     RETAIN(listener);
691 }
692 
693 void
ioloop_listener_release_(comm_t * listener,const char * file,int line)694 ioloop_listener_release_(comm_t *listener, const char *file, int line)
695 {
696     RELEASE(listener, listener_finalize);
697 }
698 
699 void
ioloop_listener_cancel(comm_t * connection)700 ioloop_listener_cancel(comm_t *connection)
701 {
702     if (connection->listener != NULL) {
703         nw_listener_cancel(connection->listener);
704         // connection->listener will be released in ioloop_listener_state_changed_handler: nw_listener_state_cancelled.
705     }
706 }
707 
708 static void
ioloop_listener_state_changed_handler(comm_t * listener,nw_listener_state_t state,nw_error_t error)709 ioloop_listener_state_changed_handler(comm_t *listener, nw_listener_state_t state, nw_error_t error)
710 {
711     int i;
712 
713 #ifdef DEBUG_VERBOSE
714     if (listener->listener == NULL) {
715         if (state == nw_listener_state_cancelled) {
716             INFO("nw_listener gets released before the final nw_listener_state_cancelled event - name: " PRI_S_SRP,
717                  listener->name);
718         } else {
719             ERROR("nw_listener gets released before the listener is canceled - name: " PRI_S_SRP ", state: %d",
720                   listener->name, state);
721         }
722     }
723 #endif // DEBUG_VERBOSE
724 
725     // Should never happen.
726     if (listener->listener == NULL && state != nw_listener_state_cancelled) {
727         return;
728     }
729 
730     if (error != NULL) {
731         INFO("nw_listener_create:state changed: error");
732     } else {
733         if (state == nw_listener_state_waiting) {
734             INFO("nw_listener_create: waiting");
735             return;
736         } else if (state == nw_listener_state_failed) {
737             INFO("nw_listener_create: failed");
738             nw_listener_cancel(listener->listener);
739         } else if (state == nw_listener_state_ready) {
740             INFO("nw_listener_create: ready");
741             if (listener->avoiding) {
742                 listener->listen_port = nw_listener_get_port(listener->listener);
743                 if (listener->avoid_ports != NULL) {
744                     for (i = 0; i < listener->num_avoid_ports; i++) {
745                         if (listener->avoid_ports[i] == listener->listen_port) {
746                             INFO("ioloop_listener_state_changed_handler: Got port %d, which we are avoiding.",
747                                  listener->listen_port);
748                             listener->avoiding = true;
749                             listener->listen_port = 0;
750                             nw_listener_cancel(listener->listener);
751                             return;
752                         }
753                     }
754                 }
755                 INFO("ioloop_listener_state_changed_handler: Got port %d.", listener->listen_port);
756                 listener->avoiding = false;
757                 if (listener->ready) {
758                     listener->ready(listener->context, listener->listen_port);
759                 }
760             }
761         } else if (state == nw_listener_state_cancelled) {
762             INFO("ioloop_listener_state_changed_handler: cancelled");
763             nw_release(listener->listener);
764             listener->listener = NULL;
765             if (listener->avoiding) {
766                 listener->listener = nw_listener_create(listener->parameters);
767                 if (listener->listener == NULL) {
768                     ERROR("ioloop_listener_state_changed_handler: Unable to recreate listener.");
769                     goto cancel;
770                 } else {
771                     RETAIN_HERE(listener);
772                     nw_listener_set_state_changed_handler(listener->listener,
773                                                           ^(nw_listener_state_t ev_state, nw_error_t ev_error) {
774                             ioloop_listener_state_changed_handler(listener, ev_state, ev_error);
775                         });
776                 }
777             } else {
778                 ;
779             cancel:
780                 if (listener->cancel) {
781                     listener->cancel(listener->context);
782                 }
783                 RELEASE_HERE(listener, listener_finalize);
784             }
785         }
786     }
787 }
788 
789 comm_t *
ioloop_listener_create(bool stream,bool tls,uint16_t * avoid_ports,int num_avoid_ports,const addr_t * ip_address,const char * multicast,const char * name,datagram_callback_t datagram_callback,connect_callback_t connected,cancel_callback_t cancel,ready_callback_t ready,finalize_callback_t finalize,void * context)790 ioloop_listener_create(bool stream, bool tls, uint16_t *avoid_ports, int num_avoid_ports,
791                        const addr_t *ip_address, const char *multicast, const char *name,
792                        datagram_callback_t datagram_callback, connect_callback_t connected, cancel_callback_t cancel,
793                        ready_callback_t ready, finalize_callback_t finalize, void *context)
794 {
795     comm_t *listener;
796     int family = (ip_address != NULL) ? ip_address->sa.sa_family : AF_UNSPEC;
797     uint16_t port;
798     char portbuf[10];
799     nw_endpoint_t endpoint;
800 
801     if (ip_address == NULL) {
802         port = 0;
803     } else {
804         port = (family == AF_INET) ? ip_address->sin.sin_port : ip_address->sin6.sin6_port;
805     }
806 
807     if (multicast != NULL) {
808         ERROR("ioloop_setup_listener: multicast not supported.");
809         return NULL;
810     }
811 
812     if (datagram_callback == NULL) {
813         ERROR("ioloop_setup: no datagram callback provided.");
814         return NULL;
815     }
816 
817     sprintf(portbuf, "%d", port);
818     listener = calloc(1, sizeof(*listener));
819     if (listener == NULL) {
820         if (ip_address == NULL) {
821             ERROR("No memory for listener on <NULL>#%d", port);
822         } else if (family == AF_INET) {
823             IPv4_ADDR_GEN_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf);
824             ERROR("No memory for listener on " PRI_IPv4_ADDR_SRP "#%d",
825                   IPv4_ADDR_PARAM_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf), port);
826         } else if (family == AF_INET6) {
827             SEGMENTED_IPv6_ADDR_GEN_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
828             ERROR("No memory for listener on " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
829                   SEGMENTED_IPv6_ADDR_PARAM_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf), port);
830         } else {
831             ERROR("No memory for listener on <family address other than AF_INET or AF_INET6: %d>#%d", family, port);
832         }
833         return NULL;
834     }
835     if (avoid_ports != NULL) {
836         listener->avoid_ports = malloc(num_avoid_ports * sizeof(uint16_t));
837         if (listener->avoid_ports == NULL) {
838             if (ip_address == NULL) {
839                 ERROR("No memory for listener avoid_ports on <NULL>#%d", port);
840             } else if (family == AF_INET) {
841                 IPv4_ADDR_GEN_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf);
842                 ERROR("No memory for listener avoid_ports on " PRI_IPv4_ADDR_SRP "#%d",
843                       IPv4_ADDR_PARAM_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf), port);
844             } else if (family == AF_INET6) {
845                 SEGMENTED_IPv6_ADDR_GEN_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
846                 ERROR("No memory for listener avoid_ports on " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
847                       SEGMENTED_IPv6_ADDR_PARAM_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf), port);
848             } else {
849                 ERROR("No memory for listener avoid_ports on <family address other than AF_INET or AF_INET6: %d>#%d",
850                       family, port);
851             }
852             free(listener);
853             return NULL;
854         }
855         listener->num_avoid_ports = num_avoid_ports;
856         listener->avoiding = true;
857     }
858     RETAIN_HERE(listener);
859     if (port == 0) {
860         endpoint = NULL;
861         // Even though we don't have any ports to avoid, we still want the "avoiding" behavior in this case, since that
862         // is what triggers a call to the ready handler, which passes the port number that we got to it.
863         listener->avoiding = true;
864     } else {
865         listener->listen_port = port;
866         char ip_address_str[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
867         if (ip_address == NULL) {
868             if (family == AF_INET) {
869                 snprintf(ip_address_str, sizeof(ip_address_str), "0.0.0.0");
870             } else {
871                 // AF_INET6 or AF_UNSPEC
872                 snprintf(ip_address_str, sizeof(ip_address_str), "::");
873             }
874         } else {
875             inet_ntop(family, ip_address->sa.sa_data, ip_address_str, sizeof(ip_address_str));
876         }
877         endpoint = nw_endpoint_create_host(ip_address_str, portbuf);
878         if (endpoint == NULL) {
879             ERROR("No memory for listener endpoint.");
880             RELEASE_HERE(listener, listener_finalize);
881             return NULL;
882         }
883     }
884 
885     if (stream) {
886         listener->parameters = nw_parameters_create_secure_tcp(tls ? NW_PARAMETERS_DEFAULT_CONFIGURATION
887                                                                     : NW_PARAMETERS_DISABLE_PROTOCOL,
888                                                                NW_PARAMETERS_DEFAULT_CONFIGURATION);
889     } else {
890         if (tls) {
891             ERROR("DTLS support not implemented.");
892             nw_release(endpoint);
893             RELEASE_HERE(listener, listener_finalize);
894             return NULL;
895         }
896         listener->parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL,
897                                                                NW_PARAMETERS_DEFAULT_CONFIGURATION);
898     }
899     if (listener->parameters == NULL) {
900         ERROR("No memory for listener parameters.");
901         nw_release(endpoint);
902         RELEASE_HERE(listener, listener_finalize);
903         return NULL;
904     }
905 
906     if (endpoint != NULL) {
907         nw_parameters_set_local_endpoint(listener->parameters, endpoint);
908         nw_release(endpoint);
909     }
910 
911     if (tls) {
912         nw_protocol_options_t tls_options = nw_tls_create_options();
913         if (tls_options == NULL) {
914             ERROR("No memory for tls protocol options.");
915             RELEASE_HERE(listener, listener_finalize);
916             return NULL;
917         }
918         // XXX set up the listener certificate(s).
919         // XXX how to configure this onto the parameters object?
920     }
921 
922     // Set SO_REUSEADDR.
923     nw_parameters_set_reuse_local_address(listener->parameters, true);
924 
925     // Create the nw_listener_t.
926     listener->listener = nw_listener_create(listener->parameters);
927     if (listener->listener == NULL) {
928         ERROR("no memory for nw_listener object");
929         RELEASE_HERE(listener, listener_finalize);
930         return NULL;
931     }
932     nw_listener_set_new_connection_handler(listener->listener,
933                                            ^(nw_connection_t connection) { connection_callback(listener, connection); }
934                                            );
935 
936     RETAIN_HERE(listener); // for the nw_listener_t
937     nw_listener_set_state_changed_handler(listener->listener, ^(nw_listener_state_t state, nw_error_t error) {
938             ioloop_listener_state_changed_handler(listener, state, error);
939         });
940 
941     listener->name = strdup(name);
942     listener->datagram_callback = datagram_callback;
943     listener->cancel = cancel;
944     listener->ready = ready;
945     listener->finalize = finalize;
946     listener->context = context;
947     listener->connected = connected;
948     listener->tcp_stream = stream;
949 
950     nw_listener_set_queue(listener->listener, ioloop_main_queue);
951     nw_listener_start(listener->listener);
952     // Listener has one refcount
953     return listener;
954 }
955 
956 comm_t *
ioloop_connection_create(addr_t * NONNULL remote_address,bool tls,bool stream,datagram_callback_t datagram_callback,connect_callback_t connected,disconnect_callback_t disconnected,finalize_callback_t finalize,void * context)957 ioloop_connection_create(addr_t *NONNULL remote_address, bool tls, bool stream,
958                          datagram_callback_t datagram_callback, connect_callback_t connected,
959                          disconnect_callback_t disconnected, finalize_callback_t finalize, void *context)
960 {
961     comm_t *connection;
962     char portbuf[10];
963     nw_parameters_t parameters;
964     nw_endpoint_t endpoint;
965     char addrbuf[INET6_ADDRSTRLEN];
966 
967     inet_ntop(remote_address->sa.sa_family, (remote_address->sa.sa_family == AF_INET
968                                              ? (void *)&remote_address->sin.sin_addr
969                                              : (void *)&remote_address->sin6.sin6_addr), addrbuf, sizeof addrbuf);
970     sprintf(portbuf, "%d", (remote_address->sa.sa_family == AF_INET
971                             ? ntohs(remote_address->sin.sin_port)
972                             : ntohs(remote_address->sin6.sin6_port)));
973     connection = calloc(1, sizeof(*connection));
974     if (connection == NULL) {
975         ERROR("No memory for connection");
976         return NULL;
977     }
978     // If we don't release this because of an error, this is the caller's reference to the comm_t.
979     RETAIN_HERE(connection);
980     endpoint = nw_endpoint_create_host(addrbuf, portbuf);
981     if (endpoint == NULL) {
982         ERROR("No memory for connection endpoint.");
983         RELEASE_HERE(connection, comm_finalize);
984         return NULL;
985     }
986 
987     if (stream) {
988         parameters = nw_parameters_create_secure_tcp(tls ? NW_PARAMETERS_DEFAULT_CONFIGURATION
989                                                             : NW_PARAMETERS_DISABLE_PROTOCOL,
990                                                      NW_PARAMETERS_DEFAULT_CONFIGURATION);
991     } else {
992         if (tls) {
993             ERROR("DTLS support not implemented.");
994             nw_release(endpoint);
995             RELEASE_HERE(connection, comm_finalize);
996             return NULL;
997         }
998         parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL,
999                                                      NW_PARAMETERS_DEFAULT_CONFIGURATION);
1000     }
1001     if (parameters == NULL) {
1002         ERROR("No memory for connection parameters.");
1003         nw_release(endpoint);
1004         RELEASE_HERE(connection, comm_finalize);
1005         return NULL;
1006     }
1007 
1008     if (tls) {
1009 #ifdef NOTYET
1010         nw_protocol_options_t tls_options = nw_tls_create_options();
1011         if (tls_options == NULL) {
1012             ERROR("No memory for tls protocol options.");
1013             RELEASE_HERE(connection, comm_finalize);
1014             return NULL;
1015         }
1016         // XXX set up the connection certificate(s).
1017         // XXX how to configure this onto the parameters object?
1018 #endif
1019     }
1020 
1021     connection->name = strdup(addrbuf);
1022 
1023     // Create the nw_connection_t.
1024     connection->connection = nw_connection_create(endpoint, parameters);
1025     nw_release(endpoint);
1026     nw_release(parameters);
1027     if (connection->connection == NULL) {
1028         ERROR("no memory for nw_connection object");
1029         RELEASE_HERE(connection, comm_finalize);
1030         return NULL;
1031     }
1032 
1033     connection->datagram_callback = datagram_callback;
1034     connection->connected = connected;
1035     connection->disconnected = disconnected;
1036     connection->finalize = finalize;
1037     connection->tcp_stream = stream;
1038     connection->context = context;
1039     nw_connection_set_state_changed_handler(connection->connection,
1040                                             ^(nw_connection_state_t state, nw_error_t error)
1041                                             { connection_state_changed(connection, state, error); });
1042     nw_connection_set_queue(connection->connection, ioloop_main_queue);
1043     // Until we get the canceled callback in connection_state_changed, the nw_connection_t holds a reference to this
1044     // comm_t object.
1045     RETAIN_HERE(connection);
1046     nw_connection_start(connection->connection);
1047     return connection;
1048 }
1049 
1050 static void
subproc_finalize(subproc_t * subproc)1051 subproc_finalize(subproc_t *subproc)
1052 {
1053     int i;
1054     for (i = 0; i < subproc->argc; i++) {
1055         if (subproc->argv[i] != NULL) {
1056             free(subproc->argv[i]);
1057             subproc->argv[i] = NULL;
1058         }
1059     }
1060     if (subproc->dispatch_source != NULL) {
1061         dispatch_release(subproc->dispatch_source);
1062     }
1063     if (subproc->output_fd != NULL) {
1064         ioloop_file_descriptor_release(subproc->output_fd);
1065     }
1066     if (subproc->finalize != NULL) {
1067         subproc->finalize(subproc->context);
1068     }
1069     free(subproc);
1070 }
1071 
subproc_cancel(void * context)1072 static void subproc_cancel(void *context)
1073 {
1074     subproc_t *subproc = context;
1075     subproc->dispatch_source = NULL;
1076     RELEASE_HERE(subproc, subproc_finalize);
1077 }
1078 
1079 static void
subproc_event(void * context)1080 subproc_event(void *context)
1081 {
1082     subproc_t *subproc = context;
1083     pid_t pid;
1084     int status;
1085 
1086     pid = waitpid(subproc->pid, &status, WNOHANG);
1087     if (pid <= 0) {
1088         return;
1089     }
1090     subproc->callback(subproc, status, NULL);
1091     if (!WIFSTOPPED(status)) {
1092         dispatch_source_cancel(subproc->dispatch_source);
1093     }
1094 }
1095 
subproc_output_finalize(void * context)1096 static void subproc_output_finalize(void *context)
1097 {
1098     subproc_t *subproc = context;
1099     if (subproc->output_fd) {
1100         subproc->output_fd = NULL;
1101     }
1102 }
1103 
1104 void
ioloop_subproc_release_(subproc_t * subproc,const char * file,int line)1105 ioloop_subproc_release_(subproc_t *subproc, const char *file, int line)
1106 {
1107     RELEASE(subproc, subproc_finalize);
1108 }
1109 
1110 // Invoke the specified executable with the specified arguments.   Call callback when it exits.
1111 // All failures are reported through the callback.
1112 subproc_t *
ioloop_subproc(const char * exepath,char * NULLABLE * argv,int argc,subproc_callback_t callback,io_callback_t output_callback,void * context)1113 ioloop_subproc(const char *exepath, char *NULLABLE *argv, int argc,
1114                subproc_callback_t callback, io_callback_t output_callback, void *context)
1115 {
1116     subproc_t *subproc;
1117     int i, rv;
1118     posix_spawn_file_actions_t actions;
1119     posix_spawnattr_t attrs;
1120 
1121     if (callback == NULL) {
1122         ERROR("ioloop_add_wake_event called with null callback");
1123         return NULL;
1124     }
1125 
1126     if (argc > MAX_SUBPROC_ARGS) {
1127         callback(NULL, 0, "too many subproc args");
1128         return NULL;
1129     }
1130 
1131     subproc = calloc(1, sizeof *subproc);
1132     if (subproc == NULL) {
1133         callback(NULL, 0, "out of memory");
1134         return NULL;
1135     }
1136     RETAIN_HERE(subproc);
1137     if (output_callback != NULL) {
1138         rv = pipe(subproc->pipe_fds);
1139         if (rv < 0) {
1140             callback(NULL, 0, "unable to create pipe.");
1141             RELEASE_HERE(subproc, subproc_finalize);
1142             return NULL;
1143         }
1144         subproc->output_fd = ioloop_file_descriptor_create(subproc->pipe_fds[0], subproc, subproc_output_finalize);
1145         if (subproc->output_fd == NULL) {
1146             callback(NULL, 0, "out of memory.");
1147             close(subproc->pipe_fds[0]);
1148             close(subproc->pipe_fds[1]);
1149             RELEASE_HERE(subproc, subproc_finalize);
1150             return NULL;
1151         }
1152     }
1153 
1154     subproc->argv[0] = strdup(exepath);
1155     if (subproc->argv[0] == NULL) {
1156         RELEASE_HERE(subproc, subproc_finalize);
1157         callback(NULL, 0, "out of memory");
1158         return NULL;
1159     }
1160     subproc->argc++;
1161     for (i = 0; i < argc; i++) {
1162         subproc->argv[i + 1] = strdup(argv[i]);
1163         if (subproc->argv[i + 1] == NULL) {
1164             RELEASE_HERE(subproc, subproc_finalize);
1165             callback(NULL, 0, "out of memory");
1166             return NULL;
1167         }
1168         subproc->argc++;
1169     }
1170 
1171     // Set up for posix_spawn
1172     posix_spawn_file_actions_init(&actions);
1173     if (output_callback != NULL) {
1174         posix_spawn_file_actions_adddup2(&actions, subproc->pipe_fds[1], STDOUT_FILENO);
1175         posix_spawn_file_actions_addclose(&actions, subproc->pipe_fds[0]);
1176         posix_spawn_file_actions_addclose(&actions, subproc->pipe_fds[1]);
1177     }
1178     posix_spawnattr_init(&attrs);
1179     extern char **environ;
1180     rv = posix_spawn(&subproc->pid, exepath, &actions, &attrs, subproc->argv, environ);
1181     posix_spawn_file_actions_destroy(&actions);
1182     posix_spawnattr_destroy(&attrs);
1183     if (rv < 0) {
1184         ERROR("posix_spawn failed for " PUB_S_SRP ": " PUB_S_SRP, subproc->argv[0], strerror(errno));
1185         callback(subproc, 0, strerror(errno));
1186         RELEASE_HERE(subproc, subproc_finalize);
1187         return NULL;
1188     }
1189     subproc->callback = callback;
1190     subproc->context = context;
1191 
1192     subproc->dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, subproc->pid, DISPATCH_PROC_EXIT,
1193                                                       ioloop_main_queue);
1194     if (subproc->dispatch_source == NULL) {
1195         ERROR("dispatch_source_create failed in ioloop_add_wake_event().");
1196         return false;
1197     }
1198     dispatch_retain(subproc->dispatch_source);
1199     dispatch_source_set_event_handler_f(subproc->dispatch_source, subproc_event);
1200     dispatch_source_set_cancel_handler_f(subproc->dispatch_source, subproc_cancel);
1201     dispatch_set_context(subproc->dispatch_source, subproc);
1202     dispatch_activate(subproc->dispatch_source);
1203     RETAIN_HERE(subproc); // Dispatch has a reference
1204 
1205     // Now that we have a viable subprocess, add the reader callback.
1206     if (output_callback != NULL && subproc->output_fd != NULL) {
1207         close(subproc->pipe_fds[1]);
1208         ioloop_add_reader(subproc->output_fd, output_callback);
1209     }
1210     return subproc;
1211 }
1212 
1213 void
ioloop_dnssd_txn_cancel(dnssd_txn_t * txn)1214 ioloop_dnssd_txn_cancel(dnssd_txn_t *txn)
1215 {
1216     if (txn->sdref != NULL) {
1217         DNSServiceRefDeallocate(txn->sdref);
1218         txn->sdref = NULL;
1219     } else {
1220         INFO("ioloop_dnssd_txn_cancel: dead transaction.");
1221     }
1222 }
1223 
1224 static void
dnssd_txn_finalize(dnssd_txn_t * txn)1225 dnssd_txn_finalize(dnssd_txn_t *txn)
1226 {
1227     if (txn->sdref != NULL) {
1228         ioloop_dnssd_txn_cancel(txn);
1229     }
1230     if (txn->finalize_callback) {
1231         txn->finalize_callback(txn->context);
1232     }
1233     free(txn);
1234 }
1235 
1236 void
ioloop_dnssd_txn_retain_(dnssd_txn_t * dnssd_txn,const char * file,int line)1237 ioloop_dnssd_txn_retain_(dnssd_txn_t *dnssd_txn, const char *file, int line)
1238 {
1239     (void)file; (void)line;
1240     RETAIN(dnssd_txn);
1241 }
1242 
1243 void
ioloop_dnssd_txn_release_(dnssd_txn_t * dnssd_txn,const char * file,int line)1244 ioloop_dnssd_txn_release_(dnssd_txn_t *dnssd_txn, const char *file, int line)
1245 {
1246     (void)file; (void)line;
1247     RELEASE(dnssd_txn, dnssd_txn_finalize);
1248 }
1249 
1250 dnssd_txn_t *
ioloop_dnssd_txn_add_(DNSServiceRef ref,void * context,finalize_callback_t finalize_callback,const char * file,int line)1251 ioloop_dnssd_txn_add_(DNSServiceRef ref, void *context, finalize_callback_t finalize_callback, const char *file,
1252                       int line)
1253 {
1254     dnssd_txn_t *txn = calloc(1, sizeof(*txn));
1255     (void)file; (void)line;
1256 
1257     if (txn != NULL) {
1258         RETAIN(txn);
1259         txn->sdref = ref;
1260         txn->context = context;
1261         txn->finalize_callback = finalize_callback;
1262         DNSServiceSetDispatchQueue(ref, ioloop_main_queue);
1263     }
1264     return txn;
1265 }
1266 
1267 void
ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t * NONNULL txn,void * aux_pointer)1268 ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t *NONNULL txn, void *aux_pointer)
1269 {
1270     txn->aux_pointer = aux_pointer;
1271 }
1272 
1273 void *
ioloop_dnssd_txn_get_aux_pointer(dnssd_txn_t * NONNULL txn)1274 ioloop_dnssd_txn_get_aux_pointer(dnssd_txn_t *NONNULL txn)
1275 {
1276     return txn->aux_pointer;
1277 }
1278 
1279 void *
ioloop_dnssd_txn_get_context(dnssd_txn_t * NONNULL txn)1280 ioloop_dnssd_txn_get_context(dnssd_txn_t *NONNULL txn)
1281 {
1282     return txn->context;
1283 }
1284 
1285 static bool
ioloop_xpc_client_is_entitled(xpc_connection_t conn,const char * entitlement_name)1286 ioloop_xpc_client_is_entitled(xpc_connection_t conn, const char *entitlement_name)
1287 {
1288     bool entitled = false;
1289     xpc_object_t entitled_obj = xpc_connection_copy_entitlement_value(conn, entitlement_name);
1290 
1291     if (entitled_obj) {
1292         if (xpc_get_type(entitled_obj) == XPC_TYPE_BOOL && xpc_bool_get_value(entitled_obj)) {
1293             entitled = true;
1294         }
1295         xpc_release(entitled_obj);
1296     } else {
1297         ERROR("ioloop_xpc_client_is_entitled: Client Entitlement is NULL");
1298     }
1299 
1300     if (!entitled) {
1301         ERROR("ioloop_xpc_client_is_entitled: Client is missing Entitlement!");
1302     }
1303 
1304     return entitled;
1305 }
1306 
1307 static void
ioloop_xpc_accept(xpc_connection_t conn,const char * name,ioloop_xpc_callback_t callback)1308 ioloop_xpc_accept(xpc_connection_t conn, const char *name, ioloop_xpc_callback_t callback)
1309 {
1310     struct state {
1311         xpc_connection_t conn;
1312         ioloop_xpc_callback_t callback;
1313     } *state;
1314 
1315     if (conn == NULL) {
1316         ERROR("ioloop_xpc_accept: listener has been canceled.");
1317         return;
1318     }
1319 
1320     state = calloc(1, sizeof(*state));
1321     if (state == NULL) {
1322         ERROR("ioloop_xpc_accept: no memory for xpc connection state.");
1323         return;
1324     }
1325 
1326     int pid = xpc_connection_get_pid(conn);
1327     int uid = xpc_connection_get_euid(conn);
1328 
1329     if (!ioloop_xpc_client_is_entitled(conn, name)) {
1330         ERROR("ioloop_xpc_accept: connection from uid %d pid %d is missing entitlement " PUB_S_SRP ".", uid, pid, name);
1331         xpc_connection_cancel(conn);
1332         free(state);
1333         return;
1334     }
1335 
1336     state->conn = conn;
1337     xpc_retain(conn);
1338     state->callback = callback;
1339     xpc_connection_set_target_queue(conn, ioloop_main_queue);
1340     xpc_connection_set_event_handler(conn, ^(xpc_object_t request) {
1341             xpc_type_t type = xpc_get_type(request);
1342 
1343             if (request == XPC_ERROR_CONNECTION_INVALID) {
1344                 INFO("ioloop_xpc_accept event handler: connection has been finalized.");
1345                 if (state->callback != NULL) {
1346                     state->callback(state->conn, NULL);
1347                 }
1348                 // We are guaranteed that this is the last callback, so we can safely free state.
1349                 if (state->conn != NULL) {
1350                     xpc_release(state->conn);
1351                     state->conn = NULL;
1352                 }
1353                 free(state);
1354             } else if (type == XPC_TYPE_DICTIONARY) {
1355                 // If the callback returns false, that means that we're done.
1356                 if (state->callback != NULL) {
1357                     if (!state->callback(state->conn, request)) {
1358                         INFO("ioloop_xpc_accept event handler: callback indicated done.");
1359                         xpc_connection_cancel(state->conn);
1360                         state->callback = NULL;
1361                     } else {
1362                         INFO("ioloop_xpc_accept event handler: continuing.");
1363                     }
1364                 }
1365             } else {
1366                 INFO("ioloop_xpc_accept event handler: client went away.");
1367                 // Passing a null request to the callback means the client went away.
1368                 xpc_connection_cancel(state->conn);
1369                 if (state->callback != NULL) {
1370                     callback(state->conn, NULL);
1371                 }
1372                 state->callback = NULL;
1373             }
1374         });
1375     xpc_connection_resume(conn);
1376 }
1377 
1378 xpc_connection_t
ioloop_create_xpc_service(const char * name,ioloop_xpc_callback_t callback)1379 ioloop_create_xpc_service(const char *name, ioloop_xpc_callback_t callback)
1380 {
1381     xpc_connection_t listener = xpc_connection_create_mach_service(name, ioloop_main_queue,
1382                                                                    XPC_CONNECTION_MACH_SERVICE_LISTENER);
1383     if (listener == NULL || xpc_get_type(listener) != XPC_TYPE_CONNECTION) {
1384         ERROR("ioloop_create_xpc_service: " PUB_S_SRP ": unable to create listener %p", name, listener);
1385         if (listener != NULL) {
1386             xpc_release(listener);
1387         }
1388         return NULL;
1389     }
1390 
1391     xpc_connection_set_event_handler(listener, ^(xpc_object_t eventmsg) {
1392             xpc_type_t type = xpc_get_type(eventmsg);
1393 
1394             if (type == XPC_TYPE_CONNECTION) {
1395                 INFO("ioloop_create_xpc_service: New " PUB_S_SRP " Client %p", name, eventmsg);
1396                 ioloop_xpc_accept((xpc_connection_t)eventmsg, name, callback);
1397             }
1398             else if (type == XPC_TYPE_ERROR) // Ideally, we would never hit these cases
1399             {
1400                 ERROR("ioloop_create_xpc_service: XPCError: " PUB_S_SRP,
1401                       xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION));
1402                 callback(NULL, NULL);
1403             }
1404             else
1405             {
1406                 INFO("ioloop_create_xpc_service: Unknown EventMsg type");
1407             }
1408         });
1409     xpc_connection_resume(listener);
1410     return listener;
1411 }
1412 
1413 static void
file_descriptor_finalize(void * context)1414 file_descriptor_finalize(void *context)
1415 {
1416     io_t *file_descriptor = context;
1417     if (file_descriptor->ref_count == 0) {
1418         if (file_descriptor->finalize) {
1419             file_descriptor->finalize(file_descriptor->context);
1420         }
1421         free(file_descriptor);
1422     }
1423 }
1424 
1425 void
ioloop_file_descriptor_retain_(io_t * file_descriptor,const char * file,int line)1426 ioloop_file_descriptor_retain_(io_t *file_descriptor, const char *file, int line)
1427 {
1428     (void)file; (void)line;
1429     RETAIN(file_descriptor);
1430 }
1431 
1432 void
ioloop_file_descriptor_release_(io_t * file_descriptor,const char * file,int line)1433 ioloop_file_descriptor_release_(io_t *file_descriptor, const char *file, int line)
1434 {
1435     (void)file; (void)line;
1436     RELEASE(file_descriptor, file_descriptor_finalize);
1437 }
1438 
1439 io_t *
ioloop_file_descriptor_create_(int fd,void * context,finalize_callback_t finalize,const char * file,int line)1440 ioloop_file_descriptor_create_(int fd, void *context, finalize_callback_t finalize, const char *file, int line)
1441 {
1442     io_t *ret;
1443     ret = calloc(1, sizeof(*ret));
1444     if (ret) {
1445         ret->fd = fd;
1446         ret->context = context;
1447         ret->finalize = finalize;
1448         RETAIN(ret);
1449     }
1450     return ret;
1451 }
1452 
1453 static void
ioloop_read_cancel(void * context)1454 ioloop_read_cancel(void *context)
1455 {
1456     io_t *io = context;
1457 
1458     if (io->read_source != NULL) {
1459         dispatch_release(io->read_source);
1460         io->read_source = NULL;
1461         // Release the reference count that dispatch was holding.
1462         RELEASE_HERE(io, file_descriptor_finalize);
1463     }
1464 }
1465 
1466 static void
ioloop_read_event(void * context)1467 ioloop_read_event(void *context)
1468 {
1469     io_t *io = context;
1470 
1471     if (io->read_callback != NULL) {
1472         io->read_callback(io, io->context);
1473     }
1474 }
1475 
1476 void
ioloop_close(io_t * io)1477 ioloop_close(io_t *io)
1478 {
1479     if (io->read_source != NULL) {
1480         dispatch_cancel(io->read_source);
1481     }
1482     if (io->write_source != NULL) {
1483         dispatch_cancel(io->write_source);
1484     }
1485     io->fd = -1;
1486 }
1487 
1488 void
ioloop_add_reader(io_t * NONNULL io,io_callback_t NONNULL callback)1489 ioloop_add_reader(io_t *NONNULL io, io_callback_t NONNULL callback)
1490 {
1491     io->read_callback = callback;
1492     if (io->read_source == NULL) {
1493         io->read_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, io->fd, 0, ioloop_main_queue);
1494     }
1495     if (io->read_source == NULL) {
1496         ERROR("dispatch_source_create: unable to create read dispatch source.");
1497         return;
1498     }
1499     dispatch_source_set_event_handler_f(io->read_source, ioloop_read_event);
1500     dispatch_source_set_cancel_handler_f(io->read_source, ioloop_read_cancel);
1501     dispatch_set_context(io->read_source, io);
1502     RETAIN_HERE(io); // Dispatch will hold a reference.
1503     dispatch_resume(io->read_source);
1504 }
1505 
1506 // Local Variables:
1507 // mode: C
1508 // tab-width: 4
1509 // c-file-style: "bsd"
1510 // c-basic-offset: 4
1511 // fill-column: 108
1512 // indent-tabs-mode: nil
1513 // End:
1514