|
|
1.1 root 1: /* A 'C' version of the shellscript dishinit
2: * By Steve Titcombe
3: *
4: * Most of this has fixed calls to other functions, and will require going
5: * through again to strip out all unnecessary error trapping, etc.
6: * (Utterly Horrible Hack.)
7: */
8:
9: #ifndef lint
10: static char *rcsid = "$Header: /f/osi/others/quipu/uips/dish/RCS/quipurc.c,v 7.4 90/07/27 08:45:06 mrose Exp $";
11: #endif
12:
13: /*
14: * NOTICE
15: *
16: * Acquisition, use, and distribution of this module and related
17: * materials are subject to the restrictions of a license agreement.
18: * Consult the Preface in the User's Manual for the full terms of
19: * this agreement.
20: *
21: */
22:
23: #include <fcntl.h>
24: #include "manifest.h"
25: #include <sys/stat.h>
26: #include <signal.h>
27: #include <pwd.h>
28: #include "quipu/read.h"
29: #include "quipu/sequence.h"
30: #include "quipu/name.h"
31: #include "quipu/bind.h"
32: #include "quipu/dsp.h"
33: #include "quipu/ds_error.h"
34: #include "tailor.h"
35: #include "quipu/util.h"
36: #include "quipu/dua.h"
37: #include "quipu/ds_search.h"
38: #include "quipu/list.h"
39: #include "quipu/entry.h"
40: #include "quipu/modify.h"
41:
42: #define ORG_PERSON "thornPerson & quipuObject"
43: /* this should probably go elsewhere !!! */
44:
45: LLog *log_dua;
46: DN sequence_dn() ;
47: DN dn, moddn ;
48: DN fixed_pos = NULLDN ;
49: PS opt;
50: PS rps;
51: PS fileps ;
52:
53: FILE *fp_quipurc ;
54: FILE *fp_draft ;
55: FILE *fp_tailor ;
56:
57: Filter get_filter ();
58: struct entrymod *ems_append() ;
59: struct entrymod *modify_avs() ;
60:
61: static struct ds_bind_arg bindarg ;
62: static struct ds_bind_arg bindresult ;
63: static struct ds_bind_error binderr ;
64:
65: struct ds_read_arg read_arg;
66: struct DSError read_error;
67:
68: struct ds_read_result read_result;
69: struct ds_modifyentry_arg mod_arg ;
70: struct DSError mod_error ;
71:
72: struct DSError search_error;
73: struct ds_search_result search_result;
74: static struct ds_search_arg search_arg =
75: {
76: default_common_args,
77: NULLDN,
78: SRA_ONELEVEL,
79: NULLFILTER, /* filter */
80: FALSE,
81: { /* eis */
82: FALSE,
83: NULLATTR,
84: EIS_ATTRIBUTESANDVALUES
85: }
86: };
87:
88: static struct dua_sequence *current_sequence = NULL_DS;
89: struct entrymod *emnew ;
90:
91: AV_Sequence avst = NULLAV ;
92: Attr_Sequence as_flag = NULLATTR ;
93: Attr_Sequence trail = NULLATTR ;
94: Attr_Sequence eptr ;
95: Attr_Sequence temp ;
96: Attr_Sequence as ;
97: AttributeType at;
98:
99: extern Attr_Sequence get_attributes() ;
100: extern char *TidyString() ;
101:
102: Entry current_entry ;
103: Entry entry_ptr ;
104:
105: char Manager[LINESIZE] ;
106: char Password[LINESIZE] ;
107: char Local[LINESIZE] ;
108: char filterstring[LINESIZE] ;
109:
110: main()
111: {
112: struct passwd *pw_entry ;
113: struct passwd *getpwuid() ;
114: struct stat buf ;
115:
116: int i = 1 ;
117: int uid ;
118: int um ;
119: char pass1[LINESIZE] ;
120: char pass2[LINESIZE] ;
121: char Read_in_Stuff[LINESIZE] ;
122: char **vecptr ;
123: char *tmpdraft ;
124: char home_dir[LINESIZE] ;
125: char *p, *part1, *part2 ;
126: char quipurc_file[100] ;
127: char tailor_file[100] ;
128: char user_name[9] ;
129: char *localptr = Local ;
130: char print_format = EDBOUT ;
131: EntryInfo *ptr ;
132: static CommonArgs ca = default_common_args;
133:
134: vecptr = (char **) malloc(100) ;
135: vecptr[0] = malloc (LINESIZE) ;
136: (void) strcpy(vecptr[0], "showentry") ;
137: (void) strcpy(pass1, "x") ;
138: (void) strcpy(pass2, "y") ;
139: tmpdraft = malloc (LINESIZE) ;
140: (void) strcpy(tmpdraft, "/tmp/dish-") ;
141:
142: if ((opt = ps_alloc (std_open)) == NULLPS)
143: fatal (-62, "ps_alloc failed");
144: if (std_setup (opt, stderr) == NOTOK)
145: fatal (-63, "std_setup failed");
146: if ((rps = ps_alloc (std_open)) == NULLPS)
147: fatal (-64, "ps_alloc 2 failed");
148: if (std_setup (rps, stdout) == NOTOK)
149: fatal (-65, "std_setup 2 failed");
150: (void) strcpy(filterstring, "userid=") ;
151:
152: /* Sort out files, userids etc. */
153: uid=getuid() ;
154: if ((pw_entry=getpwuid(uid)) == 0)
155: {
156: ps_printf(rps, "Who are you? (no name for your uid number)\n") ;
157: exit(1) ;
158: }
159: (void) strcpy(user_name, pw_entry->pw_name) ;
160: (void) strcat(tmpdraft, user_name) ;
161:
162: if (getenv("HOME") == 0)
163: {
164: ps_printf(rps, "No home directory?!!") ;
165: (void) strcpy(home_dir, pw_entry->pw_dir) ;
166: }
167: else
168: {
169: (void) strcpy(home_dir, getenv("HOME")) ;
170: }
171:
172: (void) strcpy(quipurc_file, home_dir) ;
173: (void) strcat(quipurc_file, "/.quipurc") ;
174:
175: (void) strcpy(tailor_file, isodefile ("dishinit", 1));
176:
177: Manager[0] = 0;
178: Password[0] = 0;
179: Local[0] = 0;
180:
181: (void) stat(tailor_file, &buf) ;
182: (void) seteuid(buf.st_uid) ; /* set effective to enable */
183: /* us to read protected file */
184:
185: if ((fp_tailor = fopen(tailor_file, "r")) == 0)
186: {
187: ps_print(rps, "Can't open Tailor File. Abort.\n") ;
188: exit(1) ;
189: }
190:
191: while (fgets (Read_in_Stuff, LINESIZE, fp_tailor) != 0)
192: {
193: if (!strcmp(Read_in_Stuff, "##Anything after this line is copied into the users ~/.quipurc file\n"))
194: {
195: break ;
196: }
197:
198: p = SkipSpace (Read_in_Stuff);
199: if (( *p == '#') || (*p == '\0'))
200: continue; /* ignore comments and blanks */
201:
202: part1 = p;
203: if ((part2 = index (p,':')) == NULLCP) {
204: ps_printf (opt,"Seperator missing '%s'. Ignoring..\n",p);
205: }
206:
207: *part2++ = '\0';
208: part2 = TidyString (part2);
209:
210: if (lexequ(part1, "manager") == 0)
211: {
212: (void) strcpy(Manager, part2) ;
213: }
214: else
215: if (lexequ(part1, "password") == 0)
216: {
217: (void) strcpy(Password, part2) ;
218: }
219: else
220: if (lexequ(part1, "local") == 0)
221: {
222: (void) strcpy(Local, part2) ;
223: }
224: else
225: {
226: ps_printf(rps, "Error in tailor. What's a %s?\n", part1) ;
227: }
228:
229: }
230: (void) setuid(uid) ; /* Restore Userid to original user. */
231:
232: /* create ~/.quipurc file. NB this does eradicate anything in there.
233: * (Theoretically nothing.)
234: */
235:
236: if (Manager[0] == 0) {
237: ps_print(rps, "Can't find out the managers name\n") ;
238: exit(1) ;
239: }
240: if (Password[0] == 0) {
241: ps_print(rps, "Can't find out the managers password\n") ;
242: exit(1) ;
243: }
244: if (Local[0] == 0) {
245: ps_print(rps, "Can't find out where to search\n") ;
246: exit(1) ;
247: }
248:
249: um = umask(0177) ;
250: if ((fp_quipurc = fopen(quipurc_file, "w")) == 0)
251: {
252: ps_printf(rps, "Can't open ~/.quipurc. Aborting..\n") ;
253: exit(1) ;
254: }
255: (void) umask(um) ;
256:
257: if ((fileps = ps_alloc(std_open)) == NULLPS)
258: {
259: fatal (-66, "ps_alloc 2 failed");
260: }
261: if (std_setup (fileps, fp_quipurc) == NOTOK)
262: {
263: fatal (-67, "std_setup 2 failed");
264: }
265:
266:
267: /* Sorting out the bind section */
268: quipu_syntaxes() ; /* set up the needed function pointers */
269: dsap_init(&i, &vecptr) ;
270:
271: (void) strcpy(bindarg.dba_passwd, Password) ;
272: bindarg.dba_version = DBA_VERSION_V1988;
273: bindarg.dba_passwd_len = strlen(bindarg.dba_passwd) ;
274:
275: if ((bindarg.dba_dn = str2dn (Manager)) == NULLDN)
276: {
277: ps_printf (opt,"Invalid Manager name %s (???!)\n",Manager) ;
278: exit(1) ;
279: }
280:
281: if (ds_bind (&bindarg, &binderr, &bindresult) != OK)
282: {
283: ps_printf(rps, "Can't bind as the manager.\n") ;
284: exit(1);
285: }
286: /* Hopefully, should be successfully bound */
287:
288: /*
289: * We now call the search stuff with the right bits, to see if we can get a
290: * match of uid='user_name'. Once there, we echo lots of information from
291: * their entry out to the .quipurc file.
292: * Hopefully there should only be one match. This assumes that ALL dir info
293: * up to date, and that SG do not allow multiple users with the same login.
294: */
295:
296: /* set up the appropriate structures and defaults. */
297:
298: search_arg.sra_common = ca; /* struct copy */
299: search_arg.sra_common.ca_servicecontrol.svc_sizelimit = 2 ;
300: search_arg.sra_eis.eis_allattributes = FALSE ;
301: search_arg.sra_searchaliases = FALSE;
302: search_arg.sra_subset = SRA_ONELEVEL;
303: search_arg.sra_eis.eis_infotypes = EIS_ATTRIBUTESANDVALUES ;
304: search_arg.sra_eis.eis_select = NULLATTR ;
305: search_arg.sra_eis.eis_allattributes = TRUE ;
306: search_arg.sra_filter = filter_alloc() ;
307: /* Default filter. */
308: search_arg.sra_filter->flt_next = NULLFILTER;
309: search_arg.sra_filter->flt_type = FILTER_ITEM;
310: search_arg.sra_filter->FUFILT = NULLFILTER;
311:
312:
313: if (*localptr == '@')
314: {
315: localptr++;
316: }
317: if ((search_arg.sra_baseobject = str2dn(localptr)) == NULLDN)
318: {
319: ps_printf (opt,"Invalid sequence in username %s.\n", localptr);
320: exit(1) ;
321: }
322:
323: (void) strcat(filterstring, user_name) ;
324:
325: search_arg.sra_filter->flt_un.flt_un_item.fi_type = FILTERITEM_EQUALITY ;
326:
327: if ((search_arg.sra_filter->flt_un.flt_un_item.fi_un.fi_un_ava.ava_type = AttrT_new ("userid")) == NULLAttrT)
328: {
329: ps_printf(rps, "Oops, userid is not a valid attr type. ABORT!!\n") ;
330: exit(1) ;
331: }
332: if ((search_arg.sra_filter->flt_un.flt_un_item.fi_un.fi_un_ava.ava_value = str2AttrV (user_name, search_arg.sra_filter->flt_un.flt_un_item.fi_un.fi_un_ava.ava_type->oa_syntax)) == NULLAttrV)
333: {
334: ps_printf(rps, "%s is not a valid attribute value.\n", user_name) ;
335: }
336:
337: /* call search */
338: /* We now ought to be in the right place, and with the search stuff set,
339: * ready to call search, and receive one (or no) entry back, which then
340: * gets processed accordingly.
341: */
342:
343: if (ds_search (&search_arg, &search_error, &search_result) != DS_OK)
344: {
345: ps_printf(rps, "Search failed...\n") ;
346: exit (1) ;
347: /* This is not the same as coming back with */
348: /* message "search failed to find anything. */
349: }
350:
351: /* If the user does not exist in the DIT, print out the limited .quipurc
352: * and the warning message, and allow the user to play DISH.
353: */
354:
355: if (search_result.CSR_entries == NULLENTRYINFO)
356: {
357: ps_printf(opt, "Unfortunately, you seem to have no entry in\n") ;
358: ps_printf(opt, "the directory. Contact '%s' who should be able to help.\n", Manager) ;
359: ps_printf(opt, "In the mean time, you can read, but not write.\n") ;
360: }
361: else
362: {
363: ptr = search_result.CSR_entries ;
364: dn = dn_cpy(ptr->ent_dn) ; /* Essence of move user_name. */
365:
366: /* collect the info and put it into current_entry */
367:
368: /* Set up the desired attribute type to be read*/
369: /* from read.c */
370: if ((at = AttrT_new ("userPassword")) != NULLAttrT)
371: {
372: as_flag = as_merge (as_flag, as_comp_new (AttrT_cpy (at), NULLAV, NULLACL_INFO));
373: }
374: else
375: {
376: ps_printf(rps, "Oops, Serious error. unknown attribute type 'userPassword'.\n") ;
377: exit(1) ;
378: }
379:
380: if ((current_entry = local_find_entry (dn, FALSE)) == NULLENTRY)
381: {
382: read_arg.rda_common = ca; /* struct copy */
383: read_arg.rda_object = dn;
384: read_arg.rda_eis.eis_infotypes = EIS_ATTRIBUTESANDVALUES;
385: read_arg.rda_eis.eis_allattributes = TRUE ;
386: read_arg.rda_eis.eis_select = NULLATTR ;
387:
388: if (ds_read (&read_arg, &read_error, &read_result) != DS_OK)
389: {
390: ps_printf(rps, "We even seem to be having problems reading\n" ) ;
391: ps_printf(rps, "an entry we searched and found!! HELP!!\n") ;
392: exit(1) ;
393: }
394: if (read_result.rdr_entry.ent_attr == NULLATTR)
395: {
396: ps_printf(rps, "No attributes present. Even though\n") ;
397: ps_printf(rps, "we found you by userid attribute!!! HELP!!\n") ;
398: exit (1) ;
399: }
400: cache_entry (&(read_result.rdr_entry), read_arg.rda_eis.eis_allattributes, TRUE) ;
401: }
402:
403: if ((current_entry = local_find_entry (dn, FALSE)) == NULLENTRY)
404: {
405: ps_printf(rps, "We still have nothing.Even after reading? Abort.\n") ;
406: exit(1) ;
407: }
408:
409: ps_printf(fileps, "username: ") ;
410: dn_print(fileps, dn, EDBOUT) ;
411: ps_printf(fileps, "\n") ;
412:
413: ps_printf(fileps, "me: ") ;
414: dn_print(fileps, dn, EDBOUT) ;
415: ps_printf(fileps, "\n") ;
416:
417: /* now showattribute -nokey to display it. */
418:
419: ps_printf(fileps, "password: ") ;
420: for (eptr = current_entry->e_attributes; eptr != NULLATTR;
421: eptr = eptr->attr_link)
422: {
423: /* Tiptoe through the list of types until one matches, and then print value. */
424: if (AttrT_cmp (eptr->attr_type, at) == 0)
425: {
426: avs_print (fileps, eptr->attr_value,print_format);
427: break;
428: }
429: }
430:
431: if (eptr == NULLATTR)
432: {
433: while( strcmp(pass1, pass2))
434: {
435: ps_printf(opt, "You need a password...\n") ;
436: (void) strcpy(pass1, getpassword("Enter Password: ")) ;
437: (void) strcpy(pass2, getpassword("Re-enter password: ")) ;
438: if (strcmp(pass1, pass2))
439: {
440: ps_printf(opt, "\nMismatch - Try again.\n") ;
441: }
442: }
443: ps_printf(fileps, "%s\n", pass1) ;
444:
445: um = umask(0177) ;
446: if ((fp_draft = fopen(tmpdraft, "w")) == 0)
447: {
448: ps_print(rps, "Can't open draft file... Abort.\n") ;
449: exit(1) ;
450: }
451: (void) umask(um) ;
452:
453: (void) fprintf(fp_draft, "UserPassword = %s\n", pass1) ;
454: (void) fprintf(fp_draft, "acl = self # write # attributes # acl $ userPassword\n") ;
455: (void) fprintf(fp_draft, "acl = others # compare # attributes # acl $ userPassword\n\n") ;
456: (void) fclose(fp_draft) ;
457:
458: if ((fp_draft = fopen (tmpdraft, "r")) == NULL) {
459: ps_printf (opt, "Can't open draft entry %s\n", tmpdraft);
460: exit(1) ;
461: }
462:
463: entry_ptr = get_default_entry (NULLENTRY);
464: entry_ptr->e_attributes = get_attributes (fp_draft);
465:
466: (void) fclose (fp_draft);
467:
468: mod_arg.mea_common = ca; /* struct copy */
469: mod_arg.mea_object = dn;
470: for (moddn = dn ; moddn->dn_parent != NULLDN; moddn=moddn->dn_parent)
471: ;
472: entry_ptr->e_name = rdn_cpy (moddn->dn_rdn);
473:
474: /* add rdn as attribute */
475: avst = avs_comp_new (AttrV_cpy (&entry_ptr->e_name->rdn_av));
476: temp = as_comp_new (AttrT_cpy (entry_ptr->e_name->rdn_at), avst, NULLACL_INFO);
477: entry_ptr->e_attributes = as_merge (entry_ptr->e_attributes, temp);
478:
479: for (as = entry_ptr->e_attributes; as != NULLATTR; as = as->attr_link)
480: {
481: emnew = NULLMOD;
482: trail = as->attr_link;
483: as->attr_link = NULLATTR;
484: temp = current_entry->e_attributes;
485: for (; temp != NULLATTR; temp = temp->attr_link)
486: if (AttrT_cmp (as->attr_type, temp->attr_type) == 0)
487: {
488: /* found it - does it need changing ? */
489: if (avs_cmp (as->attr_value, temp->attr_value) != 0)
490: emnew = modify_avs (as->attr_value, temp->attr_value,as->attr_type);
491: break;
492: }
493:
494: if (temp == NULLATTR)
495: {
496: emnew = em_alloc ();
497: emnew->em_type = EM_ADDATTRIBUTE;
498: emnew->em_what = as_cpy(as);
499: emnew->em_next = NULLMOD;
500: }
501: if (emnew != NULLMOD)
502: {
503: mod_arg.mea_changes = ems_append (mod_arg.mea_changes,emnew);
504: }
505: as->attr_link = trail;
506: }
507:
508: while (ds_modifyentry (&mod_arg, &mod_error) != DS_OK)
509: {
510: if (dish_error (opt, &mod_error) == 0)
511: {
512: ps_printf(rps,"We have a dish error. Bye.\n") ;
513: entry_free (entry_ptr);
514: exit(1) ;
515: }
516: mod_arg.mea_object = mod_error.ERR_REFERRAL.DSE_ref_candidates->cr_name;
517: }
518: ps_print (rps, "Modified ");
519: dn_print (rps, dn, EDBOUT);
520: ps_print (rps, "\n");
521: delete_cache (dn); /* re-cache when next read */
522:
523: entry_free (entry_ptr);
524: ems_part_free (mod_arg.mea_changes);
525: }
526: }
527:
528: while(fgets(Read_in_Stuff, LINESIZE, fp_tailor) != 0)
529: {
530: fputs(Read_in_Stuff, fp_quipurc) ;
531: }
532:
533: (void) fclose(fp_quipurc) ;
534: (void) fclose(fp_tailor) ;
535:
536: /* (void) fprintf(fp_quipurc, "dsap: local_dit \"%s\"\n", Local) ;
537: (void) fprintf(fp_quipurc, "notype: acl\n") ;
538: (void) fprintf(fp_quipurc, "notype: treestructure\n") ;
539: (void) fprintf(fp_quipurc, "notype: masterdsa\n") ;
540: (void) fprintf(fp_quipurc, "notype: slavedsa\n") ;
541: (void) fprintf(fp_quipurc, "notype: objectclass\n") ;
542: (void) fprintf(fp_quipurc, "cache_time: 30\n") ;
543: (void) fprintf(fp_quipurc, "connect_time: 2\n") ;
544: */
545: (void) ds_unbind() ;
546: (void) unlink(tmpdraft) ;
547: }
548:
549: void
550: advise()
551: {
552: }
553:
554: void
555: set_sequence()
556: {
557: }
558:
559: void
560: unset_sequence()
561: {
562: }
563:
564: dish_error (ps,error)
565: PS ps;
566: struct DSError * error;
567: {
568:
569: if (error->dse_type == DSE_ABANDONED) {
570: ps_printf (ps,"(DAP call interrupted - abandon successful)\n");
571: return (0);
572: }
573:
574: if (error->dse_type == DSE_ABANDON_FAILED) {
575: ps_printf (ps,"(DAP call interrupted - abandon unsuccessful)\n");
576: return (0);
577: }
578:
579: if (error->dse_type == DSE_INTRERROR) {
580: ps_printf (ps,"(DAP call interrupted)\n");
581: return (0);
582: }
583:
584: ds_error (ps,error);
585:
586: return (0);
587: }
588:
589: DN sequence_dn(y)
590: int y;
591: {
592: struct dua_seq_entry * ptr;
593: register int x = 1;
594:
595: if (current_sequence == NULL_DS)
596: return (NULLDN);
597:
598: for (ptr=current_sequence->ds_data;
599: (ptr != NULL_DE) && (x<y);
600: ptr=ptr->de_next,x++)
601: ;
602:
603: if (ptr == NULL_DE)
604: return (NULLDN);
605: if ( x == y )
606: return (ptr->de_name);
607: return (NULLDN);
608:
609: }
610:
611: struct entrymod * ems_append (a,b)
612: struct entrymod *a;
613: struct entrymod *b;
614: {
615: struct entrymod *ptr;
616:
617: if ((ptr = a) == NULLMOD)
618: return b;
619:
620: for ( ; ptr->em_next != NULLMOD; ptr = ptr->em_next)
621: ;
622:
623: ptr->em_next = b;
624: return a;
625: }
626:
627: struct entrymod * modify_avs (a,b,ent_mod_at)
628: AV_Sequence a;
629: AV_Sequence b;
630: AttributeType ent_mod_at;
631: {
632: AV_Sequence x;
633: AV_Sequence y;
634: struct entrymod *em = NULLMOD, *em_new;
635: int removed_all = TRUE;
636:
637: for (x=b; x != NULLAV; x=x->avseq_next) {
638: em_new = NULLMOD;
639: for (y=a; y != NULLAV; y=y->avseq_next)
640: if (AttrV_cmp (&x->avseq_av,&y->avseq_av) == 0)
641: break;
642: if (y == NULLAV) {
643: em_new = em_alloc ();
644: em_new->em_type = EM_REMOVEVALUES;
645: em_new->em_what = as_comp_new (ent_mod_at,avs_comp_new(&x->avseq_av),NULLACL_INFO);
646: em_new->em_next = NULLMOD;
647: } else
648: removed_all = FALSE;
649: if (em_new != NULLMOD)
650: em = ems_append (em,em_new);
651: }
652:
653: if (removed_all) {
654: ems_part_free (em);
655: em_new = em_alloc ();
656: em_new->em_type = EM_REMOVEATTRIBUTE;
657: em_new->em_what = as_comp_new (ent_mod_at,b,NULLACL_INFO);
658: em_new->em_next = em_alloc();
659: em_new->em_next->em_type = EM_ADDATTRIBUTE;
660: em_new->em_next->em_what = as_comp_new (ent_mod_at,avs_cpy(a),NULLACL_INFO);
661: em_new->em_next->em_next = NULLMOD;
662: return (em_new);
663: }
664:
665: for (x=a; x != NULLAV; x=x->avseq_next) {
666: em_new = NULLMOD;
667: for (y=b; y != NULLAV; y=y->avseq_next)
668: if (AttrV_cmp (&x->avseq_av,&y->avseq_av) == 0)
669: break;
670: if (y == NULLAV) {
671: em_new = em_alloc ();
672: em_new->em_type = EM_ADDVALUES;
673: em_new->em_what = as_comp_new (ent_mod_at,avs_comp_new(&x->avseq_av),NULLACL_INFO);
674: em_new->em_next = NULLMOD;
675: }
676: if (em_new != NULLMOD)
677: em = ems_append (em,em_new);
678: }
679:
680:
681: return (em);
682: }
683:
684: ems_part_free(emp)
685: struct entrymod *emp;
686: {
687: if(emp == NULLMOD)
688: return;
689: ems_part_free(emp->em_next);
690: free((char *)emp);
691: }
692:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.