1/*
2   Unix SMB/CIFS implementation.
3   Broadcom implementation of GNU C Library functions used by Samba
4   system utilities.
5
6   Copyright (C)
7   Copyright (C) Broadcom 2004
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22*/
23
24/*
25  We've had some issues with the standard system calls, so at least
26  temporarily, we've implemented our own versions.
27 */
28
29#include "includes.h"
30#include <unistd.h>
31#include <stdlib.h>
32#include <string.h>
33#include <errno.h>
34
35
36#define PASSWD_FILENAME			"/tmp/passwd"
37#define GROUP_FILENAME			"/tmp/group"
38
39
40static BOOL
41ug_lock( const char * filename, int open_flags, int *file_descriptor )
42{
43	int result;
44
45
46	*file_descriptor = open( filename, open_flags );
47	if (*file_descriptor == -1)
48	{
49		DEBUG(0, ("ERROR: open of %s for lock failed with %u: %s.\n", filename, errno, strerror(errno)));
50		return False;
51	}
52	result = flock(*file_descriptor, LOCK_EX);
53	if (result == -1)
54	{
55		DEBUG(0, ("ERROR: lock of %s failed with %u: %s.\n", filename, errno, strerror(errno)));
56		close(*file_descriptor);
57		return False;
58	}
59	return True;
60}
61
62
63struct passwd * brcm_getpwnam(const char *name)
64{
65	int fd;
66	static struct passwd result;
67	static char line[256];
68	FILE *fp;
69	char *tok;
70
71	if (name == NULL || *name == '\0')
72		return NULL;
73
74	//DEBUG(1, ("entered name=%s\n", name));
75
76	if (!ug_lock(PASSWD_FILENAME, O_RDONLY, &fd))
77	{
78		return NULL;
79	}
80
81	if ((fp = fdopen(fd, "r")) == NULL)
82	{
83		DEBUG(1, ("open failed, errno=%u, str='%s'\n", errno, strerror(errno)));
84		close(fd);
85		return NULL;
86	}
87
88	while (fgets(line, 256, fp) != NULL)
89	{
90		char *ptr = line;
91		//DEBUG(1, ("line='%s'\n", line));
92
93		tok = strsep( &ptr, ":" );
94		if (tok == NULL)
95			return NULL;
96		if (strcmp(name, tok) == 0)
97		{
98			/* Yeah, we found it.  Parse the rest. */
99			result.pw_name		= tok;
100			result.pw_passwd	= strsep( &ptr, ":" );
101			result.pw_uid		= strtol( strsep( &ptr, ":" ), NULL, 10 );
102			result.pw_gid		= strtol( strsep( &ptr, ":" ), NULL, 10 );
103			result.pw_gecos		= strsep( &ptr, ":" );
104			result.pw_dir		= strsep( &ptr, ":" );
105			result.pw_shell		= strsep( &ptr, ":" );
106
107			//DEBUG(1, ("match! %s:%s:%u:%u:%s:%s:%s\n", result.pw_name,result.pw_passwd,result.pw_uid,result.pw_gid,result.pw_gecos,result.pw_dir,result.pw_shell));
108
109			fclose( fp );
110			return &result;
111		}
112	}
113	fclose( fp );
114	return NULL;
115}
116
117
118struct passwd * brcm_getpwuid(uid_t uid)
119{
120	int fd;
121	static struct passwd result;
122	static char line[256];
123	FILE *fp;
124	char *tok;
125
126	//DEBUG(1, ("gew_getpwuid:1: entered uid=%u\n",uid));
127
128	if (!ug_lock(PASSWD_FILENAME, O_RDONLY, &fd))
129	{
130		return NULL;
131	}
132
133	if ((fp = fdopen(fd, "r")) == NULL)
134	{
135		DEBUG(1, ("gew_getpwuid:1: open failed, errno=%u, str='%s'\n", errno, strerror(errno)));
136		close(fd);
137		return NULL;
138	}
139
140	while (fgets(line, 256, fp) != NULL)
141	{
142		char *ptr = line;
143		//DEBUG(1, ("gew_getpwuid:1: line='%s'\n", line));
144
145		tok = strsep( &ptr, ":" );
146		if (tok == NULL)
147			return NULL;
148
149		/* Use 'result' as a temporary. */
150		result.pw_name		= tok;
151		result.pw_passwd	= strsep( &ptr, ":" );
152		result.pw_uid		= strtol( strsep( &ptr, ":" ), NULL, 10 );
153		result.pw_gid		= strtol( strsep( &ptr, ":" ), NULL, 10 );
154		result.pw_gecos		= strsep( &ptr, ":" );
155		result.pw_dir		= strsep( &ptr, ":" );
156		result.pw_shell		= strsep( &ptr, ":" );
157
158		if (uid == result.pw_uid)
159		{
160			//DEBUG(1, ("gew_getpwuid:1: match! %s:%s:%u:%u:%s:%s:%s\n", result.pw_name,result.pw_passwd,result.pw_uid,result.pw_gid,result.pw_gecos,result.pw_dir,result.pw_shell));
161
162			fclose( fp );
163			return &result;
164		}
165	}
166	fclose( fp );
167	return NULL;
168}
169
170static int brcm_pw_fd = -1;
171
172void brcm_setpwent(void)
173{
174        if (brcm_pw_fd != -1)
175		close(brcm_pw_fd);
176
177	brcm_pw_fd = open(GROUP_FILENAME, O_RDONLY);
178}
179
180void brcm_endpwent(void)
181{
182	if (brcm_pw_fd != -1)
183		close(brcm_pw_fd);
184	brcm_pw_fd = -1;
185}
186
187struct passwd *brcm_getpwent(void)
188{
189	if (brcm_pw_fd != -1)
190		return (brcm__getpwent(brcm_pw_fd));
191	return NULL;
192}
193
194
195
196#if 1
197struct group * brcm_getgrgid(gid_t gid)
198{
199	int fd;
200	static struct group *group;
201	FILE *fp;
202
203//	DEBUG(0, ("brcm_getgrgid: entered gid=%d\n",gid));
204	/*
205	if (!ug_lock(GROUP_FILENAME, O_RDONLY, &fd))
206	{
207		return NULL;
208	}
209
210	if ((fp = fdopen(fd, "r")) == NULL)
211	{
212		DEBUG(1, ("vkp_getgrgid:1: open failed, errno=%u, str='%s'\n", errno, strerror(errno)));
213		close(fd);
214		return NULL;
215	}
216        */
217
218	if ((fd = open(GROUP_FILENAME, O_RDONLY)) < 0)
219		return NULL;
220
221      	while ((group = (struct group *) brcm_getgrent(fd)) != NULL)
222		if (group->gr_gid == gid) {
223//		        DEBUG(0, ("brcm_getgrgid: found group with gid=%d\n",gid));
224			close(fd);
225			return group;
226		}
227
228	close(fd);
229	return NULL;
230}
231#endif
232
233#if 1
234struct group * brcm_getgrnam(const char *name)
235{
236	static struct group *group;
237	FILE *fp;
238	int fd;
239
240	if (name == NULL || *name == '\0')
241		return NULL;
242
243//DEBUG(0, ("brcm_getgrnam enterred name=%s\n", name));
244/*
245	if (!ug_lock(GROUP_FILENAME, O_RDONLY, &fd))
246	{
247		return NULL;
248	}
249
250	if ((fp = fdopen(fd, "r")) == NULL)
251	{
252		DEBUG(1, ("vkp_getgrgnam:1: open failed, errno=%u, str='%s'\n", errno, strerror(errno)));
253		close(fd);
254		return NULL;
255	}
256*/
257
258	if ((fd = open(GROUP_FILENAME, O_RDONLY)) < 0)
259		return NULL;
260
261      	while ((group = (struct group *) brcm_getgrent(fd)) != NULL)
262		if (!strcmp(group->gr_name,name)) {
263//		  DEBUG(0, ("brcm_getgrnam found group with name=%s\n", name));
264			close(fd);
265			return group;
266		}
267
268	close(fd);
269	return NULL;
270}
271#endif
272
273#if 0
274struct passwd * brcm_getpwuid(uid_t uid)
275{
276	static struct passwd result;
277	static char line[256];
278	FILE *fp;
279	char *tok;
280
281DEBUG(1, ("gew_getpwuid:1: entered uid=%u\n",uid));
282
283	if ((fp = fopen(PASSWD_FILENAME, "r")) == NULL)
284	{
285DEBUG(1, ("gew_getpwuid:1: open failed, errno=%u, str='%s'\n", errno, strerror(errno)));
286		return NULL;
287	}
288
289	while (fgets(line, 256, fp) != NULL)
290	{
291		char *ptr = line;
292DEBUG(1, ("gew_getpwuid:1: line='%s'\n", line));
293
294		tok = strsep( &ptr, ":" );
295		if (tok == NULL)
296			return NULL;
297
298		/* Use 'result' as a temporary. */
299		result.pw_name		= tok;
300		result.pw_passwd	= strsep( &ptr, ":" );
301		result.pw_uid		= strtol( strsep( &ptr, ":" ), NULL, 10 );
302		result.pw_gid		= strtol( strsep( &ptr, ":" ), NULL, 10 );
303		result.pw_gecos		= strsep( &ptr, ":" );
304		result.pw_dir		= strsep( &ptr, ":" );
305		result.pw_shell		= strsep( &ptr, ":" );
306
307		if (uid == result.pw_uid)
308		{
309DEBUG(1, ("gew_getpwuid:1: match! %s:%s:%u:%u:%s:%s:%s\n", result.pw_name,result.pw_passwd,result.pw_uid,result.pw_gid,result.pw_gecos,result.pw_dir,result.pw_shell));
310
311			fclose( fp );
312			return &result;
313		}
314	}
315	fclose( fp );
316	return NULL;
317}
318#endif
319
320
321/*
322 * Define GR_SCALE_DYNAMIC if you want grp to dynamically scale its read buffer
323 * so that lines of any length can be used.  On very very small systems,
324 * you may want to leave this undefined becasue it will make the grp functions
325 * somewhat larger (because of the inclusion of malloc and the code necessary).
326 * On larger systems, you will want to define this, because grp will _not_
327 * deal with long lines gracefully (they will be skipped).
328 */
329#undef GR_SCALE_DYNAMIC
330
331#ifndef GR_SCALE_DYNAMIC
332/*
333 * If scaling is not dynamic, the buffers will be statically allocated, and
334 * maximums must be chosen.  GR_MAX_LINE_LEN is the maximum number of
335 * characters per line in the group file.  GR_MAX_MEMBERS is the maximum
336 * number of members of any given group.
337 */
338#define GR_MAX_LINE_LEN 128
339/* GR_MAX_MEMBERS = (GR_MAX_LINE_LEN-(24+3+6))/9 */
340#define GR_MAX_MEMBERS 11
341
342#endif /* !GR_SCALE_DYNAMIC */
343
344/*
345 * This is the core group-file read function.  It behaves exactly like
346 * getgrent() except that it is passed a file descriptor.  getgrent()
347 * is just a wrapper for this function.
348 */
349struct group *brcm_getgrent(int grp_fd)
350{
351#ifndef GR_SCALE_DYNAMIC
352	static char line_buff[GR_MAX_LINE_LEN];
353	static char *members[GR_MAX_MEMBERS];
354#else
355	static char *line_buff = NULL;
356	static char **members = NULL;
357	short line_index;
358	short buff_size;
359#endif
360	static struct group group;
361	register char *ptr;
362	char *field_begin;
363	short member_num;
364	char *endptr;
365	int line_len;
366
367
368	/* We use the restart label to handle malformatted lines */
369  restart:
370#ifdef GR_SCALE_DYNAMIC
371	line_index = 0;
372	buff_size = 256;
373#endif
374
375#ifndef GR_SCALE_DYNAMIC
376	/* Read the line into the static buffer */
377	if ((line_len = read(grp_fd, line_buff, GR_MAX_LINE_LEN)) <= 0)
378		return NULL;
379	field_begin = strchr(line_buff, '\n');
380	if (field_begin != NULL)
381		lseek(grp_fd, (long) (1 + field_begin - (line_buff + line_len)),
382			  SEEK_CUR);
383	else {						/* The line is too long - skip it :-\ */
384
385		do {
386			if ((line_len = read(grp_fd, line_buff, GR_MAX_LINE_LEN)) <= 0)
387				return NULL;
388		} while (!(field_begin = strchr(line_buff, '\n')));
389		lseek(grp_fd, (long) ((field_begin - line_buff) - line_len + 1),
390			  SEEK_CUR);
391		goto restart;
392	}
393	if (*line_buff == '#' || *line_buff == ' ' || *line_buff == '\n' ||
394		*line_buff == '\t')
395		goto restart;
396	*field_begin = '\0';
397
398#else							/* !GR_SCALE_DYNAMIC */
399	line_buff = realloc(line_buff, buff_size);
400	while (1) {
401		if ((line_len = read(grp_fd, line_buff + line_index,
402							 buff_size - line_index)) <= 0)
403			return NULL;
404		field_begin = strchr(line_buff, '\n');
405		if (field_begin != NULL) {
406			lseek(grp_fd,
407				  (long) (1 + field_begin -
408						  (line_len + line_index + line_buff)), SEEK_CUR);
409			*field_begin = '\0';
410			if (*line_buff == '#' || *line_buff == ' '
411				|| *line_buff == '\n' || *line_buff == '\t')
412				goto restart;
413			break;
414		} else {				/* Allocate some more space */
415
416			line_index = buff_size;
417			buff_size += 256;
418			line_buff = realloc(line_buff, buff_size);
419		}
420	}
421#endif							/* GR_SCALE_DYNAMIC */
422
423	/* Now parse the line */
424	group.gr_name = line_buff;
425	ptr = strchr(line_buff, ':');
426	if (ptr == NULL)
427		goto restart;
428	*ptr++ = '\0';
429
430	group.gr_passwd = ptr;
431	ptr = strchr(ptr, ':');
432	if (ptr == NULL)
433		goto restart;
434	*ptr++ = '\0';
435
436	field_begin = ptr;
437	ptr = strchr(ptr, ':');
438	if (ptr == NULL)
439		goto restart;
440	*ptr++ = '\0';
441
442	group.gr_gid = (gid_t) strtoul(field_begin, &endptr, 10);
443	if (*endptr != '\0')
444		goto restart;
445
446	member_num = 0;
447	field_begin = ptr;
448
449#ifndef GR_SCALE_DYNAMIC
450	while ((ptr = strchr(ptr, ',')) != NULL) {
451		*ptr = '\0';
452		ptr++;
453		members[member_num] = field_begin;
454		field_begin = ptr;
455		member_num++;
456	}
457	if (*field_begin == '\0')
458		members[member_num] = NULL;
459	else {
460		members[member_num] = field_begin;
461		members[member_num + 1] = NULL;
462	}
463#else							/* !GR_SCALE_DYNAMIC */
464	free(members);
465	members = (char **) malloc((member_num + 1) * sizeof(char *));
466	for ( ; field_begin && *field_begin != '\0'; field_begin = ptr) {
467	    if ((ptr = strchr(field_begin, ',')) != NULL)
468		*ptr++ = '\0';
469	    members[member_num++] = field_begin;
470	    members = (char **) realloc(members,
471		    (member_num + 1) * sizeof(char *));
472	}
473	members[member_num] = NULL;
474#endif							/* GR_SCALE_DYNAMIC */
475
476	group.gr_mem = members;
477	return &group;
478}
479
480
481#if 0
482struct group *brcm_getgrent(int grp_fd)
483{
484#ifndef GR_SCALE_DYNAMIC
485	static char line_buff[GR_MAX_LINE_LEN];
486	static char *members[GR_MAX_MEMBERS];
487#else
488	static char *line_buff = NULL;
489	static char **members = NULL;
490	short line_index;
491	short buff_size;
492#endif
493	static struct group group;
494	register char *ptr;
495	char *field_begin;
496	short member_num;
497	char *endptr;
498	int line_len;
499	int count=0;
500
501
502	/* We use the restart label to handle malformatted lines */
503
504  restart:
505	DEBUG(0, ("vkp_getgrent top: count='%d'\n", count++));
506	//	return NULL;
507#ifdef GR_SCALE_DYNAMIC
508	line_index = 0;
509	buff_size = 256;
510#endif
511
512#ifndef GR_SCALE_DYNAMIC
513	/* Read the line into the static buffer */
514	if ((line_len = read(grp_fd, line_buff, GR_MAX_LINE_LEN)) <= 0)
515		return NULL;
516	DEBUG(0, ("vkp_getgrent: read line into buffer \n"));
517	field_begin = strchr(line_buff, '\n');
518	if (field_begin != NULL)
519	     {
520	        DEBUG(0, ("vkp_getgrent: before lseek \n"));
521		lseek(grp_fd, (long) (1 + field_begin - (line_buff + line_len)),
522			  SEEK_CUR);
523	        DEBUG(0, ("vkp_getgrent: after lseek \n"));
524	     }
525	else {						/* The line is too long - skip it :-\ */
526
527		do {
528			if ((line_len = read(grp_fd, line_buff, GR_MAX_LINE_LEN)) <= 0)
529				return NULL;
530		} while (!(field_begin = strchr(line_buff, '\n')));
531		lseek(grp_fd, (long) ((field_begin - line_buff) - line_len + 1),
532			  SEEK_CUR);
533		DEBUG(0, ("vkp_getgrent: restart line too long\n"));
534		goto restart;
535	}
536	if (*line_buff == '#' || *line_buff == ' ' || *line_buff == '\n' ||
537		*line_buff == '\t')
538	  {
539		DEBUG(0, ("vkp_getgrent: restart line has iffy chars\n"));
540		goto restart;
541	  }
542	*field_begin = '\0';
543
544#else							/* !GR_SCALE_DYNAMIC */
545	line_buff = realloc(line_buff, buff_size);
546	while (1) {
547		if ((line_len = read(grp_fd, line_buff + line_index,
548							 buff_size - line_index)) <= 0)
549			return NULL;
550		field_begin = strchr(line_buff, '\n');
551		if (field_begin != NULL) {
552			lseek(grp_fd,
553				  (long) (1 + field_begin -
554						  (line_len + line_index + line_buff)), SEEK_CUR);
555			*field_begin = '\0';
556			if (*line_buff == '#' || *line_buff == ' '
557				|| *line_buff == '\n' || *line_buff == '\t')
558				goto restart;
559			break;
560		} else {				/* Allocate some more space */
561
562			line_index = buff_size;
563			buff_size += 256;
564			line_buff = realloc(line_buff, buff_size);
565		}
566	}
567#endif							/* GR_SCALE_DYNAMIC */
568	DEBUG(0,("vkp_getgrent: parsing the line\n"));
569	DEBUG(0,("vkp_getgrent: line is %s\n",line_buff));
570	/* Now parse the line */
571	group.gr_name = line_buff;
572	DEBUG(0,("vkp_getgrent: gr_name is %s\n",group.gr_name));
573	ptr = strchr(line_buff, ':');
574	if (ptr == NULL)
575	{
576	        DEBUG(0, ("vkp_getgrent: restart parsing gr_name failed\n"));
577	        goto restart;
578	}
579	*ptr++ = '\0';
580	group.gr_passwd = ptr;
581	DEBUG(0,("vkp_getgrent: gr_pwd is %s\n",group.gr_passwd));
582	ptr = strchr(ptr, ':');
583	if (ptr == NULL)
584	{
585	        DEBUG(0, ("vkp_getgrent: restart parsing gr_pwd failed\n"));
586		goto restart;
587	}
588	*ptr++ = '\0';
589
590	field_begin = ptr;
591	ptr = strchr(ptr, ':');
592	if (ptr == NULL)
593	  {
594	         DEBUG(0, ("vkp_getgrent: restart parsing gr_gid failed\n"));
595		goto restart;
596	  }
597	*ptr++ = '\0';
598
599	group.gr_gid = (gid_t) strtoul(field_begin, &endptr, 10);
600	DEBUG(0,("vkp_getgrent: gr_gid is %d\n",group.gr_gid));
601	if (*endptr != '\0')
602		goto restart;
603
604	member_num = 0;
605	field_begin = ptr;
606
607#ifndef GR_SCALE_DYNAMIC
608	DEBUG(0,("vkp_getgrent: parse members\n"));
609	while ((ptr = strchr(ptr, ',')) != NULL) {
610		*ptr = '\0';
611		ptr++;
612		members[member_num] = field_begin;
613		field_begin = ptr;
614		member_num++;
615	}
616	DEBUG(0,("vkp_getgrent: parsed members\n"));
617	//	return(NULL);
618	if (*field_begin == '\0')
619		members[member_num] = NULL;
620	else {
621		members[member_num] = field_begin;
622		members[member_num + 1] = NULL;
623	}
624#else							/* !GR_SCALE_DYNAMIC */
625	free(members);
626	members = (char **) malloc((member_num + 1) * sizeof(char *));
627	for ( ; field_begin && *field_begin != '\0'; field_begin = ptr) {
628	    if ((ptr = strchr(field_begin, ',')) != NULL)
629		*ptr++ = '\0';
630	    members[member_num++] = field_begin;
631	    members = (char **) realloc(members,
632		    (member_num + 1) * sizeof(char *));
633	}
634	members[member_num] = NULL;
635#endif							/* GR_SCALE_DYNAMIC */
636
637	group.gr_mem = members;
638	DEBUG(0,("vkp_getgrent: parsed members are %s\n",group.gr_mem));
639	return(NULL);
640	return &group;
641}
642#endif
643
644#define PWD_BUFFER_SIZE 256
645
646/* This isn't as flash as my previous version -- it doesn't dynamically
647  scale down the gecos on too-long lines, but it also makes fewer syscalls,
648  so it's probably nicer.  Write me if you want the old version.  Maybe I
649  should include it as a build-time option... ?
650  -Nat <ndf@linux.mit.edu> */
651
652struct passwd *brcm__getpwent(int pwd_fd)
653{
654	static char line_buff[PWD_BUFFER_SIZE];
655	static struct passwd passwd;
656	char *field_begin;
657	char *endptr;
658	char *gid_ptr=NULL;
659	char *uid_ptr=NULL;
660	int line_len;
661	int i;
662
663	/* We use the restart label to handle malformatted lines */
664  restart:
665	/* Read the passwd line into the static buffer using a minimal of
666	   syscalls. */
667	if ((line_len = read(pwd_fd, line_buff, PWD_BUFFER_SIZE)) <= 0)
668		return NULL;
669	field_begin = strchr(line_buff, '\n');
670	if (field_begin != NULL)
671		lseek(pwd_fd, (long) (1 + field_begin - (line_buff + line_len)),
672			  SEEK_CUR);
673	else {						/* The line is too long - skip it. :-\ */
674
675		do {
676			if ((line_len = read(pwd_fd, line_buff, PWD_BUFFER_SIZE)) <= 0)
677				return NULL;
678		} while (!(field_begin = strchr(line_buff, '\n')));
679		lseek(pwd_fd, (long) (field_begin - line_buff) - line_len + 1,
680			  SEEK_CUR);
681		goto restart;
682	}
683	if (*line_buff == '#' || *line_buff == ' ' || *line_buff == '\n' ||
684		*line_buff == '\t')
685		goto restart;
686	*field_begin = '\0';
687
688	/* We've read the line; now parse it. */
689	field_begin = line_buff;
690	for (i = 0; i < 7; i++) {
691		switch (i) {
692		case 0:
693			passwd.pw_name = field_begin;
694			break;
695		case 1:
696			passwd.pw_passwd = field_begin;
697			break;
698		case 2:
699			uid_ptr = field_begin;
700			break;
701		case 3:
702			gid_ptr = field_begin;
703			break;
704		case 4:
705			passwd.pw_gecos = field_begin;
706			break;
707		case 5:
708			passwd.pw_dir = field_begin;
709			break;
710		case 6:
711			passwd.pw_shell = field_begin;
712			break;
713		}
714		if (i < 6) {
715			field_begin = strchr(field_begin, ':');
716			if (field_begin == NULL)
717				goto restart;
718			*field_begin++ = '\0';
719		}
720	}
721	passwd.pw_gid = (gid_t) strtoul(gid_ptr, &endptr, 10);
722	if (*endptr != '\0')
723		goto restart;
724
725	passwd.pw_uid = (uid_t) strtoul(uid_ptr, &endptr, 10);
726	if (*endptr != '\0')
727		goto restart;
728
729	return &passwd;
730}
731
732