/ lib / ipc / client.c
client.c
  1  /*
  2   * Copyright (c) 2009 Kungliga Tekniska H�gskolan
  3   * (Royal Institute of Technology, Stockholm, Sweden).
  4   * All rights reserved.
  5   *
  6   * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
  7   *
  8   * Redistribution and use in source and binary forms, with or without
  9   * modification, are permitted provided that the following conditions
 10   * are met:
 11   *
 12   * 1. Redistributions of source code must retain the above copyright
 13   *    notice, this list of conditions and the following disclaimer.
 14   *
 15   * 2. Redistributions in binary form must reproduce the above copyright
 16   *    notice, this list of conditions and the following disclaimer in the
 17   *    documentation and/or other materials provided with the distribution.
 18   *
 19   * 3. Neither the name of the Institute nor the names of its contributors
 20   *    may be used to endorse or promote products derived from this software
 21   *    without specific prior written permission.
 22   *
 23   * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 24   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 25   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 26   * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 27   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 28   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 29   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 30   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 31   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 32   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 33   * SUCH DAMAGE.
 34   */
 35  
 36  #include "hi_locl.h"
 37  
 38  #if defined(__APPLE__) && defined(HAVE_GCD)
 39  
 40  #include "heim_ipc.h"
 41  #include "heim_ipc_asyncServer.h"
 42  
 43  #include <dispatch/dispatch.h>
 44  
 45  #include <mach/mach.h>
 46  #include <servers/bootstrap.h>
 47  #ifdef __APPLE_PRIVATE__
 48  #include <bootstrap_priv.h>
 49  #endif
 50  
 51  static dispatch_once_t jobqinited = 0;
 52  static dispatch_queue_t jobq = NULL;
 53  static dispatch_queue_t syncq;
 54  
 55  struct mach_ctx {
 56      mach_port_t server;
 57      char *name;
 58  };
 59  
 60  static int
 61  mach_release(void *ctx);
 62  
 63  static kern_return_t
 64  look_up(const char *service, mach_port_t *nport)
 65  {
 66  #ifdef __APPLE_PRIVATE__
 67      return bootstrap_look_up2(bootstrap_port, service, nport, 0, BOOTSTRAP_PRIVILEGED_SERVER);
 68  #else
 69      return bootstrap_look_up(bootstrap_port, service, nport);
 70  #endif
 71  }
 72  
 73  
 74  static int
 75  mach_init(const char *service, void **ctx)
 76  {
 77      struct mach_ctx *ipc;
 78      mach_port_t sport;
 79      int ret;
 80  
 81      dispatch_once(&jobqinited, ^{
 82  	    jobq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 83  	    syncq = dispatch_queue_create("heim-ipc-syncq", NULL);
 84  	});
 85  
 86      ret = look_up(service, &sport);
 87      if (ret)
 88  	return ret;
 89  
 90      ipc = malloc(sizeof(*ipc));
 91      if (ipc == NULL) {
 92  	mach_port_mod_refs(mach_task_self(), sport, MACH_PORT_RIGHT_SEND, -1);
 93  	return ENOMEM;
 94      }
 95  
 96      ipc->server = sport;
 97      ipc->name = strdup(service);
 98      if (ipc->name == NULL) {
 99  	mach_release(ipc);
100  	return ENOMEM;
101      }
102  
103      *ctx = ipc;
104  
105      return 0;
106  }
107  
108  static int
109  mach_ipc(void *ctx,
110  	 const heim_idata *request, heim_idata *response,
111  	 heim_icred *cred)
112  {
113      struct mach_ctx *ipc = ctx;
114      heim_ipc_message_inband_t requestin;
115      mach_msg_type_number_t requestin_length = 0;
116      heim_ipc_message_outband_t requestout = NULL;
117      mach_msg_type_number_t requestout_length = 0;
118      heim_ipc_message_inband_t replyin;
119      mach_msg_type_number_t replyin_length = 0;
120      heim_ipc_message_outband_t replyout = 0;
121      mach_msg_type_number_t replyout_length = 0;
122      int ret, errorcode = -1, retries = 0;
123  
124      if (request->length < sizeof(requestin)) {
125  	memcpy(requestin, request->data, request->length);
126  	requestin_length = (mach_msg_type_number_t)request->length;
127      } else {
128  	ret = vm_read(mach_task_self(), 
129  		      (vm_address_t)request->data, request->length, 
130  		      (vm_address_t *)&requestout, &requestout_length);
131  	if (ret)
132  	    return ENOMEM;
133      }
134  
135      while (retries < 2) {
136  	__block mach_port_t sport;
137  
138  	dispatch_sync(syncq, ^{ sport = ipc->server; });
139  
140  	ret = mheim_ipc_call(sport,
141  			     requestin, requestin_length,
142  			     requestout, requestout_length,
143  			     &errorcode,
144  			     replyin, &replyin_length,
145  			     &replyout, &replyout_length);
146  	if (ret == MACH_SEND_INVALID_DEST || ret == MIG_SERVER_DIED) {
147  	    mach_port_t nport;
148  	    /* race other threads to get a new port */
149  	    ret = look_up(ipc->name, &nport);
150  	    if (ret)
151  		return ret;
152  	    dispatch_sync(syncq, ^{
153  		    /* check if we lost the race to lookup the port */
154  		    if (sport != ipc->server) {
155  			mach_port_deallocate(mach_task_self(), nport);
156  		    } else {
157  			mach_port_deallocate(mach_task_self(), ipc->server);
158  			ipc->server = nport;
159  		    }
160  		});
161  	    retries++;
162  	} else if (ret) {
163  	    return ret;
164  	} else
165  	    break;
166      }
167      if (retries >= 2)
168  	return EINVAL;
169  
170      if (errorcode) {
171  	if (replyout_length)
172  	    vm_deallocate (mach_task_self (), (vm_address_t) replyout,
173  			   replyout_length);
174  	return errorcode;
175      }
176  
177      if (replyout_length) {
178  	response->data = malloc(replyout_length);
179  	if (response->data == NULL) {
180  	    vm_deallocate (mach_task_self (), (vm_address_t) replyout,
181  			   replyout_length);
182  	    return ENOMEM;
183  	}
184  	memcpy(response->data, replyout, replyout_length);
185  	response->length = replyout_length;
186  	vm_deallocate (mach_task_self (), (vm_address_t) replyout,
187  		       replyout_length);
188      } else {
189  	response->data = malloc(replyin_length);
190  	if (response->data == NULL)
191  	    return ENOMEM;
192  	memcpy(response->data, replyin, replyin_length);
193  	response->length = replyin_length;
194      }
195  
196      return 0;
197  }
198  
199  struct async_client {
200      mach_port_t mp;
201      dispatch_source_t source;
202      dispatch_queue_t queue;
203      void (*func)(void *, int, heim_idata *, heim_icred);
204      void *userctx;
205  };
206  
207  kern_return_t
208  mheim_ado_acall_reply(mach_port_t server_port,
209  		      audit_token_t client_creds,
210  		      int returnvalue,
211  		      heim_ipc_message_inband_t replyin,
212  		      mach_msg_type_number_t replyinCnt,
213  		      heim_ipc_message_outband_t replyout,
214  		      mach_msg_type_number_t replyoutCnt)
215  {
216      struct async_client *c = dispatch_get_specific((void *)mheim_ado_acall_reply);
217      heim_idata response;
218  
219      if (returnvalue) {
220  	response.data = NULL;
221  	response.length = 0;
222      } else if (replyoutCnt) {
223  	response.data = replyout;
224  	response.length = replyoutCnt;
225      } else {
226  	response.data = replyin;
227  	response.length = replyinCnt;
228      }
229  
230      (*c->func)(c->userctx, returnvalue, &response, NULL);
231  
232      if (replyoutCnt)
233  	vm_deallocate (mach_task_self (), (vm_address_t) replyout, replyoutCnt);
234  
235      dispatch_source_cancel(c->source);
236  
237      return 0;
238  
239  
240  }
241  
242  
243  static int
244  mach_async(void *ctx, const heim_idata *request, void *userctx,
245  	   void (*func)(void *, int, heim_idata *, heim_icred))
246  {
247      struct mach_ctx *ipc = ctx;
248      heim_ipc_message_inband_t requestin;
249      mach_msg_type_number_t requestin_length = 0;
250      heim_ipc_message_outband_t requestout = NULL;
251      mach_msg_type_number_t requestout_length = 0;
252      int ret, retries = 0;
253      kern_return_t kr;
254      struct async_client *c;
255  
256      /* first create the service that will catch the reply from the server */
257      /* XXX these object should be cached and reused */
258  
259      c = malloc(sizeof(*c));
260      if (c == NULL)
261  	return ENOMEM;
262  
263      kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &c->mp);
264      if (kr != KERN_SUCCESS) {
265  	free(c);
266  	return EINVAL;
267      }
268      
269      c->queue = dispatch_queue_create("heim-ipc-async-client", NULL);
270      c->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, c->mp, 0, c->queue);
271      dispatch_queue_set_specific(c->queue, (void *)mheim_ado_acall_reply, c, NULL);
272  
273      dispatch_source_set_event_handler(c->source, ^{
274  	    dispatch_mig_server(c->source,
275  				sizeof(union __RequestUnion__mheim_ado_mheim_aipc_subsystem),
276  				mheim_aipc_server);
277  	});
278  
279      dispatch_source_set_cancel_handler(c->source, ^{
280  	    mach_port_mod_refs(mach_task_self(), c->mp,
281  			       MACH_PORT_RIGHT_RECEIVE, -1);
282  	    dispatch_release(c->queue);
283  	    dispatch_release(c->source);
284  	    free(c);
285  	});
286  
287      c->func = func;
288      c->userctx = userctx;
289  
290      dispatch_resume(c->source);
291  
292      /* ok, send the message */
293      if (request->length < sizeof(requestin)) {
294  	memcpy(requestin, request->data, request->length);
295  	requestin_length = (mach_msg_type_number_t)request->length;
296      } else {
297  	ret = vm_read(mach_task_self(), 
298  		      (vm_address_t)request->data, request->length, 
299  		      (vm_address_t *)&requestout, &requestout_length);
300  	if (ret)
301  	    return ENOMEM;
302      }
303  
304      while (retries < 2) {
305  	__block mach_port_t sport;
306  
307  	dispatch_sync(syncq, ^{ sport = ipc->server; });
308  
309  	ret = mheim_ipc_call_request(sport, c->mp,
310  				     requestin, requestin_length,
311  				     requestout, requestout_length);
312  	if (ret == MACH_SEND_INVALID_DEST) {
313  	    ret = look_up(ipc->name, &sport);
314  	    if (ret) {
315  		dispatch_source_cancel(c->source);
316  		return ret;
317  	    }
318  	    mach_port_deallocate(mach_task_self(), ipc->server);
319  	    ipc->server = sport;
320  	    retries++;
321  	} else if (ret) {
322  	    dispatch_source_cancel(c->source);
323  	    return ret;
324  	} else
325  	    break;
326      }
327      if (retries >= 2) {
328  	dispatch_source_cancel(c->source);
329  	return EINVAL;
330      }
331  
332      return 0;
333  }
334  
335  static int
336  mach_release(void *ctx)
337  {
338      struct mach_ctx *ipc = ctx;
339      if (ipc->server != MACH_PORT_NULL)
340  	mach_port_deallocate(mach_task_self(), ipc->server);
341      free(ipc->name);
342      free(ipc);
343      return 0;
344  }
345  
346  #endif
347  
348  struct path_ctx {
349      char *path;
350      int fd;
351  };
352  
353  static int common_release(void *);
354  
355  static int
356  connect_unix(struct path_ctx *s)
357  {
358      struct sockaddr_un addr;
359  
360      addr.sun_family = AF_UNIX;
361      strlcpy(addr.sun_path, s->path, sizeof(addr.sun_path));
362  
363      s->fd = socket(AF_UNIX, SOCK_STREAM, 0);
364      if (s->fd < 0)
365  	return errno;
366      rk_cloexec(s->fd);
367      socket_set_nopipe(s->fd, 1);
368  
369      if (connect(s->fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
370  	close(s->fd);
371  	s->fd = -1;
372  	return errno;
373      }
374  
375      return 0;
376  }
377  
378  static int
379  common_path_init(const char *service,
380  		 const char *file,
381  		 void **ctx)
382  {
383      struct path_ctx *s;
384  
385      s = malloc(sizeof(*s));
386      if (s == NULL)
387  	return ENOMEM;
388      s->fd = -1;
389  
390      asprintf(&s->path, "/var/run/.heim_%s-%s", service, file);
391  
392      *ctx = s;
393  
394      return 0;
395  }
396  
397  static int
398  unix_socket_init(const char *service,
399  		 void **ctx)
400  {
401      int ret;
402  
403      ret = common_path_init(service, "socket", ctx);
404      if (ret)
405  	return ret;
406      ret = connect_unix(*ctx);
407      if (ret)
408  	common_release(*ctx);
409  
410      return ret;
411  }
412  
413  static int
414  unix_socket_ipc(void *ctx,
415  		const heim_idata *req, heim_idata *rep,
416  		heim_icred *cred)
417  {
418      struct path_ctx *s = ctx;
419      uint32_t len = htonl((uint32_t)req->length);
420      uint32_t rv;
421      int retval;
422  
423      if (cred)
424  	*cred = NULL;
425  
426      rep->data = NULL;
427      rep->length = 0;
428  
429      if (net_write(s->fd, &len, sizeof(len)) != sizeof(len))
430  	return -1;
431      if (net_write(s->fd, req->data, req->length) != (ssize_t)req->length)
432  	return -1;
433  
434      if (net_read(s->fd, &len, sizeof(len)) != sizeof(len))
435  	return -1;
436      if (net_read(s->fd, &rv, sizeof(rv)) != sizeof(rv))
437  	return -1;
438      retval = ntohl(rv);
439  
440      rep->length = ntohl(len);
441      if (rep->length > MAX_PACKET_SIZE) {
442  	rep->length = 0;
443  	return EINVAL;
444      } else {
445  	rep->data = malloc(rep->length);
446  	if (rep->data == NULL)
447  	    return -1;
448  	if (net_read(s->fd, rep->data, rep->length) != (ssize_t)rep->length)
449  	    return -1;
450      }
451  
452      return retval;
453  }
454  
455  int
456  common_release(void *ctx)
457  {
458      struct path_ctx *s = ctx;
459      if (s->fd >= 0)
460  	close(s->fd);
461      free(s->path);
462      free(s);
463      return 0;
464  }
465  
466  #ifdef HAVE_DOOR
467  
468  static int
469  door_init(const char *service,
470  	  void **ctx)
471  {
472      ret = common_path_init(context, service, "door", ctx);
473      if (ret)
474  	return ret;
475      ret = connect_door(*ctx);
476      if (ret)
477  	common_release(*ctx);
478      return ret;
479  }
480  
481  static int
482  door_ipc(void *ctx,
483  	 const heim_idata *request, heim_idata *response,
484  	 heim_icred *cred)
485  {
486      door_arg_t arg;
487      int ret;
488  
489      arg.data_ptr = request->data;
490      arg.data_size = request->length;
491      arg.desc_ptr = NULL;
492      arg.desc_num = 0;
493      arg.rbuf = NULL;
494      arg.rsize = 0;
495  
496      ret = door_call(fd, &arg);
497      close(fd);
498      if (ret != 0)
499  	return errno;
500  
501      response->data = malloc(arg.rsize);
502      if (response->data == NULL) {
503  	munmap(arg.rbuf, arg.rsize);
504  	return ENOMEM;
505      }
506      memcpy(response->data, arg.rbuf, arg.rsize);
507      response->length = arg.rsize;
508      munmap(arg.rbuf, arg.rsize);
509  
510      return ret;
511  }
512  
513  #endif
514  
515  struct hipc_ops {
516      const char *prefix;
517      int (*init)(const char *, void **);
518      int (*release)(void *);
519      int (*ipc)(void *,const heim_idata *, heim_idata *, heim_icred *);
520      int (*async)(void *, const heim_idata *, void *,
521  		 void (*)(void *, int, heim_idata *, heim_icred));
522  };
523  
524  struct hipc_ops ipcs[] = {
525  #if defined(__APPLE__) && defined(HAVE_GCD)
526      { "MACH", mach_init, mach_release, mach_ipc, mach_async },
527  #endif
528  #ifdef HAVE_DOOR
529      { "DOOR", door_init, common_release, door_ipc, NULL }
530  #endif
531      { "UNIX", unix_socket_init, common_release, unix_socket_ipc, NULL }
532  };
533  
534  struct heim_ipc {
535      struct hipc_ops *ops;
536      void *ctx;
537  };
538  
539  
540  int
541  heim_ipc_init_context(const char *name, heim_ipc *ctx)
542  {
543      unsigned int i;
544      int ret, any = 0;
545  
546      for(i = 0; i < sizeof(ipcs)/sizeof(ipcs[0]); i++) {
547  	size_t prefix_len = strlen(ipcs[i].prefix);
548  	heim_ipc c;
549  	if(strncmp(ipcs[i].prefix, name, prefix_len) == 0
550  	   && name[prefix_len] == ':')  {
551  	} else if (strncmp("ANY:", name, 4) == 0) {
552  	    prefix_len = 3;
553  	    any = 1;
554  	} else
555  	    continue;
556  
557  	c = calloc(1, sizeof(*c));
558  	if (c == NULL)
559  	    return ENOMEM;
560  
561  	c->ops = &ipcs[i];
562  
563  	ret = (c->ops->init)(name + prefix_len + 1, &c->ctx);
564  	if (ret) {
565  	    free(c);
566  	    if (any)
567  		continue;
568  	    return ret;
569  	}
570  
571  	*ctx = c;
572  	return 0;
573      }
574  
575      return ENOENT;
576  }
577  
578  void
579  heim_ipc_free_context(heim_ipc ctx)
580  {
581      (ctx->ops->release)(ctx->ctx);
582      free(ctx);
583  }
584  
585  int
586  heim_ipc_call(heim_ipc ctx, const heim_idata *snd, heim_idata *rcv,
587  	      heim_icred *cred)
588  {
589      if (cred)
590  	*cred = NULL;
591      return (ctx->ops->ipc)(ctx->ctx, snd, rcv, cred);
592  }
593  
594  int
595  heim_ipc_async(heim_ipc ctx, const heim_idata *snd, void *userctx,
596  	       void (*func)(void *, int, heim_idata *, heim_icred))
597  {
598      if (ctx->ops->async == NULL) {
599  	heim_idata rcv;
600  	heim_icred cred = NULL;
601  	int ret;
602  
603  	ret = (ctx->ops->ipc)(ctx->ctx, snd, &rcv, &cred);
604  	(*func)(userctx, ret, &rcv, cred);
605  	heim_ipc_free_cred(cred);
606  	free(rcv.data);
607  	return ret;
608      } else {
609  	return (ctx->ops->async)(ctx->ctx, snd, userctx, func);
610      }
611  }