/* * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights * Reserved. This file contains Original Code and/or Modifications of * Original Code as defined in and that are subject to the Apple Public * Source License Version 1.1 (the "License"). You may not use this file * except in compliance with the License. Please obtain a copy of the * License at http://www.apple.com/publicsource and read it before using * this file. * * The Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Services file lookup * Copyright (C) 1989 by NeXT, Inc. */ #include #include #include #include #include #include #include #include #include #include "_lu_types.h" #include "lookup.h" #include "lu_utils.h" #define SERVICE_CACHE_SIZE 10 #define DEFAULT_SERVICE_CACHE_TTL 10 static pthread_mutex_t _service_cache_lock = PTHREAD_MUTEX_INITIALIZER; static void *_service_cache[SERVICE_CACHE_SIZE] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static unsigned int _service_cache_best_before[SERVICE_CACHE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static unsigned int _service_cache_index = 0; static unsigned int _service_cache_ttl = DEFAULT_SERVICE_CACHE_TTL; static pthread_mutex_t _service_lock = PTHREAD_MUTEX_INITIALIZER; #define S_GET_NAME 1 #define S_GET_PORT 2 #define S_GET_ENT 3 extern struct servent *_old_getservbyport(); extern struct servent *_old_getservbyname(); extern struct servent *_old_getservent(); extern void _old_setservent(); extern void _old_endservent(); extern void _old_setservfile(); static void free_service_data(struct servent *s) { char **aliases; if (s == NULL) return; if (s->s_name != NULL) free(s->s_name); if (s->s_proto != NULL) free(s->s_proto); aliases = s->s_aliases; if (aliases != NULL) { while (*aliases != NULL) free(*aliases++); free(s->s_aliases); } } static void free_service(struct servent *s) { if (s == NULL) return; free_service_data(s); free(s); } static void free_lu_thread_info_service(void *x) { struct lu_thread_info *tdata; if (x == NULL) return; tdata = (struct lu_thread_info *)x; if (tdata->lu_entry != NULL) { free_service((struct servent *)tdata->lu_entry); tdata->lu_entry = NULL; } _lu_data_free_vm_xdr(tdata); free(tdata); } static struct servent * extract_service(XDR *xdr, const char *proto) { struct servent *s; int i, j, nvals, nkeys, status; char *key, **vals; if (xdr == NULL) return NULL; if (!xdr_int(xdr, &nkeys)) return NULL; s = (struct servent *)calloc(1, sizeof(struct servent)); for (i = 0; i < nkeys; i++) { key = NULL; vals = NULL; nvals = 0; status = _lu_xdr_attribute(xdr, &key, &vals, &nvals); if (status < 0) { free_service(s); return NULL; } if (nvals == 0) { free(key); continue; } j = 0; if ((s->s_name == NULL) && (!strcmp("name", key))) { s->s_name = vals[0]; if (nvals > 1) { s->s_aliases = (char **)calloc(nvals, sizeof(char *)); for (j = 1; j < nvals; j++) s->s_aliases[j-1] = vals[j]; } j = nvals; } else if ((s->s_proto == NULL) && (!strcmp("protocol", key))) { if ((proto == NULL) || (proto[0] == '\0')) { s->s_proto = vals[0]; j = 1; } else { s->s_proto = strdup(proto); } } else if ((s->s_port == 0) && (!strcmp("port", key))) { s->s_port = htons(atoi(vals[0])); } free(key); if (vals != NULL) { for (; j < nvals; j++) free(vals[j]); free(vals); } } if (s->s_name == NULL) s->s_name = strdup(""); if (s->s_proto == NULL) s->s_proto = strdup(""); if (s->s_aliases == NULL) s->s_aliases = (char **)calloc(1, sizeof(char *)); return s; } static struct servent * copy_service(struct servent *in) { int i, len; struct servent *s; if (in == NULL) return NULL; s = (struct servent *)calloc(1, sizeof(struct servent)); s->s_name = LU_COPY_STRING(in->s_name); len = 0; if (in->s_aliases != NULL) { for (len = 0; in->s_aliases[len] != NULL; len++); } s->s_aliases = (char **)calloc(len + 1, sizeof(char *)); for (i = 0; i < len; i++) { s->s_aliases[i] = strdup(in->s_aliases[i]); } s->s_proto = LU_COPY_STRING(in->s_proto); s->s_port = in->s_port; return s; } static void recycle_service(struct lu_thread_info *tdata, struct servent *in) { struct servent *s; if (tdata == NULL) return; s = (struct servent *)tdata->lu_entry; if (in == NULL) { free_service(s); tdata->lu_entry = NULL; } if (tdata->lu_entry == NULL) { tdata->lu_entry = in; return; } free_service_data(s); s->s_name = in->s_name; s->s_aliases = in->s_aliases; s->s_proto = in->s_proto; s->s_port = in->s_port; free(in); } __private_extern__ unsigned int get_service_cache_ttl() { return _service_cache_ttl; } __private_extern__ void set_service_cache_ttl(unsigned int ttl) { int i; pthread_mutex_lock(&_service_cache_lock); _service_cache_ttl = ttl; if (ttl == 0) { for (i = 0; i < SERVICE_CACHE_SIZE; i++) { if (_service_cache[i] == NULL) continue; free_service((struct servent *)_service_cache[i]); _service_cache[i] = NULL; _service_cache_best_before[i] = 0; } } pthread_mutex_unlock(&_service_cache_lock); } static void cache_service(struct servent *s) { struct timeval now; struct servent *scache; if (_service_cache_ttl == 0) return; if (s == NULL) return; pthread_mutex_lock(&_service_cache_lock); scache = copy_service(s); gettimeofday(&now, NULL); if (_service_cache[_service_cache_index] != NULL) free_service((struct servent *)_service_cache[_service_cache_index]); _service_cache[_service_cache_index] = scache; _service_cache_best_before[_service_cache_index] = now.tv_sec + _service_cache_ttl; _service_cache_index = (_service_cache_index + 1) % SERVICE_CACHE_SIZE; pthread_mutex_unlock(&_service_cache_lock); } static struct servent * cache_getservbyname(const char *name, const char *proto) { int i; struct servent *s, *res; struct timeval now; char **aliases; if (_service_cache_ttl == 0) return NULL; if (name == NULL) return NULL; pthread_mutex_lock(&_service_cache_lock); gettimeofday(&now, NULL); for (i = 0; i < SERVICE_CACHE_SIZE; i++) { if (_service_cache_best_before[i] == 0) continue; if ((unsigned int)now.tv_sec > _service_cache_best_before[i]) continue; s = (struct servent *)_service_cache[i]; if (s->s_name != NULL) { if (!strcmp(name, s->s_name)) { if ((proto == NULL) || ((s->s_proto != NULL) && (!strcmp(proto, s->s_proto)))) { res = copy_service(s); pthread_mutex_unlock(&_service_cache_lock); return res; } } } aliases = s->s_aliases; if (aliases == NULL) { pthread_mutex_unlock(&_service_cache_lock); return NULL; } for (; *aliases != NULL; *aliases++) { if (!strcmp(name, *aliases)) { if ((proto == NULL) || ((s->s_proto != NULL) && (!strcmp(proto, s->s_proto)))) { res = copy_service(s); pthread_mutex_unlock(&_service_cache_lock); return res; } } } } pthread_mutex_unlock(&_service_cache_lock); return NULL; } static struct servent * cache_getservbyport(int port, const char *proto) { int i; struct servent *s, *res; struct timeval now; if (_service_cache_ttl == 0) return NULL; pthread_mutex_lock(&_service_cache_lock); gettimeofday(&now, NULL); for (i = 0; i < SERVICE_CACHE_SIZE; i++) { if (_service_cache_best_before[i] == 0) continue; if ((unsigned int)now.tv_sec > _service_cache_best_before[i]) continue; s = (struct servent *)_service_cache[i]; if (port == s->s_port) { if ((proto == NULL) || ((s->s_proto != NULL) && (!strcmp(proto, s->s_proto)))) { res = copy_service(s); pthread_mutex_unlock(&_service_cache_lock); return res; } } } pthread_mutex_unlock(&_service_cache_lock); return NULL; } static struct servent * lu_getservbyport(int port, const char *proto) { struct servent *s; unsigned int datalen; XDR outxdr, inxdr; static int proc = -1; char output_buf[_LU_MAXLUSTRLEN + 3 * BYTES_PER_XDR_UNIT]; char *lookup_buf; int count; if (proc < 0) { if (_lookup_link(_lu_port, "getservbyport", &proc) != KERN_SUCCESS) { return NULL; } } /* Encode NULL for xmission to lookupd. */ if (proto == NULL) proto = ""; xdrmem_create(&outxdr, output_buf, sizeof(output_buf), XDR_ENCODE); if (!xdr_int(&outxdr, &port) || !xdr__lu_string(&outxdr, (_lu_string *)&proto)) { xdr_destroy(&outxdr); return NULL; } datalen = 0; lookup_buf = NULL; if (_lookup_all(_lu_port, proc, (unit *)output_buf, xdr_getpos(&outxdr) / BYTES_PER_XDR_UNIT, &lookup_buf, &datalen) != KERN_SUCCESS) { xdr_destroy(&outxdr); return NULL; } xdr_destroy(&outxdr); datalen *= BYTES_PER_XDR_UNIT; if ((lookup_buf == NULL) || (datalen == 0)) return NULL; xdrmem_create(&inxdr, lookup_buf, datalen, XDR_DECODE); count = 0; if (!xdr_int(&inxdr, &count)) { xdr_destroy(&inxdr); vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen); return NULL; } if (count == 0) { xdr_destroy(&inxdr); vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen); return NULL; } /* * lookupd will only send back a reply for a service with the protocol specified * if it finds a match. We pass the protocol name to extract_service, which * copies the requested protocol name into the returned servent. This is a * bit of a kludge, but since NetInfo / lookupd treat services as single entities * with multiple protocols, we are forced to do some special-case handling. */ s = extract_service(&inxdr, proto); xdr_destroy(&inxdr); vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen); return s; } static struct servent * lu_getservbyname(const char *name, const char *proto) { struct servent *s; unsigned int datalen; char *lookup_buf; char output_buf[2 * (_LU_MAXLUSTRLEN + BYTES_PER_XDR_UNIT)]; XDR outxdr, inxdr; static int proc = -1; int count; if (proc < 0) { if (_lookup_link(_lu_port, "getservbyname", &proc) != KERN_SUCCESS) { return NULL; } } /* Encode NULL for xmission to lookupd. */ if (proto == NULL) proto = ""; xdrmem_create(&outxdr, output_buf, sizeof(output_buf), XDR_ENCODE); if (!xdr__lu_string(&outxdr, (_lu_string *)&name) || !xdr__lu_string(&outxdr, (_lu_string *)&proto)) { xdr_destroy(&outxdr); return NULL; } datalen = 0; lookup_buf = NULL; if (_lookup_all(_lu_port, proc, (unit *)output_buf, xdr_getpos(&outxdr) / BYTES_PER_XDR_UNIT, &lookup_buf, &datalen) != KERN_SUCCESS) { xdr_destroy(&outxdr); return NULL; } xdr_destroy(&outxdr); datalen *= BYTES_PER_XDR_UNIT; if ((lookup_buf == NULL) || (datalen == 0)) return NULL; xdrmem_create(&inxdr, lookup_buf, datalen, XDR_DECODE); count = 0; if (!xdr_int(&inxdr, &count)) { xdr_destroy(&inxdr); vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen); return NULL; } if (count == 0) { xdr_destroy(&inxdr); vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen); return NULL; } /* * lookupd will only send back a reply for a service with the protocol specified * if it finds a match. We pass the protocol name to extract_service, which * copies the requested protocol name into the returned servent. This is a * bit of a kludge, but since NetInfo / lookupd treat services as single entities * with multiple protocols, we are forced to do some special-case handling. */ s = extract_service(&inxdr, proto); xdr_destroy(&inxdr); vm_deallocate(mach_task_self(), (vm_address_t)lookup_buf, datalen); return s; } static void lu_endservent() { struct lu_thread_info *tdata; tdata = _lu_data_create_key(_lu_data_key_service, free_lu_thread_info_service); _lu_data_free_vm_xdr(tdata); } static void lu_setservent() { lu_endservent(); } static struct servent * lu_getservent() { struct servent *s; static int proc = -1; struct lu_thread_info *tdata; tdata = _lu_data_create_key(_lu_data_key_service, free_lu_thread_info_service); if (tdata == NULL) { tdata = (struct lu_thread_info *)calloc(1, sizeof(struct lu_thread_info)); _lu_data_set_key(_lu_data_key_service, tdata); } if (tdata->lu_vm == NULL) { if (proc < 0) { if (_lookup_link(_lu_port, "getservent", &proc) != KERN_SUCCESS) { lu_endservent(); return NULL; } } if (_lookup_all(_lu_port, proc, NULL, 0, &(tdata->lu_vm), &(tdata->lu_vm_length)) != KERN_SUCCESS) { lu_endservent(); return NULL; } /* mig stubs measure size in words (4 bytes) */ tdata->lu_vm_length *= 4; if (tdata->lu_xdr != NULL) { xdr_destroy(tdata->lu_xdr); free(tdata->lu_xdr); } tdata->lu_xdr = (XDR *)calloc(1, sizeof(XDR)); xdrmem_create(tdata->lu_xdr, tdata->lu_vm, tdata->lu_vm_length, XDR_DECODE); if (!xdr_int(tdata->lu_xdr, &tdata->lu_vm_cursor)) { lu_endservent(); return NULL; } } if (tdata->lu_vm_cursor == 0) { lu_endservent(); return NULL; } s = extract_service(tdata->lu_xdr, NULL); if (s == NULL) { lu_endservent(); return NULL; } tdata->lu_vm_cursor--; return s; } static struct servent * getserv(const char *name, const char *proto, int port, int source) { struct servent *res = NULL; struct lu_thread_info *tdata; int from_cache; tdata = _lu_data_create_key(_lu_data_key_service, free_lu_thread_info_service); if (tdata == NULL) { tdata = (struct lu_thread_info *)calloc(1, sizeof(struct lu_thread_info)); _lu_data_set_key(_lu_data_key_service, tdata); } from_cache = 0; res = NULL; switch (source) { case S_GET_NAME: res = cache_getservbyname(name, proto); break; case S_GET_PORT: res = cache_getservbyport(port, proto); break; default: res = NULL; } if (res != NULL) { from_cache = 1; } else if (_lu_running()) { switch (source) { case S_GET_NAME: res = lu_getservbyname(name, proto); break; case S_GET_PORT: res = lu_getservbyport(port, proto); break; case S_GET_ENT: res = lu_getservent(); break; default: res = NULL; } } else { pthread_mutex_lock(&_service_lock); switch (source) { case S_GET_NAME: res = copy_service(_old_getservbyname(name, proto)); break; case S_GET_PORT: res = copy_service(_old_getservbyport(port, proto)); break; case S_GET_ENT: res = copy_service(_old_getservent()); break; default: res = NULL; } pthread_mutex_unlock(&_service_lock); } if (from_cache == 0) cache_service(res); recycle_service(tdata, res); return (struct servent *)tdata->lu_entry; } struct servent * getservbyport(int port, const char *proto) { return getserv(NULL, proto, port, S_GET_PORT); } struct servent * getservbyname(const char *name, const char *proto) { return getserv(name, proto, 0, S_GET_NAME); } struct servent * getservent(void) { return getserv(NULL, NULL, 0, S_GET_ENT); } void setservent(int stayopen) { if (_lu_running()) lu_setservent(); else _old_setservent(); } void endservent(void) { if (_lu_running()) lu_endservent(); else _old_endservent(); }