/*
* Copyright (c) 1999 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.0 (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@
*/
/*
* NIAgent.m
* Written by Marc Majka
*/
#import "NIAgent.h"
#import "Config.h"
#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <NetInfo/syslock.h>
#import <NetInfo/system_log.h>
#import <NetInfo/dsutil.h>
#import <NetInfo/nilib2.h>
#import "LUGlobal.h"
#define forever for(;;)
#define DEFAULT_TIMEOUT 30
#define DEFAULT_CONNECT_TIMEOUT 300
#define DEFAULT_LATENCY 30
static unsigned long _shared_handle_count_ = 0;
static ni_shared_handle_t **_shared_handle_ = NULL;
static ni_shared_handle_t *_shared_local_ = NULL;
static char *pathForCategory[] =
{
"/users",
"/groups",
"/machines",
"/networks",
"/services",
"/protocols",
"/rpcs",
"/mounts",
"/printers",
"/machines",
"/machines",
"/aliases",
"/netdomains",
NULL,
NULL,
NULL,
NULL
};
typedef struct ni_private
{
int naddrs;
struct in_addr *addrs;
int whichwrite;
ni_name *tags;
int pid;
int tsock;
int tport;
CLIENT *tc;
long tv_sec;
long rtv_sec;
long wtv_sec;
int abort;
int needwrite;
int uid;
ni_name passwd;
} ni_private;
static ni_shared_handle_t *
ni_shared_handle(struct in_addr *addr, char *tag)
{
struct sockaddr_in sa;
void *domain, *d2;
ni_status status;
ni_id root;
ni_shared_handle_t *h;
if (addr == NULL) return NULL;
if (tag == NULL) return NULL;
memset(&sa, 0, sizeof(struct in_addr));
sa.sin_family = AF_INET;
sa.sin_addr = *addr;
domain = ni_connect(&sa, tag);
if (domain == NULL) return NULL;
if (strcmp(tag, "local") != 0)
{
d2 = ni_new(domain, ".");
ni_free(domain);
domain = d2;
if (domain == NULL) return NULL;
}
root.nii_object = 0;
root.nii_instance = 0;
status = ni_self(domain, &root);
if (status != NI_OK)
{
ni_free(domain);
return NULL;
}
h = (ni_shared_handle_t *)malloc(sizeof(ni_shared_handle_t));
memset(h, 0, sizeof(ni_shared_handle_t));
h->refcount = 1;
h->ni = domain;
return h;
}
static ni_shared_handle_t *
ni_shared_local(void)
{
struct in_addr loop;
if (_shared_local_ != NULL)
{
_shared_local_->refcount++;
return _shared_local_;
}
memset(&loop, 0, sizeof(struct in_addr));
loop.s_addr = htonl(INADDR_LOOPBACK);
_shared_local_ = ni_shared_handle(&loop, "local");
return _shared_local_;
}
static int
ni_shared_match(ni_shared_handle_t *h, struct in_addr *a, char *t)
{
ni_private *ni;
unsigned long i;
if (h == NULL) return 0;
if (h->ni == NULL) return 0;
if (a == NULL) return 0;
if (t == NULL) return 0;
ni = (ni_private *)h->ni;
if (ni == NULL) return 0;
for (i = 0; i < ni->naddrs; i++)
{
if ((ni->addrs[i].s_addr == a->s_addr) && (strcmp(ni->tags[i], t) == 0)) return 1;
}
return 0;
}
static ni_shared_handle_t *
ni_shared_connection(struct in_addr *addr, char *tag)
{
unsigned long i;
ni_shared_handle_t *h;
if (addr == NULL) return NULL;
if (tag == NULL) return NULL;
if (!strcmp(tag, "local") && (addr->s_addr == htonl(INADDR_LOOPBACK)))
{
return ni_shared_local();
}
for (i = 0; i < _shared_handle_count_; i++)
{
if (ni_shared_match(_shared_handle_[i], addr, tag))
{
_shared_handle_[i]->refcount++;
return _shared_handle_[i];
}
}
h = ni_shared_handle(addr, tag);
if (h == NULL) return NULL;
if (_shared_handle_count_ == 0)
{
_shared_handle_ = (ni_shared_handle_t **)malloc(sizeof(ni_shared_handle_t *));
}
else
{
_shared_handle_ = (ni_shared_handle_t **)realloc(_shared_handle_, (_shared_handle_count_ + 1) * sizeof(ni_shared_handle_t *));
}
_shared_handle_[_shared_handle_count_] = h;
_shared_handle_count_++;
return h;
}
static void
ni_shared_release(ni_shared_handle_t *h)
{
unsigned long i, j;
if (h == NULL) return;
if (h->refcount > 0) h->refcount--;
if (h->refcount > 0) return;
ni_free(h->ni);
if (h == _shared_local_)
{
free(_shared_local_);
_shared_local_ = NULL;
return;
}
for (i = 0; i < _shared_handle_count_; i++)
{
if (_shared_handle_[i] == h)
{
free(_shared_handle_[i]);
for (j = i + 1; j < _shared_handle_count_; j++, i++)
{
_shared_handle_[i] = _shared_handle_[j];
}
_shared_handle_count_--;
if (_shared_handle_count_ == 0)
{
free(_shared_handle_);
_shared_handle_ = NULL;
return;
}
_shared_handle_ = (ni_shared_handle_t **)realloc(_shared_handle_, _shared_handle_count_ * sizeof(ni_shared_handle_t *));
return;
}
}
}
static ni_shared_handle_t *
ni_shared_parent(ni_shared_handle_t *h)
{
ni_rparent_res *rpres;
ni_private *ni;
struct in_addr addr;
ni_shared_handle_t *p;
if (h == NULL) return NULL;
if (h->ni == NULL) return NULL;
ni = (ni_private *)h->ni;
rpres = _ni_rparent_2(NULL, ni->tc);
if (rpres == NULL) return NULL;
if (rpres->status != NI_OK) return NULL;
addr.s_addr = htonl(rpres->ni_rparent_res_u.binding.addr);
p = ni_shared_connection(&addr, rpres->ni_rparent_res_u.binding.tag);
free(rpres->ni_rparent_res_u.binding.tag);
return p;
}
static ni_shared_handle_t *
ni_shared_open(void *x, char *rel)
{
void *d;
ni_private *ni;
ni_status status;
ni_shared_handle_t *h;
if (rel == NULL) return NULL;
status = ni_open(x, rel, &d);
if (status != NI_OK) return NULL;
ni = (ni_private *)d;
h = ni_shared_connection(&(ni->addrs[0]), ni->tags[0]);
ni_free(d);
return h;
}
static ni_status
_ni_find(ni_data *domain, unsigned long domain_count, char *str, unsigned long *where, ni_id *dir)
{
ni_status status;
unsigned long i;
if (str == NULL) return NI_NODIR;
if (where == NULL) return NI_NODIR;
if (dir == NULL) return NI_NODIR;
for (i = 0; i < domain_count; i++)
{
status = ni_pathsearch(domain[i].handle->ni, dir, str);
if (status == NI_OK)
{
*where = i;
return status;
}
}
return NI_NODIR;
}
static ni_status
_ni_find_any(ni_data *domain, unsigned long domain_count, char **str, unsigned long *where, ni_id *dir)
{
ni_status status;
unsigned long i, j;
if (str == NULL) return NI_NODIR;
if (where == NULL) return NI_NODIR;
if (dir == NULL) return NI_NODIR;
for (i = 0; i < domain_count; i++)
{
for (j = 0; str[j] != NULL; j++)
{
status = ni_pathsearch(domain[i].handle->ni, dir, str[j]);
if (status == NI_OK)
{
*where = i;
return status;
}
}
}
return NI_NODIR;
}
@implementation NIAgent
- (NIAgent *)init
{
return (NIAgent *)[self initWithArg:NULL];
}
- (LUAgent *)initWithArg:(char *)arg
{
LUDictionary *config;
LUDictionary *global;
char *p, **domain_order;
int i, len;
[super initWithArg:arg];
latency = DEFAULT_LATENCY;
timeout = DEFAULT_TIMEOUT;
connect_timeout = DEFAULT_CONNECT_TIMEOUT;
config = [configManager configForAgent:"NIAgent" fromConfig:configurationArray];
global = [configManager configGlobal:configurationArray];
if (global != nil)
{
if ([global valueForKey:"Timeout"] != NULL)
timeout = [global unsignedLongForKey:"Timeout"];
if ([global valueForKey:"ConnectTimeout"] != NULL)
connect_timeout = [global unsignedLongForKey:"ConnectTimeout"];
if ([global valueForKey:"ValidationLatency"] != NULL)
latency = [global unsignedLongForKey:"ValidationLatency"];
}
if (config != nil)
{
if ([config valueForKey:"Timeout"] != NULL)
timeout = [config unsignedLongForKey:"Timeout"];
if ([config valueForKey:"ConnectTimeout"] != NULL)
connect_timeout = [config unsignedLongForKey:"ConnectTimeout"];
if ([config valueForKey:"ValidationLatency"] != NULL)
latency = [config unsignedLongForKey:"ValidationLatency"];
}
p = NULL;
if ((arg == NULL) && (config != nil))
{
domain_order = [config valuesForKey:"DomainOrder"];
if (domain_order != NULL)
{
len = 0;
for (i = 0; domain_order[i] != NULL; i++) len += (strlen(domain_order[i]) + 1);
if (len > 0)
{
p = malloc(len + 1);
memset(p, 0, len + 1);
for (i = 0; domain_order[i] != NULL; i++)
{
strcat(p, domain_order[i]);
strcat(p, ",");
}
p[len - 1] = '\0';
}
}
}
domain = NULL;
domain_count = 0;
if (p == NULL)
{
[self setSource:arg];
}
else
{
[self setSource:p];
free(p);
}
return self;
}
- (void)dealloc
{
unsigned long i;
syslock_lock(rpcLock);
for (i = 0; i < domain_count; i++)
{
ni_shared_release(domain[i].handle);
}
syslock_unlock(rpcLock);
free(domain);
domain = NULL;
domain_count = 0;
system_log(LOG_DEBUG, "Deallocated NIAgent 0x
[super dealloc];
}
- (void)climbToRoot
{
ni_shared_handle_t *h0, *h1;
if (domain_count == 0) return;
h0 = domain[domain_count - 1].handle;
ni_setabort(h0->ni, 1);
ni_setreadtimeout(h0->ni, connect_timeout);
forever
{
syslock_lock(rpcLock);
h1 = ni_shared_parent(h0);
syslock_unlock(rpcLock);
if (h1 == NULL) return;
h0 = h1;
ni_setreadtimeout(h0->ni, timeout);
syslock_lock(rpcLock);
ni_setpassword(h0->ni, "checksum");
syslock_unlock(rpcLock);
domain = (ni_data *)realloc(domain, (domain_count + 1) * sizeof(ni_data));
domain[domain_count].handle = h0;
domain[domain_count].checksum = 0;
domain[domain_count].checksum_time = 0;
domain_count++;
}
}
- (void)setSource:(char *)arg
{
ni_shared_handle_t *h;
char **list, *str;
unsigned long i, len;
if (arg == NULL)
{
syslock_lock(rpcLock);
h = ni_shared_local();
syslock_unlock(rpcLock);
if (h == NULL) return;
domain = (ni_data *)malloc(sizeof(ni_data));
ni_setreadtimeout(h->ni, timeout);
syslock_lock(rpcLock);
ni_setpassword(h->ni, "checksum");
syslock_unlock(rpcLock);
domain[0].handle = h;
domain[0].checksum = 0;
domain[0].checksum_time = 0;
domain_count = 1;
[self climbToRoot];
return;
}
str = malloc(strlen(arg) + 16);
sprintf(str, "NIAgent ( [self setBanner:str];
free(str);
list = explode(arg, ",");
len = listLength(list);
for (i = 0; i < len; i++)
{
if (streq(list[i], "...")) [self climbToRoot];
else
{
syslock_lock(rpcLock);
h = ni_shared_open(NULL, list[i]);
syslock_unlock(rpcLock);
if (h == NULL) continue;
ni_setreadtimeout(h->ni, timeout);
syslock_lock(rpcLock);
ni_setpassword(h->ni, "checksum");
syslock_unlock(rpcLock);
domain = (ni_data *)realloc(domain, (domain_count + 1) * sizeof(ni_data));
domain[domain_count].handle = h;
domain[domain_count].checksum = 0;
domain[domain_count].checksum_time = 0;
domain_count++;
}
}
freeList(list);
}
- (BOOL)findDirectory:(char *)path domain:(void **)d nidir:(ni_id *)nid
{
ni_status status;
unsigned long where;
where = IndexNull;
status = _ni_find(domain, domain_count, path, &where, nid);
if (status == NI_OK) *d = domain[where].handle->ni;
return (status == NI_OK);
}
- (LUDictionary *)dictionaryForNIProplist:(ni_proplist *)p
{
LUDictionary *item;
int i, j, len;
unsigned int where;
if (p == NULL) return nil;
item = [[LUDictionary alloc] init];
for (i = 0; i < p->ni_proplist_len; i++)
{
if (p->ni_proplist_val[i].nip_name == NULL) continue;
where = [item addKey:p->ni_proplist_val[i].nip_name];
if (where == IndexNull) continue;
len = p->ni_proplist_val[i].nip_val.ni_namelist_len;
for (j = 0; j < len; j++)
{
[item addValue:p->ni_proplist_val[i].nip_val.ni_namelist_val[j] atIndex:where];
}
}
return item;
}
- (const char *)shortName
{
return "NI";
}
- (unsigned long)checksumForIndex:(unsigned long)i
{
struct timeval now;
unsigned long age;
ni_status status;
ni_proplist pl;
unsigned long sum;
ni_index where;
gettimeofday(&now, (struct timezone *)NULL);
age = now.tv_sec - domain[i].checksum_time;
if (age <= latency) return domain[i].checksum;
NI_INIT(&pl);
syslock_lock(rpcLock);
status = ni_statistics(domain[i].handle->ni, &pl);
syslock_unlock(rpcLock);
if (status != NI_OK)
{
system_log(LOG_ERR, "ni_statistics: ni_proplist_free(&pl);
return 0;
}
/* checksum should be first (and only!) property */
where = NI_INDEX_NULL;
if (pl.ni_proplist_len > 0)
{
if (strcmp(pl.ni_proplist_val[0].nip_name, "checksum"))
where = 0;
else
where = ni_proplist_match(pl, "checksum", NULL);
}
if (where == NI_INDEX_NULL)
{
system_log(LOG_ERR, "Domain ni_proplist_free(&pl);
return 0;
}
sscanf(pl.ni_proplist_val[where].nip_val.ni_namelist_val[0], " ni_proplist_free(&pl);
domain[i].checksum_time = now.tv_sec;
domain[i].checksum = sum;
return sum;
}
- (LUDictionary *)stamp:(LUDictionary *)item index:(unsigned long)i
{
char str[64];
if (item == nil) return nil;
[item setAgent:self];
[item setValue:"NI" forKey:"_lookup_info_system"];
sprintf(str, " [item setValue:str forKey:"_lookup_NI_index"];
sprintf(str, " [item setValue:str forKey:"_lookup_NI_checksum"];
return item;
}
- (BOOL)isValid:(LUDictionary *)item
{
unsigned long oldsum, newsum, i;
char *c;
if (item == nil) return NO;
if ([self isStale]) return NO;
c = [item valueForKey:"_lookup_NI_checksum"];
if (c == NULL) return NO;
sscanf(c, "
c = [item valueForKey:"_lookup_NI_index"];
if (c == NULL) return NO;
sscanf(c, "
newsum = [self checksumForIndex:i];
if (oldsum != newsum) return NO;
return YES;
}
- (LUDictionary *)itemWithKey:(char *)key
value:(char *)val
category:(LUCategory)cat
{
char *path, *str, **names;
LUDictionary *item;
ni_status status;
ni_proplist pl;
ni_id dir;
unsigned long where;
BOOL tryRealName;
if (key == NULL) return nil;
if (val == NULL) return nil;
path = pathForCategory[cat];
if (path == NULL) return nil;
str = malloc(strlen(path) + strlen(key) + strlen(val) + 3);
sprintf(str, "
/*
* Special case for user by name queries:
* we check for both "name" and "realname" with the given value.
*/
tryRealName = NO;
if ((cat == LUCategoryUser) && (streq(key, "name")))
{
tryRealName = YES;
names = appendString(str, NULL);
free(str);
str = malloc(strlen(path) + strlen(val) + 11);
sprintf(str, " names = appendString(str, names);
}
where = IndexNull;
status = NI_NODIR;
syslock_lock(rpcLock);
if (tryRealName)
{
status = _ni_find_any(domain, domain_count, names, &where, &dir);
freeList(names);
}
else
{
status = _ni_find(domain, domain_count, str, &where, &dir);
}
syslock_unlock(rpcLock);
free(str);
if (status != NI_OK) return nil;
NI_INIT(&pl);
syslock_lock(rpcLock);
status = ni_read(domain[where].handle->ni, &dir, &pl);
syslock_unlock(rpcLock);
if (status != NI_OK)
{
system_log(LOG_ERR, "ni_read: ni_proplist_free(&pl);
return nil;
}
item = [self dictionaryForNIProplist:&pl];
ni_proplist_free(&pl);
return [self stamp:item index:where];
}
- (void)allChildren:(char *)path index:(unsigned long)where append:(LUArray *)all
{
ni_proplist_list l;
ni_id dir;
int i;
ni_status status;
LUDictionary *item;
syslock_lock(rpcLock);
status = ni_pathsearch(domain[where].handle->ni, &dir, path);
syslock_unlock(rpcLock);
if (status != NI_OK) return;
NI_INIT(&l);
syslock_lock(rpcLock);
status = ni_listall(domain[where].handle->ni, &dir, &l);
syslock_unlock(rpcLock);
if (status == NI_NODIR) return;
if (status != NI_OK)
{
system_log(LOG_ERR, "ni_listall: return;
}
if (l.ni_proplist_list_len == 0) return;
for (i = 0; i < l.ni_proplist_list_len; i++)
{
item = [self dictionaryForNIProplist:&(l.ni_proplist_list_val[i])];
if (item == nil) continue;
[all addObject:item];
[item release];
}
ni_proplist_list_free(&l);
}
- (void)lookupChildren:(char *)path key:(char *)key value:(char *)val index:(unsigned long)where append:(LUArray *)all
{
ni_proplist l;
ni_idlist idl;
ni_id dir;
int i;
ni_status status;
LUDictionary *item;
syslock_lock(rpcLock);
status = ni_pathsearch(domain[where].handle->ni, &dir, path);
syslock_unlock(rpcLock);
if (status != NI_OK) return;
NI_INIT(&idl);
syslock_lock(rpcLock);
status = ni_lookup(domain[where].handle->ni, &dir, key, val, &idl);
syslock_unlock(rpcLock);
if (status == NI_NODIR) return;
if (status != NI_OK)
{
system_log(LOG_ERR, "ni_lookup: ni_idlist_free(&idl);
return;
}
if (idl.ni_idlist_len == 0) return;
for (i = 0; i < idl.ni_idlist_len; i++)
{
dir.nii_object = idl.ni_idlist_val[i];
NI_INIT(&l);
syslock_lock(rpcLock);
status = ni_read(domain[where].handle->ni, &dir, &l);
syslock_unlock(rpcLock);
item = [self dictionaryForNIProplist:&l];
ni_proplist_free(&l);
if (item == nil) continue;
[all addObject:item];
[item release];
}
ni_idlist_free(&idl);
}
- (LUArray *)allItemsWithCategory:(LUCategory)cat key:(char *)key value:(char *)val
{
char *path;
LUArray *all;
LUDictionary *vstamp;
unsigned long i;
char str[128];
path = pathForCategory[cat];
if (path == NULL) return nil;
all = [[LUArray alloc] init];
for (i = 0; i < domain_count; i++)
{
if (key == NULL) [self allChildren:path index:i append:all];
else [self lookupChildren:path key:key value:val index:i append:all];
vstamp = [[LUDictionary alloc] init];
sprintf(str, "V-0x [vstamp setBanner:str];
[self stamp:vstamp index:i];
[all addValidationStamp:vstamp];
[vstamp release];
}
return all;
}
- (LUArray *)allItemsWithCategory:(LUCategory)cat
{
return [self allItemsWithCategory:cat key:NULL value:NULL];
}
/*
* Faster query for NetInfo searches.
*/
- (LUArray *)query:(LUDictionary *)pattern category:(LUCategory)cat
{
LUArray *all, *list;
int i, len;
char *p, *k, *v;
if (pattern == nil) return [self allItemsWithCategory:cat key:NULL value:NULL];
len = [pattern count];
if (len == 0) return [self allItemsWithCategory:cat key:NULL value:NULL];
k = NULL;
v = NULL;
/* Use "name" key if given (it has a good chance of matching fewer records) */
i = [pattern indexForKey:"name"];
if (i != IndexNull)
{
k = [pattern keyAtIndex:i];
v = [pattern valueAtIndex:i];
}
/* If there was no "name" key, use first key without "_lookup_" prefix */
for (i = 0; (i < len) && (k == NULL); i++)
{
p = [pattern keyAtIndex:i];
if (!strncmp(p, "_lookup_", 8)) continue;
k = p;
v = [pattern valueAtIndex:i];
}
all = [self allItemsWithCategory:cat key:k value:v];
list = nil;
if (all != nil)
{
list = [all filter:pattern];
if (list != nil)
{
len = [all validationStampCount];
for (i = 0; i < len; i++)
{
[list addValidationStamp:[all validationStampAtIndex:i]];
}
}
[all release];
}
return list;
}
/*
* Custom lookup for security options
*
* Special case: "all" enables all security options
*/
- (BOOL)isSecurityEnabledForOption:(char *)option
{
ni_id dir;
ni_status status;
unsigned long i, j;
ni_namelist nl;
if (option == NULL) return NO;
dir.nii_object = 0;
for (i = 0; i < domain_count; i++)
{
NI_INIT(&nl);
syslock_lock(rpcLock);
status = ni_lookupprop(domain[i].handle->ni, &dir, "security_options", &nl);
syslock_unlock(rpcLock);
if (status != NI_OK)
{
ni_namelist_free(&nl);
continue;
}
for (j = 0; j < nl.ni_namelist_len; j++)
{
if (streq(nl.ni_namelist_val[j], option) || streq(nl.ni_namelist_val[j], "all"))
{
ni_namelist_free(&nl);
return YES;
}
}
ni_namelist_free(&nl);
}
return NO;
}
@end