#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 #include #include #include #include #include #include #include #include #include #include #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; }