xref: /dragonfly/lib/libc/rpc/getnetconfig.c (revision ba96d07f7c72db2df2f4855fd9cef54db7111960)
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 *
__nc_error(void)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 *
setnetconfig(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 *
getnetconfig(void * handlep)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
endnetconfig(void * handlep)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 *
getnetconfigent(const char * netid)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
freenetconfigent(struct netconfig * netconfigp)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
parse_ncp(char * stringp,struct netconfig * ncp)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 *
nc_sperror(void)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
nc_perror(const char * s)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 *
dup_ncp(struct netconfig * ncp)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