2021-10-11 18:37:13 -03:00

693 lines
16 KiB
C

#ifndef lint
static char sccsid[] = "@(#)rpc.ipallocd.c 1.1 94/10/31 Copyright 1990 Sun Microsystems";
#endif
/*
* Copyright (c) 1990 by Sun Microsystems, Inc.
*/
/* rpc.ipalloc.c
*
* An implementation of the remote procedure calls of the ipalloc
* service.
*
* XXX Very strong need to speed this up for large networks.
* There's a requirement to deliver the address to the end
* user in under 2 minutes for PNP of most diskful systems.
* While the documented procedures say do PNP one system at
* a time, that might not always occur. Also, not all networks
* will be Class C or subnetted.
*/
#include <stdio.h>
#include <netdb.h>
#include <des_crypt.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <rpc/rpc.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include "ipalloc.h"
#include "netrange.h"
#define CACHEFILE "/etc/ipalloc.cache"
#define MAXHOSTNAME 256
extern int optind, getopt ();
extern char *optarg;
extern char *ether_ntoa (), *inet_ntoa ();
struct cache_ent {
struct cache_ent *next;
struct timeval tv;
struct ether_addr ea;
struct in_addr ip;
};
static struct cache_ent *cache_head; /* address queue */
int debug; /* print out debugging info */
static int insecure; /* allow non-DES auth */
static int cache_flag; /* keep the cache on disk */
void read_cache(); /* forward */
static int
am_yp_master (map)
char *map;
{
char *dom, *master;
char me [MAXHOSTNAME];
int status;
(void) yp_get_default_domain (&dom);
yp_master (dom, map, &master);
(void) gethostname (me, sizeof (me));
status = !strcmp (me, master);
(void) free (dom);
(void) free (master);
return status;
}
#define NGID 10 /* extra */
#define SYSPRIV 12 /* group # for network privilege */
int
has_sysprivs (netname)
char *netname;
{
int gid, uid, gids [NGID], ngid = NGID, i, vallen;
char sysname [256], *domain, *val;
/* NIS servers can do it
*/
if (netname2host (netname, sysname, sizeof (sysname))
&& !yp_get_default_domain (&domain)
&& !yp_match (domain, "ypservers",
sysname, strlen (sysname), &val, &vallen)) {
(void) free (val);
return 1;
}
/* and any user with the 'networks' privilege
* can do it as well.
*/
if (netname2user (netname, &uid, &gid, &ngid, gids)) {
if (gid == SYSPRIV)
return 1;
else for (i = 0; i < ngid; i++)
if (gids [i] == SYSPRIV)
return 1;
}
return 0;
}
static int
is_auth_caller (flavor, auth)
struct authdes_cred *auth;
{
register char *name;
if (insecure)
return 1;
else if (flavor != AUTH_DES)
return 0;
else
return has_sysprivs (auth->adc_fullname.name);
}
static
have_encryption()
{
static int status = -1;
char key[8];
char data[8];
if (status < 0) {
bzero(key, sizeof(key));
status = ecb_crypt(key, data, sizeof(data), DES_ENCRYPT|DES_SW);
}
return (status == 0);
}
/*
* main:
*/
main(argc, argv)
int argc;
char **argv;
{
int c;
insecure = !have_encryption ();
debug = 0;
/*cache_flag = 1; /**/
while ((c = getopt (argc, argv, "dcis")) != EOF)
switch (c) {
case 'c':
cache_flag = 0;
continue;
case 'd':
debug++;
continue;
case 'i':
insecure = 1;
continue;
case 's':
insecure = 0;
continue;
case '?':
default:
usage:
(void) fprintf (stderr, "usage: %s [-cis]\n", argv[0]);
exit (1);
}
if (debug)
(void) printf ("%s: in debug mode\n", argv[0]);
if (argc != optind) /* non-option arguments */
goto usage;
if (!am_yp_master ("hosts.byaddr")) {
if (debug)
printf ("%s: not running on NIS master, exit\n", argv[0]);
exit (1);
} else if (getuid ()) {
if (debug)
printf ("%s: not running as root\n", argv [0]);
#ifdef NODEBUG
exit (1);
#endif NODEBUG
}
if (!debug) {
switch (fork ()) {
case 0: /* kid */
/* XXX need TIOCNOTTY, maybe set up fd 0 as client
*/
break;
case -1:
exit (-1);
/* NOTREACHED */
default: /* parent */
exit (0);
/* NOTREACHED */
}
}
/*
* XXX make this start up through inetd
* XXX quit if there are no cache entries and
* if it's not been referenced in a while.
*/
read_cache();
/* start the server-side stub. This never returns */
serverstub_main();
}
/* Time out entries in the cache.
* Assumes that the cache timeout is long, so minor errors in the
* calculation are OK.
*/
void
cache_timeout ()
{
register struct cache_ent *cp, **prevp;
struct timeval tv;
(void) gettimeofday(&tv, (struct timezone *)NULL);
for (cp = cache_head, prevp = &cache_head;
cp != (struct cache_ent *) NULL;
cp = cp->next) {
if (tv.tv_sec > cp->tv.tv_sec + IPALLOC_TIMEOUT) {
*prevp = cp->next;
free(cp);
/* cp still valid, e.g. for realloc ()
* or in particular cp = cp->next
*/
} else
prevp = &cp->next;
}
}
/* return a cache entry with this ethernet address
*/
static struct cache_ent *
cache_lookup_ether (ep)
struct ether_addr *ep;
{
register struct cache_ent *cp;
for (cp = cache_head; cp; cp = cp->next)
if (!bcmp ((char *) ep, (char *) &cp->ea, sizeof (cp->ea))) {
(void) gettimeofday (&cp->tv, (struct timezone *)NULL);
return cp;
}
return (struct cache_ent *) NULL;
}
/* allocate a new cache entry for this etheraddr on
* this network and subnet.
*/
static struct cache_ent *
cache_add (ep, netnum, netmask)
struct ether_addr *ep;
u_long netnum, netmask;
{
register struct cache_ent *cp;
struct in_addr ip;
if (!getfreeipaddr (netnum, netmask, &ip))
return (struct cache_ent *) NULL;
if (!(cp = (struct cache_ent *) malloc (sizeof (*cp))))
return cp;
(void) gettimeofday (&cp->tv, (struct timezone *)NULL);
cp->ea = *ep;
cp->ip = ip;
cp->next = cache_head;
cache_head = cp;
return cp;
}
/*
* read_cache:
*
* Reads the allocated IP cache from a file into memory.
* Should be called once when program is started.
*/
void
read_cache()
{
struct cache_ent *cp;
struct ether_addr ea;
u_char *ip, i0, i1, i2, i3;
int n;
struct tm *tp;
FILE *fd;
cache_head = (struct cache_ent *) 0;
if (!cache_flag)
return;
if (!(fd = fopen (CACHEFILE, "r+")))
return;
/* XXX we need a much better way to store this info;
* binary mmap() file? This will eliminate scan errors
* as well...
*/
for (i0 = 0, tp = localtime(&i0), n = 0;
fscanf(fd,
"%d/%d/%d %d:%d:%d %x:%x:%x:%x:%x:%x %d.%d.%d.%d",
&tp->tm_mon, &tp->tm_mday, &tp->tm_year,
&tp->tm_hour, &tp->tm_min, &tp->tm_sec,
#define eo ea.ether_addr_octet
&eo [0], &eo [1], &eo [2],
&eo [3], &eo [4], &eo [5],
#undef eo
&i0, &i1, &i2, &i3) == 16;
cp->next = cache_head, cache_head = cp, n++) {
cp = (struct cache_ent *) malloc(sizeof(struct cache_ent));
if (cp == (struct cache_ent *) 0) {
if (debug)
(void) printf("ipalloc: init_cache: malloc failure.\n");
/* XXX nasty error handling */
exit(1);
}
cp->ea = ea;
ip = (u_char *) & cp->ip.s_addr;
ip[0] = (u_char) i0;
ip[1] = (u_char) i1;
ip[2] = (u_char) i2;
ip[3] = (u_char) i3;
cp->tv.tv_sec = timelocal (tp);
cp->tv.tv_usec = 0;
}
(void) fclose (fd);
if (debug)
(void) printf("init_cache: read %d cache entries.\n", n);
cache_timeout ();
}
/*
* write_cache:
*
* Writes the in-memory allocated IP address cache to a file.
*/
void
write_cache()
{
u_char *ea, *ip;
struct cache_ent *cp;
int n;
struct tm *tp;
FILE *fd;
if (!cache_flag)
return;
if (!(fd = fopen (CACHEFILE, "w"))) {
if (debug)
printf ("Couldn't open %s to write cache\n", CACHEFILE);
return;
}
cache_timeout ();
for (n = 0, cp = cache_head; cp; cp = cp->next) {
ea = (u_char *) & cp->ea;
ip = (u_char *) & cp->ip.s_addr;
tp = localtime(&cp->tv.tv_sec);
if (fprintf(fd, "%d/%d/%.2d %2d:%.2d:%.2d %x:%x:%x:%x:%x:%x %d.%d.%d.%d\n",
tp->tm_mon, tp->tm_mday, tp->tm_year,
tp->tm_hour, tp->tm_min, tp->tm_sec,
(u_int) ea[0], (u_int) ea[1], (u_int) ea[2],
(u_int) ea[3], (u_int) ea[4], (u_int) ea[5],
(u_int) ip[0], (u_int) ip[1], (u_int) ip[2],
(u_int) ip[3]) == EOF) {
if (debug)
perror("ipalloc: write_cache");
/* XXX very bad to exit!! */
exit(1);
}
n++;
}
(void) fclose (fd);
if (debug)
(void) printf("write_cache: wrote %d entries.\n", n);
}
/*
* getfreeipaddr:
*
* Searches the hosts database and local cache for unassigned IP addresses
* on the network and subnet specified. Returns true iff successful in
* storing an unused address in *ipp.
*/
int
getfreeipaddr (netnum, subnetmask, ipp)
u_long netnum;
u_long subnetmask;
struct in_addr *ipp;
{
u_long max;
u_long lhost;
struct in_addr ip;
struct cache_ent *cp;
hostrange *hrp;
netnum &= subnetmask;
hrp = get_hostrange (netnum);
if (debug) {
u_long xnet = htonl(netnum), xmask = htonl(subnetmask);
(void) printf ("getfreeipaddr: netnum = %s,",
inet_ntoa (*(struct in_addr *)&xnet));
(void) printf ("subnetmask = %s\n",
inet_ntoa (*(struct in_addr *)&xmask));
}
/* the addresses must be in host order for these
* #defines to work
*/
if (IN_CLASSA(netnum))
max = (IN_CLASSA_HOST - 1) & ~subnetmask;
else if (IN_CLASSB(netnum))
max = (IN_CLASSB_HOST - 1) & ~subnetmask;
else if (IN_CLASSC(netnum))
max = (IN_CLASSC_HOST - 1) & ~subnetmask;
else {
if (debug)
(void) printf ("class D or other unknown network: %s\n",
inet_ntoa (*(struct in_addr *)&netnum));
return 0;
}
/* Cycle through possible IP addresses. Addresses are
* ruled out if on wrong subnet or unavailable. They may
* be unavailable because this daemon doesn't have authority
* over them, because they're in the hosts database, or
* because they're in use.
* Count down through range of available addresses so that
* we're more likely to hit a free one soon.
*/
for (lhost = max ; lhost >= (hrp ? hrp->h_start : 1); lhost--) {
if (!lhost || lhost & subnetmask) /* nonzero? right subnet? */
continue;
/* XXX expects 32 bit u_long !! */
if (lhost == ~subnetmask) /* reject broadcast */
break;
if (hrp && hrp->h_start != 0) { /* in legal range? */
if (lhost < hrp->h_start)
continue;
if (lhost >= hrp->h_end)
hrp++;
}
ip.s_addr = htonl (netnum | lhost); /* construct address */
/* check hosts database, then cache ... maybe
* we have an address to return.
*/
if (!gethostbyaddr ((char *) &ip, sizeof(ip), AF_INET)) {
cache_timeout ();
for (cp = cache_head; cp != (struct cache_ent *) 0; cp = cp->next)
if (cp->ip.s_addr == ip.s_addr)
break;
if (!cp) {
if (debug)
(void) printf("getfreeipaddr: ip = %s.\n", inet_ntoa (ip));
*ipp = ip;
return 1;
}
}
}
return 0;
}
static void
get_default_netmask (l, m)
u_long l, *m;
{
if (IN_CLASSA (l))
*m = IN_CLASSA_NET;
else if (IN_CLASSB (l))
*m = IN_CLASSB_NET;
else if (IN_CLASSC (l))
*m = IN_CLASSC_NET;
else
*m = ~0; /* error */
}
static int
right_net (in, arg)
u_long in;
struct ip_alloc_arg *arg;
{
u_long m, a, desired;
get_default_netmask (in = ntohl (in), &m);
a = in & m;
get_default_netmask (arg->netnum, &m);
desired = arg->netnum & m;
if (a != desired) /* right ABC network? */
return 0;
m |= arg->subnetmask; /* just in case */
a = in & ~m; /* local host # */
if ((in & m) != (arg->netnum & m) /* wrong subnet? */
|| !a /* local net #, lh = 0 */
|| a == ~m) /* local net broadcast, lh = ~0 */
return 0;
return 1;
}
/*
* ipalloc (version 2):
*
* Allocate a new IP address. Caller provides the ethernet address of the
* machine for which the IP address is being allocated, along with the
* network number and subnet mask of the network where the new IP address is
* to be allocated. We first check to see if there is already an IP address
* for this machine on this network+subnet. If there is not, we consider
* allocating one. We keep a cache of recently allocated IP addresses along
* with the ethernet addresses of the machines to which they were allocated.
* This cache is checked before a new IP address is allocated.
*/
struct ip_alloc_res *
ip_alloc_2(argument, svcreq)
struct ip_alloc_arg *argument;
struct svc_req *svcreq;
{
struct ether_addr ea;
struct cache_ent *cp;
struct hostent *hp;
char hostname [MAXHOSTNAME];
static struct ip_alloc_res
result;
if (!is_auth_caller (svcreq->rq_cred.oa_flavor, svcreq->rq_clntcred)) {
if (debug)
(void) fprintf (stderr, "ip_alloc_2: invalid auth\n");
result.status = ip_no_priv;
return &result;
}
if (debug)
fprintf (stderr, "--- ip_alloc %s\n",
ether_ntoa (argument->etheraddr));
bzero((char *) &result, sizeof(result));
bcopy((char *) &argument->etheraddr[0], (char *) &ea, sizeof(ea));
bzero((char *) &hostname[0], MAXHOSTNAME);
/* Do ether --> hosts database lookup.
* Maybe we can return the results.
*/
if (ether_ntohost(&hostname[0], &ea) == 0) {
if ((hp = gethostbyname(&hostname[0])) != (struct hostent *) NULL) {
if (hp->h_addrtype == AF_INET
&& right_net (*(u_long *)hp->h_addr, argument)) {
(void) bcopy((char *) hp->h_addr,
(char *) &result.ip_alloc_res_u.ipaddr,
sizeof(result.ip_alloc_res_u.ipaddr));
result.status = ip_success;
return &result;
}
}
}
/* The ether --> hosts database lookup failed;
* return an existing cache entry or a newly
* created one, if possible.
*/
cache_timeout ();
if ((cp = cache_lookup_ether (&ea))
|| (cp = cache_add (&ea, argument->netnum, argument->subnetmask))) {
result.ip_alloc_res_u.ipaddr = ntohl (cp->ip.s_addr);
result.status = ip_success;
if (debug)
printf ("Success, %s is %s\n",
ether_ntoa (&cp->ea), inet_ntoa (cp->ip));
write_cache ();
return &result;
}
/* Couldn't return an address, so sorry.
*/
result.status = ip_no_addresses;
return &result;
}
/*
* iptoname (version 1):
*
* Map an IP address into the hostname associated with it in the hosts database.
* Basically, just a call to gethostbyaddr. Used by clients who don't
* believe their "local" hosts database to check with a more authoritative
* database.
*/
/* ARGSUSED */
struct ip_toname_res *
ip_toname_2(argument, svcreq)
struct ip_addr_arg *argument;
struct svc_req *svcreq;
{
struct hostent *hp;
static struct ip_toname_res result;
long the_addr = htonl (argument->ipaddr);
if (debug)
fprintf (stderr, "--- ip_toname %s\n", inet_ntoa (the_addr));
hp = gethostbyaddr((char *) &the_addr, sizeof(argument->ipaddr),
AF_INET);
if (hp == (struct hostent *) 0)
result.status = ip_failure;
else {
result.status = ip_success;
result.ip_toname_res_u.name = hp->h_name;
}
if (debug)
fprintf (stderr, "\t--> %d\n", result.status);
return &result;
}
ip_status *
ip_free_2 (arg, svcreq)
register ip_addr_arg *arg;
struct svc_req *svcreq;
{
static ip_status s;
register struct cache_ent *cp, **prevp;
if (!is_auth_caller (svcreq->rq_cred.oa_flavor, svcreq->rq_clntcred)) {
s = ip_no_priv;
return &s;
}
if (debug) {
fprintf (stderr, "--- ip_free %s\n",
inet_ntoa (ntohl (arg->ipaddr)));
}
for (cp = cache_head, prevp = &cache_head, s = ip_failure;
cp != (struct cache_ent *) NULL;
cp = cp->next) {
if (ntohl (cp->ip.s_addr) == arg->ipaddr) {
*prevp = cp->next;
free(cp);
s = ip_success;
write_cache ();
break;
} else
prevp = &cp->next;
}
if (debug)
fprintf (stderr, "\t--> %d\n", s);
return &s;
}