xref: /dragonfly/lib/libc/rpc/getnetconfig.c (revision bb8c85ff)
1 /*-
2  * Copyright (c) 2009, Sun Microsystems, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * - Redistributions of source code must retain the above copyright notice,
8  *   this list of conditions and the following disclaimer.
9  * - Redistributions in binary form must reproduce the above copyright notice,
10  *   this list of conditions and the following disclaimer in the documentation
11  *   and/or other materials provided with the distribution.
12  * - Neither the name of Sun Microsystems, Inc. nor the names of its
13  *   contributors may be used to endorse or promote products derived
14  *   from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  *
28  * @(#)getnetconfig.c	1.12 91/12/19 SMI
29  * $NetBSD: getnetconfig.c,v 1.3 2000/07/06 03:10:34 christos Exp $
30  * $FreeBSD: src/lib/libc/rpc/getnetconfig.c,v 1.14 2007/09/20 22:35:24 matteo Exp $
31  */
32 
33 /*
34  * Copyright (c) 1989 by Sun Microsystems, Inc.
35  */
36 
37 #include "namespace.h"
38 #include "reentrant.h"
39 #include <stdio.h>
40 #include <errno.h>
41 #include <netconfig.h>
42 #include <stddef.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <rpc/rpc.h>
46 #include <unistd.h>
47 #include "un-namespace.h"
48 #include "rpc_com.h"
49 
50 /*
51  * The five library routines in this file provide application access to the
52  * system network configuration database, /etc/netconfig.  In addition to the
53  * netconfig database and the routines for accessing it, the environment
54  * variable NETPATH and its corresponding routines in getnetpath.c may also be
55  * used to specify the network transport to be used.
56  */
57 
58 
59 /*
60  * netconfig errors
61  */
62 
63 #define NC_NONETCONFIG	ENOENT
64 #define NC_NOMEM	ENOMEM
65 #define NC_NOTINIT	EINVAL	    /* setnetconfig was not called first */
66 #define NC_BADFILE	EBADF	    /* format for netconfig file is bad */
67 #define NC_NOTFOUND	ENOPROTOOPT /* specified netid was not found */
68 
69 /*
70  * semantics as strings (should be in netconfig.h)
71  */
72 #define NC_TPI_CLTS_S	    "tpi_clts"
73 #define	NC_TPI_COTS_S	    "tpi_cots"
74 #define	NC_TPI_COTS_ORD_S   "tpi_cots_ord"
75 #define	NC_TPI_RAW_S        "tpi_raw"
76 
77 /*
78  * flags as characters (also should be in netconfig.h)
79  */
80 #define	NC_NOFLAG_C	'-'
81 #define	NC_VISIBLE_C	'v'
82 #define	NC_BROADCAST_C	'b'
83 
84 /*
85  * Character used to indicate there is no name-to-address lookup library
86  */
87 #define NC_NOLOOKUP	"-"
88 
89 static const char * const _nc_errors[] = {
90     "Netconfig database not found",
91     "Not enough memory",
92     "Not initialized",
93     "Netconfig database has invalid format",
94     "Netid not found in netconfig database"
95 };
96 
97 struct netconfig_info {
98     int		eof;	/* all entries has been read */
99     int		ref;	/* # of times setnetconfig() has been called */
100     struct netconfig_list	*head;	/* head of the list */
101     struct netconfig_list	*tail;	/* last of the list */
102 };
103 
104 struct netconfig_list {
105     char			*linep;	/* hold line read from netconfig */
106     struct netconfig		*ncp;
107     struct netconfig_list	*next;
108 };
109 
110 struct netconfig_vars {
111     int   valid;	/* token that indicates a valid netconfig_vars */
112     int   flag;		/* first time flag */
113     struct netconfig_list *nc_configs;  /* pointer to the current netconfig entry */
114 };
115 
116 #define NC_VALID	0xfeed
117 #define NC_STORAGE	0xf00d
118 #define NC_INVALID	0
119 
120 
121 static int *__nc_error(void);
122 static int parse_ncp(char *, struct netconfig *);
123 static struct netconfig *dup_ncp(struct netconfig *);
124 
125 
126 static FILE *nc_file;		/* for netconfig db */
127 static struct netconfig_info	ni = { 0, 0, NULL, NULL};
128 
129 #define MAXNETCONFIGLINE    1000
130 
131 static int *
132 __nc_error(void)
133 {
134 	static pthread_mutex_t nc_lock = PTHREAD_MUTEX_INITIALIZER;
135 	static thread_key_t nc_key = 0;
136 	static int nc_error = 0;
137 	int error, *nc_addr;
138 
139 	/*
140 	 * Use the static `nc_error' if we are the main thread
141 	 * (including non-threaded programs), or if an allocation
142 	 * fails.
143 	 */
144 	if (thr_main())
145 		return (&nc_error);
146 	if (nc_key == 0) {
147 		error = 0;
148 		mutex_lock(&nc_lock);
149 		if (nc_key == 0)
150 			error = thr_keycreate(&nc_key, free);
151 		mutex_unlock(&nc_lock);
152 		if (error)
153 			return (&nc_error);
154 	}
155 	if ((nc_addr = (int *)thr_getspecific(nc_key)) == NULL) {
156 		nc_addr = (int *)malloc(sizeof (int));
157 		if (thr_setspecific(nc_key, (void *) nc_addr) != 0) {
158 			if (nc_addr)
159 				free(nc_addr);
160 			return (&nc_error);
161 		}
162 		*nc_addr = 0;
163 	}
164 	return (nc_addr);
165 }
166 
167 #define nc_error        (*(__nc_error()))
168 /*
169  * A call to setnetconfig() establishes a /etc/netconfig "session".  A session
170  * "handle" is returned on a successful call.  At the start of a session (after
171  * a call to setnetconfig()) searches through the /etc/netconfig database will
172  * proceed from the start of the file.  The session handle must be passed to
173  * getnetconfig() to parse the file.  Each call to getnetconfig() using the
174  * current handle will process one subsequent entry in /etc/netconfig.
175  * setnetconfig() must be called before the first call to getnetconfig().
176  * (Handles are used to allow for nested calls to setnetpath()).
177  *
178  * A new session is established with each call to setnetconfig(), with a new
179  * handle being returned on each call.  Previously established sessions remain
180  * active until endnetconfig() is called with that session's handle as an
181  * argument.
182  *
183  * setnetconfig() need *not* be called before a call to getnetconfigent().
184  * setnetconfig() returns a NULL pointer on failure (for example, if
185  * the netconfig database is not present).
186  */
187 void *
188 setnetconfig(void)
189 {
190     struct netconfig_vars *nc_vars;
191 
192     if ((nc_vars = (struct netconfig_vars *)malloc(sizeof
193 		(struct netconfig_vars))) == NULL) {
194 	return(NULL);
195     }
196 
197     /*
198      * For multiple calls, i.e. nc_file is not NULL, we just return the
199      * handle without reopening the netconfig db.
200      */
201     ni.ref++;
202     if ((nc_file != NULL) || (nc_file = fopen(NETCONFIG, "r")) != NULL) {
203 	nc_vars->valid = NC_VALID;
204 	nc_vars->flag = 0;
205 	nc_vars->nc_configs = ni.head;
206 	return ((void *)nc_vars);
207     }
208     ni.ref--;
209     nc_error = NC_NONETCONFIG;
210     free(nc_vars);
211     return (NULL);
212 }
213 
214 
215 /*
216  * When first called, getnetconfig() returns a pointer to the first entry in
217  * the netconfig database, formatted as a struct netconfig.  On each subsequent
218  * call, getnetconfig() returns a pointer to the next entry in the database.
219  * getnetconfig() can thus be used to search the entire netconfig file.
220  * getnetconfig() returns NULL at end of file.
221  */
222 
223 struct netconfig *
224 getnetconfig(void *handlep)
225 {
226     struct netconfig_vars *ncp = (struct netconfig_vars *)handlep;
227     char *stringp;		/* tmp string pointer */
228     struct netconfig_list	*list;
229     struct netconfig *np;
230 
231     /*
232      * Verify that handle is valid
233      */
234     if (ncp == NULL || nc_file == NULL) {
235 	nc_error = NC_NOTINIT;
236 	return (NULL);
237     }
238 
239     switch (ncp->valid) {
240     case NC_VALID:
241 	/*
242 	 * If entry has already been read into the list,
243 	 * we return the entry in the linked list.
244 	 * If this is the first time call, check if there are any entries in
245 	 * linked list.  If no entries, we need to read the netconfig db.
246 	 * If we have been here and the next entry is there, we just return
247 	 * it.
248 	 */
249 	if (ncp->flag == 0) {	/* first time */
250 	    ncp->flag = 1;
251 	    ncp->nc_configs = ni.head;
252 	    if (ncp->nc_configs != NULL)	/* entry already exist */
253 		return(ncp->nc_configs->ncp);
254 	}
255 	else if (ncp->nc_configs != NULL && ncp->nc_configs->next != NULL) {
256 	    ncp->nc_configs = ncp->nc_configs->next;
257 	    return(ncp->nc_configs->ncp);
258 	}
259 
260 	/*
261 	 * If we cannot find the entry in the list and is end of file,
262 	 * we give up.
263 	 */
264 	if (ni.eof == 1)	return(NULL);
265 	break;
266     default:
267 	nc_error = NC_NOTINIT;
268 	return (NULL);
269     }
270 
271     stringp = (char *) malloc(MAXNETCONFIGLINE);
272     if (stringp == NULL)
273 	return (NULL);
274 
275 #ifdef MEM_CHK
276     if (malloc_verify() == 0) {
277 	fprintf(stderr, "memory heap corrupted in getnetconfig\n");
278 	exit(1);
279     }
280 #endif
281 
282     /*
283      * Read a line from netconfig file.
284      */
285     do {
286 	if (fgets(stringp, MAXNETCONFIGLINE, nc_file) == NULL) {
287 	    free(stringp);
288 	    ni.eof = 1;
289 	    return (NULL);
290         }
291     } while (*stringp == '#');
292 
293     list = (struct netconfig_list *) malloc(sizeof (struct netconfig_list));
294     if (list == NULL) {
295 	free(stringp);
296 	return(NULL);
297     }
298     np = (struct netconfig *) malloc(sizeof (struct netconfig));
299     if (np == NULL) {
300 	free(stringp);
301 	free(list);
302 	return(NULL);
303     }
304     list->ncp = np;
305     list->next = NULL;
306     list->ncp->nc_lookups = NULL;
307     list->linep = stringp;
308     if (parse_ncp(stringp, list->ncp) == -1) {
309 	free(stringp);
310 	free(np);
311 	free(list);
312 	return (NULL);
313     }
314     else {
315 	/*
316 	 * If this is the first entry that's been read, it is the head of
317 	 * the list.  If not, put the entry at the end of the list.
318 	 * Reposition the current pointer of the handle to the last entry
319 	 * in the list.
320 	 */
321 	if (ni.head == NULL) {	/* first entry */
322 	    ni.head = ni.tail = list;
323 	}
324 	else {
325 	    ni.tail->next = list;
326 	    ni.tail = ni.tail->next;
327 	}
328 	ncp->nc_configs = ni.tail;
329 	return(ni.tail->ncp);
330     }
331 }
332 
333 /*
334  * endnetconfig() may be called to "unbind" or "close" the netconfig database
335  * when processing is complete, releasing resources for reuse.  endnetconfig()
336  * may not be called before setnetconfig().  endnetconfig() returns 0 on
337  * success and -1 on failure (for example, if setnetconfig() was not called
338  * previously).
339  */
340 int
341 endnetconfig(void *handlep)
342 {
343     struct netconfig_vars *nc_handlep = (struct netconfig_vars *)handlep;
344 
345     struct netconfig_list *q, *p;
346 
347     /*
348      * Verify that handle is valid
349      */
350     if (nc_handlep == NULL || (nc_handlep->valid != NC_VALID &&
351 	    nc_handlep->valid != NC_STORAGE)) {
352 	nc_error = NC_NOTINIT;
353 	return (-1);
354     }
355 
356     /*
357      * Return 0 if anyone still needs it.
358      */
359     nc_handlep->valid = NC_INVALID;
360     nc_handlep->flag = 0;
361     nc_handlep->nc_configs = NULL;
362     if (--ni.ref > 0) {
363 	free(nc_handlep);
364 	return(0);
365     }
366 
367     /*
368      * Noone needs these entries anymore, then frees them.
369      * Make sure all info in netconfig_info structure has been reinitialized.
370      */
371     q = p = ni.head;
372     ni.eof = ni.ref = 0;
373     ni.head = NULL;
374     ni.tail = NULL;
375     while (q) {
376 	p = q->next;
377 	if (q->ncp->nc_lookups != NULL) free(q->ncp->nc_lookups);
378 	free(q->ncp);
379 	free(q->linep);
380 	free(q);
381 	q = p;
382     }
383     free(nc_handlep);
384 
385     fclose(nc_file);
386     nc_file = NULL;
387     return (0);
388 }
389 
390 /*
391  * getnetconfigent(netid) returns a pointer to the struct netconfig structure
392  * corresponding to netid.  It returns NULL if netid is invalid (that is, does
393  * not name an entry in the netconfig database).  It returns NULL and sets
394  * errno in case of failure (for example, if the netconfig database cannot be
395  * opened).
396  */
397 
398 struct netconfig *
399 getnetconfigent(const char *netid)
400 {
401     FILE *file;		/* NETCONFIG db's file pointer */
402     char *linep;	/* holds current netconfig line */
403     char *stringp;	/* temporary string pointer */
404     struct netconfig *ncp = NULL;   /* returned value */
405     struct netconfig_list *list;	/* pointer to cache list */
406 
407     nc_error = NC_NOTFOUND;	/* default error. */
408     if (netid == NULL || strlen(netid) == 0) {
409 	return (NULL);
410     }
411 
412     /*
413      * Look up table if the entries have already been read and parsed in
414      * getnetconfig(), then copy this entry into a buffer and return it.
415      * If we cannot find the entry in the current list and there are more
416      * entries in the netconfig db that has not been read, we then read the
417      * db and try find the match netid.
418      * If all the netconfig db has been read and placed into the list and
419      * there is no match for the netid, return NULL.
420      */
421     if (ni.head != NULL) {
422 	for (list = ni.head; list; list = list->next) {
423 	    if (strcmp(list->ncp->nc_netid, netid) == 0) {
424 	        return(dup_ncp(list->ncp));
425 	    }
426 	}
427 	if (ni.eof == 1)	/* that's all the entries */
428 		return(NULL);
429     }
430 
431 
432     if ((file = fopen(NETCONFIG, "r")) == NULL) {
433 	nc_error = NC_NONETCONFIG;
434 	return (NULL);
435     }
436 
437     if ((linep = malloc(MAXNETCONFIGLINE)) == NULL) {
438 	fclose(file);
439 	nc_error = NC_NOMEM;
440 	return (NULL);
441     }
442     do {
443 	ptrdiff_t len;
444 	char *tmpp;	/* tmp string pointer */
445 
446 	do {
447 	    if ((stringp = fgets(linep, MAXNETCONFIGLINE, file)) == NULL) {
448 		break;
449 	    }
450 	} while (*stringp == '#');
451 	if (stringp == NULL) {	    /* eof */
452 	    break;
453 	}
454 	if ((tmpp = strpbrk(stringp, "\t ")) == NULL) {	/* can't parse file */
455 	    nc_error = NC_BADFILE;
456 	    break;
457 	}
458 	if (strlen(netid) == (size_t) (len = tmpp - stringp) &&	/* a match */
459 		strncmp(stringp, netid, (size_t)len) == 0) {
460 	    if ((ncp = (struct netconfig *)
461 		    malloc(sizeof (struct netconfig))) == NULL) {
462 		break;
463 	    }
464 	    ncp->nc_lookups = NULL;
465 	    if (parse_ncp(linep, ncp) == -1) {
466 		free(ncp);
467 		ncp = NULL;
468 	    }
469 	    break;
470 	}
471     } while (stringp != NULL);
472     if (ncp == NULL) {
473 	free(linep);
474     }
475     fclose(file);
476     return(ncp);
477 }
478 
479 /*
480  * freenetconfigent(netconfigp) frees the netconfig structure pointed to by
481  * netconfigp (previously returned by getnetconfigent()).
482  */
483 
484 void
485 freenetconfigent(struct netconfig *netconfigp)
486 {
487     if (netconfigp != NULL) {
488 	free(netconfigp->nc_netid);	/* holds all netconfigp's strings */
489 	if (netconfigp->nc_lookups != NULL)
490 	    free(netconfigp->nc_lookups);
491 	free(netconfigp);
492     }
493     return;
494 }
495 
496 /*
497  * Parse line and stuff it in a struct netconfig
498  * Typical line might look like:
499  *	udp tpi_cots vb inet udp /dev/udp /usr/lib/ip.so,/usr/local/ip.so
500  *
501  * We return -1 if any of the tokens don't parse, or malloc fails.
502  *
503  * Note that we modify stringp (putting NULLs after tokens) and
504  * we set the ncp's string field pointers to point to these tokens within
505  * stringp.
506  */
507 
508 static int
509 parse_ncp(char *stringp,		/* string to parse */
510 	  struct netconfig *ncp)	/* where to put results */
511 {
512     char    *tokenp;	/* for processing tokens */
513     char    *lasts;
514     char    **nc_lookups;
515 
516     nc_error = NC_BADFILE;	/* nearly anything that breaks is for this reason */
517     stringp[strlen(stringp)-1] = '\0';	/* get rid of newline */
518     /* netid */
519     if ((ncp->nc_netid = strtok_r(stringp, "\t ", &lasts)) == NULL) {
520 	return (-1);
521     }
522 
523     /* semantics */
524     if ((tokenp = strtok_r(NULL, "\t ", &lasts)) == NULL) {
525 	return (-1);
526     }
527     if (strcmp(tokenp, NC_TPI_COTS_ORD_S) == 0)
528 	ncp->nc_semantics = NC_TPI_COTS_ORD;
529     else if (strcmp(tokenp, NC_TPI_COTS_S) == 0)
530 	ncp->nc_semantics = NC_TPI_COTS;
531     else if (strcmp(tokenp, NC_TPI_CLTS_S) == 0)
532 	ncp->nc_semantics = NC_TPI_CLTS;
533     else if (strcmp(tokenp, NC_TPI_RAW_S) == 0)
534 	ncp->nc_semantics = NC_TPI_RAW;
535     else
536 	return (-1);
537 
538     /* flags */
539     if ((tokenp = strtok_r(NULL, "\t ", &lasts)) == NULL) {
540 	return (-1);
541     }
542     for (ncp->nc_flag = NC_NOFLAG; *tokenp != '\0';
543 	    tokenp++) {
544 	switch (*tokenp) {
545 	case NC_NOFLAG_C:
546 	    break;
547 	case NC_VISIBLE_C:
548 	    ncp->nc_flag |= NC_VISIBLE;
549 	    break;
550 	case NC_BROADCAST_C:
551 	    ncp->nc_flag |= NC_BROADCAST;
552 	    break;
553 	default:
554 	    return (-1);
555 	}
556     }
557     /* protocol family */
558     if ((ncp->nc_protofmly = strtok_r(NULL, "\t ", &lasts)) == NULL) {
559 	return (-1);
560     }
561     /* protocol name */
562     if ((ncp->nc_proto = strtok_r(NULL, "\t ", &lasts)) == NULL) {
563 	return (-1);
564     }
565     /* network device */
566     if ((ncp->nc_device = strtok_r(NULL, "\t ", &lasts)) == NULL) {
567 	return (-1);
568     }
569     if ((tokenp = strtok_r(NULL, "\t ", &lasts)) == NULL) {
570 	return (-1);
571     }
572     if (strcmp(tokenp, NC_NOLOOKUP) == 0) {
573 	ncp->nc_nlookups = 0;
574 	ncp->nc_lookups = NULL;
575     } else {
576 	char *cp;	    /* tmp string */
577 
578 	if (ncp->nc_lookups != NULL)	/* from last visit */
579 	    free(ncp->nc_lookups);
580 	ncp->nc_lookups = NULL;
581 	ncp->nc_nlookups = 0;
582 	while ((cp = tokenp) != NULL) {
583 	    if ((nc_lookups = realloc(ncp->nc_lookups,
584 		(ncp->nc_nlookups + 1) * sizeof *ncp->nc_lookups)) == NULL) {
585 		    free(ncp->nc_lookups);
586 		    ncp->nc_lookups = NULL;
587 		    return (-1);
588 	    }
589 	    tokenp = _get_next_token(cp, ',');
590 	    ncp->nc_lookups = nc_lookups;
591 	    ncp->nc_lookups[ncp->nc_nlookups++] = cp;
592 	}
593     }
594     return (0);
595 }
596 
597 
598 /*
599  * Returns a string describing the reason for failure.
600  */
601 char *
602 nc_sperror(void)
603 {
604     const char *message;
605 
606     switch(nc_error) {
607     case NC_NONETCONFIG:
608 	message = _nc_errors[0];
609 	break;
610     case NC_NOMEM:
611 	message = _nc_errors[1];
612 	break;
613     case NC_NOTINIT:
614 	message = _nc_errors[2];
615 	break;
616     case NC_BADFILE:
617 	message = _nc_errors[3];
618 	break;
619     case NC_NOTFOUND:
620 	message = _nc_errors[4];
621 	break;
622     default:
623 	message = "Unknown network selection error";
624     }
625     /* LINTED const castaway */
626     return ((char *)message);
627 }
628 
629 /*
630  * Prints a message onto standard error describing the reason for failure.
631  */
632 void
633 nc_perror(const char *s)
634 {
635     fprintf(stderr, "%s: %s\n", s, nc_sperror());
636 }
637 
638 /*
639  * Duplicates the matched netconfig buffer.
640  */
641 static struct netconfig *
642 dup_ncp(struct netconfig *ncp)
643 {
644     struct netconfig	*p;
645     char	*tmp;
646     u_int	i;
647 
648     if ((tmp=malloc(MAXNETCONFIGLINE)) == NULL)
649 	return(NULL);
650     if ((p=(struct netconfig *)malloc(sizeof(struct netconfig))) == NULL) {
651 	free(tmp);
652 	return(NULL);
653     }
654     /*
655      * First we dup all the data from matched netconfig buffer.  Then we
656      * adjust some of the member pointer to a pre-allocated buffer where
657      * contains part of the data.
658      * To follow the convention used in parse_ncp(), we store all the
659      * necessary information in the pre-allocated buffer and let each
660      * of the netconfig char pointer member point to the right address
661      * in the buffer.
662      */
663     *p = *ncp;
664     p->nc_netid = (char *)strcpy(tmp,ncp->nc_netid);
665     tmp = strchr(tmp, '\0') + 1;
666     p->nc_protofmly = (char *)strcpy(tmp,ncp->nc_protofmly);
667     tmp = strchr(tmp, '\0') + 1;
668     p->nc_proto = (char *)strcpy(tmp,ncp->nc_proto);
669     tmp = strchr(tmp, '\0') + 1;
670     p->nc_device = (char *)strcpy(tmp,ncp->nc_device);
671     p->nc_lookups = (char **)malloc((size_t)(p->nc_nlookups+1) * sizeof(char *));
672     if (p->nc_lookups == NULL) {
673 	free(p->nc_netid);
674 	free(p);
675 	return(NULL);
676     }
677     for (i=0; i < p->nc_nlookups; i++) {
678 	tmp = strchr(tmp, '\0') + 1;
679 	p->nc_lookups[i] = (char *)strcpy(tmp,ncp->nc_lookups[i]);
680     }
681     return(p);
682 }
683