]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/user-util.c
util: introduce memcmp_safe()
[thirdparty/systemd.git] / src / basic / user-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
b1d4f8e1 2
11c3a366
TA
3#include <alloca.h>
4#include <errno.h>
5#include <fcntl.h>
b1d4f8e1 6#include <grp.h>
cf0fbc49 7#include <pwd.h>
11c3a366
TA
8#include <stddef.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <sys/stat.h>
14#include <unistd.h>
e4631b48 15#include <utmp.h>
b1d4f8e1 16
b5efdb8a 17#include "alloc-util.h"
e929bee0 18#include "fd-util.h"
36d85478 19#include "fileio.h"
f97b34a6 20#include "format-util.h"
b1d4f8e1 21#include "macro.h"
be39ccf3 22#include "missing.h"
6bedfcbb 23#include "parse-util.h"
b1d4f8e1 24#include "path-util.h"
6bedfcbb 25#include "string-util.h"
be39ccf3 26#include "strv.h"
6bedfcbb 27#include "user-util.h"
e4631b48 28#include "utf8.h"
b1d4f8e1
LP
29
30bool uid_is_valid(uid_t uid) {
31
1429dfe5
LP
32 /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.436. */
33
b1d4f8e1 34 /* Some libc APIs use UID_INVALID as special placeholder */
b1d52773 35 if (uid == (uid_t) UINT32_C(0xFFFFFFFF))
b1d4f8e1
LP
36 return false;
37
38 /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */
b1d52773 39 if (uid == (uid_t) UINT32_C(0xFFFF))
b1d4f8e1
LP
40 return false;
41
42 return true;
43}
44
b1d52773
LP
45int parse_uid(const char *s, uid_t *ret) {
46 uint32_t uid = 0;
b1d4f8e1
LP
47 int r;
48
49 assert(s);
50
b1d52773
LP
51 assert_cc(sizeof(uid_t) == sizeof(uint32_t));
52 r = safe_atou32(s, &uid);
b1d4f8e1
LP
53 if (r < 0)
54 return r;
55
b1d4f8e1
LP
56 if (!uid_is_valid(uid))
57 return -ENXIO; /* we return ENXIO instead of EINVAL
58 * here, to make it easy to distuingish
ba60af86 59 * invalid numeric uids from invalid
b1d4f8e1
LP
60 * strings. */
61
b1d52773
LP
62 if (ret)
63 *ret = uid;
b1d4f8e1
LP
64
65 return 0;
66}
67
b1d4f8e1
LP
68char* getlogname_malloc(void) {
69 uid_t uid;
70 struct stat st;
71
72 if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
73 uid = st.st_uid;
74 else
75 uid = getuid();
76
d0260817 77 return uid_to_name(uid);
b1d4f8e1
LP
78}
79
80char *getusername_malloc(void) {
81 const char *e;
82
83 e = getenv("USER");
84 if (e)
85 return strdup(e);
86
d0260817 87 return uid_to_name(getuid());
b1d4f8e1
LP
88}
89
90int get_user_creds(
91 const char **username,
92 uid_t *uid, gid_t *gid,
93 const char **home,
94 const char **shell) {
95
96 struct passwd *p;
97 uid_t u;
98
99 assert(username);
100 assert(*username);
101
7e61fd02
LP
102 /* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode
103 * their user record data. */
b1d4f8e1 104
7e61fd02 105 if (STR_IN_SET(*username, "root", "0")) {
b1d4f8e1
LP
106 *username = "root";
107
108 if (uid)
109 *uid = 0;
b1d4f8e1
LP
110 if (gid)
111 *gid = 0;
112
113 if (home)
114 *home = "/root";
115
116 if (shell)
117 *shell = "/bin/sh";
118
119 return 0;
120 }
121
24eccc34
LP
122 if (synthesize_nobody() &&
123 STR_IN_SET(*username, NOBODY_USER_NAME, "65534")) {
7e61fd02
LP
124 *username = NOBODY_USER_NAME;
125
126 if (uid)
127 *uid = UID_NOBODY;
128 if (gid)
129 *gid = GID_NOBODY;
130
131 if (home)
132 *home = "/";
133
134 if (shell)
135 *shell = "/sbin/nologin";
136
137 return 0;
138 }
139
b1d4f8e1
LP
140 if (parse_uid(*username, &u) >= 0) {
141 errno = 0;
142 p = getpwuid(u);
143
144 /* If there are multiple users with the same id, make
145 * sure to leave $USER to the configured value instead
146 * of the first occurrence in the database. However if
147 * the uid was configured by a numeric uid, then let's
148 * pick the real username from /etc/passwd. */
149 if (p)
150 *username = p->pw_name;
151 } else {
152 errno = 0;
153 p = getpwnam(*username);
154 }
155
156 if (!p)
157 return errno > 0 ? -errno : -ESRCH;
158
67c7c892
LP
159 if (uid) {
160 if (!uid_is_valid(p->pw_uid))
161 return -EBADMSG;
162
b1d4f8e1 163 *uid = p->pw_uid;
67c7c892
LP
164 }
165
166 if (gid) {
167 if (!gid_is_valid(p->pw_gid))
168 return -EBADMSG;
b1d4f8e1 169
b1d4f8e1 170 *gid = p->pw_gid;
67c7c892 171 }
b1d4f8e1
LP
172
173 if (home)
174 *home = p->pw_dir;
175
176 if (shell)
177 *shell = p->pw_shell;
178
179 return 0;
180}
181
7cb60911
LP
182static inline bool is_nologin_shell(const char *shell) {
183
184 return PATH_IN_SET(shell,
185 /* 'nologin' is the friendliest way to disable logins for a user account. It prints a nice
186 * message and exits. Different distributions place the binary at different places though,
187 * hence let's list them all. */
188 "/bin/nologin",
189 "/sbin/nologin",
190 "/usr/bin/nologin",
191 "/usr/sbin/nologin",
192 /* 'true' and 'false' work too for the same purpose, but are less friendly as they don't do
193 * any message printing. Different distributions place the binary at various places but at
194 * least not in the 'sbin' directory. */
195 "/bin/false",
196 "/usr/bin/false",
197 "/bin/true",
198 "/usr/bin/true");
199}
200
be39ccf3
LP
201int get_user_creds_clean(
202 const char **username,
203 uid_t *uid, gid_t *gid,
204 const char **home,
205 const char **shell) {
206
207 int r;
208
209 /* Like get_user_creds(), but resets home/shell to NULL if they don't contain anything relevant. */
210
211 r = get_user_creds(username, uid, gid, home, shell);
212 if (r < 0)
213 return r;
214
215 if (shell &&
7cb60911 216 (isempty(*shell) || is_nologin_shell(*shell)))
be39ccf3
LP
217 *shell = NULL;
218
57ea45e1 219 if (home && empty_or_root(*home))
be39ccf3
LP
220 *home = NULL;
221
222 return 0;
223}
224
b1d4f8e1
LP
225int get_group_creds(const char **groupname, gid_t *gid) {
226 struct group *g;
227 gid_t id;
228
229 assert(groupname);
230
231 /* We enforce some special rules for gid=0: in order to avoid
232 * NSS lookups for root we hardcode its data. */
233
7e61fd02 234 if (STR_IN_SET(*groupname, "root", "0")) {
b1d4f8e1
LP
235 *groupname = "root";
236
237 if (gid)
238 *gid = 0;
239
240 return 0;
241 }
242
24eccc34
LP
243 if (synthesize_nobody() &&
244 STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534")) {
7e61fd02
LP
245 *groupname = NOBODY_GROUP_NAME;
246
247 if (gid)
248 *gid = GID_NOBODY;
249
250 return 0;
251 }
252
b1d4f8e1
LP
253 if (parse_gid(*groupname, &id) >= 0) {
254 errno = 0;
255 g = getgrgid(id);
256
257 if (g)
258 *groupname = g->gr_name;
259 } else {
260 errno = 0;
261 g = getgrnam(*groupname);
262 }
263
264 if (!g)
265 return errno > 0 ? -errno : -ESRCH;
266
67c7c892
LP
267 if (gid) {
268 if (!gid_is_valid(g->gr_gid))
269 return -EBADMSG;
270
b1d4f8e1 271 *gid = g->gr_gid;
67c7c892 272 }
b1d4f8e1
LP
273
274 return 0;
275}
276
277char* uid_to_name(uid_t uid) {
d0260817
LP
278 char *ret;
279 int r;
b1d4f8e1 280
d0260817 281 /* Shortcut things to avoid NSS lookups */
b1d4f8e1
LP
282 if (uid == 0)
283 return strdup("root");
24eccc34
LP
284 if (synthesize_nobody() &&
285 uid == UID_NOBODY)
7e61fd02 286 return strdup(NOBODY_USER_NAME);
b1d4f8e1 287
d0260817
LP
288 if (uid_is_valid(uid)) {
289 long bufsize;
290
291 bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
292 if (bufsize <= 0)
293 bufsize = 4096;
294
295 for (;;) {
296 struct passwd pwbuf, *pw = NULL;
297 _cleanup_free_ char *buf = NULL;
298
299 buf = malloc(bufsize);
300 if (!buf)
301 return NULL;
302
303 r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw);
304 if (r == 0 && pw)
305 return strdup(pw->pw_name);
306 if (r != ERANGE)
307 break;
308
309 bufsize *= 2;
310 }
311 }
b1d4f8e1 312
d0260817 313 if (asprintf(&ret, UID_FMT, uid) < 0)
b1d4f8e1
LP
314 return NULL;
315
d0260817 316 return ret;
b1d4f8e1
LP
317}
318
319char* gid_to_name(gid_t gid) {
d0260817
LP
320 char *ret;
321 int r;
b1d4f8e1
LP
322
323 if (gid == 0)
324 return strdup("root");
24eccc34
LP
325 if (synthesize_nobody() &&
326 gid == GID_NOBODY)
7e61fd02 327 return strdup(NOBODY_GROUP_NAME);
b1d4f8e1 328
d0260817
LP
329 if (gid_is_valid(gid)) {
330 long bufsize;
331
332 bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
333 if (bufsize <= 0)
334 bufsize = 4096;
335
336 for (;;) {
337 struct group grbuf, *gr = NULL;
338 _cleanup_free_ char *buf = NULL;
339
340 buf = malloc(bufsize);
341 if (!buf)
342 return NULL;
343
344 r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr);
345 if (r == 0 && gr)
346 return strdup(gr->gr_name);
347 if (r != ERANGE)
348 break;
349
350 bufsize *= 2;
351 }
352 }
b1d4f8e1 353
d0260817 354 if (asprintf(&ret, GID_FMT, gid) < 0)
b1d4f8e1
LP
355 return NULL;
356
d0260817 357 return ret;
b1d4f8e1
LP
358}
359
360int in_gid(gid_t gid) {
2dc89454 361 long ngroups_max;
b1d4f8e1 362 gid_t *gids;
2dc89454 363 int r, i;
b1d4f8e1
LP
364
365 if (getgid() == gid)
366 return 1;
367
368 if (getegid() == gid)
369 return 1;
370
67c7c892
LP
371 if (!gid_is_valid(gid))
372 return -EINVAL;
373
b1d4f8e1
LP
374 ngroups_max = sysconf(_SC_NGROUPS_MAX);
375 assert(ngroups_max > 0);
376
2dc89454 377 gids = newa(gid_t, ngroups_max);
b1d4f8e1
LP
378
379 r = getgroups(ngroups_max, gids);
380 if (r < 0)
381 return -errno;
382
383 for (i = 0; i < r; i++)
384 if (gids[i] == gid)
385 return 1;
386
387 return 0;
388}
389
390int in_group(const char *name) {
391 int r;
392 gid_t gid;
393
394 r = get_group_creds(&name, &gid);
395 if (r < 0)
396 return r;
397
398 return in_gid(gid);
399}
400
401int get_home_dir(char **_h) {
402 struct passwd *p;
403 const char *e;
404 char *h;
405 uid_t u;
406
407 assert(_h);
408
409 /* Take the user specified one */
410 e = secure_getenv("HOME");
411 if (e && path_is_absolute(e)) {
412 h = strdup(e);
413 if (!h)
414 return -ENOMEM;
415
416 *_h = h;
417 return 0;
418 }
419
7e61fd02 420 /* Hardcode home directory for root and nobody to avoid NSS */
b1d4f8e1
LP
421 u = getuid();
422 if (u == 0) {
423 h = strdup("/root");
424 if (!h)
425 return -ENOMEM;
426
427 *_h = h;
428 return 0;
429 }
24eccc34
LP
430 if (synthesize_nobody() &&
431 u == UID_NOBODY) {
7e61fd02
LP
432 h = strdup("/");
433 if (!h)
434 return -ENOMEM;
435
436 *_h = h;
437 return 0;
438 }
b1d4f8e1
LP
439
440 /* Check the database... */
441 errno = 0;
442 p = getpwuid(u);
443 if (!p)
444 return errno > 0 ? -errno : -ESRCH;
445
446 if (!path_is_absolute(p->pw_dir))
447 return -EINVAL;
448
449 h = strdup(p->pw_dir);
450 if (!h)
451 return -ENOMEM;
452
453 *_h = h;
454 return 0;
455}
456
457int get_shell(char **_s) {
458 struct passwd *p;
459 const char *e;
460 char *s;
461 uid_t u;
462
463 assert(_s);
464
465 /* Take the user specified one */
466 e = getenv("SHELL");
467 if (e) {
468 s = strdup(e);
469 if (!s)
470 return -ENOMEM;
471
472 *_s = s;
473 return 0;
474 }
475
7e61fd02 476 /* Hardcode shell for root and nobody to avoid NSS */
b1d4f8e1
LP
477 u = getuid();
478 if (u == 0) {
479 s = strdup("/bin/sh");
480 if (!s)
481 return -ENOMEM;
482
483 *_s = s;
484 return 0;
485 }
24eccc34
LP
486 if (synthesize_nobody() &&
487 u == UID_NOBODY) {
7e61fd02
LP
488 s = strdup("/sbin/nologin");
489 if (!s)
490 return -ENOMEM;
491
492 *_s = s;
493 return 0;
494 }
b1d4f8e1
LP
495
496 /* Check the database... */
497 errno = 0;
498 p = getpwuid(u);
499 if (!p)
500 return errno > 0 ? -errno : -ESRCH;
501
502 if (!path_is_absolute(p->pw_shell))
503 return -EINVAL;
504
505 s = strdup(p->pw_shell);
506 if (!s)
507 return -ENOMEM;
508
509 *_s = s;
510 return 0;
511}
512
513int reset_uid_gid(void) {
97f0e76f 514 int r;
b1d4f8e1 515
97f0e76f
LP
516 r = maybe_setgroups(0, NULL);
517 if (r < 0)
518 return r;
b1d4f8e1
LP
519
520 if (setresgid(0, 0, 0) < 0)
521 return -errno;
522
523 if (setresuid(0, 0, 0) < 0)
524 return -errno;
525
526 return 0;
527}
e929bee0
LP
528
529int take_etc_passwd_lock(const char *root) {
530
531 struct flock flock = {
532 .l_type = F_WRLCK,
533 .l_whence = SEEK_SET,
534 .l_start = 0,
535 .l_len = 0,
536 };
537
538 const char *path;
539 int fd, r;
540
541 /* This is roughly the same as lckpwdf(), but not as awful. We
542 * don't want to use alarm() and signals, hence we implement
543 * our own trivial version of this.
544 *
545 * Note that shadow-utils also takes per-database locks in
546 * addition to lckpwdf(). However, we don't given that they
61233823 547 * are redundant as they invoke lckpwdf() first and keep
e929bee0
LP
548 * it during everything they do. The per-database locks are
549 * awfully racy, and thus we just won't do them. */
550
551 if (root)
d1e4b8fd 552 path = prefix_roota(root, ETC_PASSWD_LOCK_PATH);
e929bee0 553 else
d1e4b8fd 554 path = ETC_PASSWD_LOCK_PATH;
e929bee0
LP
555
556 fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
557 if (fd < 0)
d1e4b8fd 558 return log_debug_errno(errno, "Cannot open %s: %m", path);
e929bee0
LP
559
560 r = fcntl(fd, F_SETLKW, &flock);
561 if (r < 0) {
562 safe_close(fd);
d1e4b8fd 563 return log_debug_errno(errno, "Locking %s failed: %m", path);
e929bee0
LP
564 }
565
566 return fd;
567}
e4631b48
LP
568
569bool valid_user_group_name(const char *u) {
570 const char *i;
571 long sz;
572
1429dfe5
LP
573 /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition,
574 * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules:
575 *
576 * - We don't allow any dots (this would break chown syntax which permits dots as user/group name separator)
577 * - We require that names fit into the appropriate utmp field
578 * - We don't allow empty user names
579 *
580 * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
581 */
e4631b48
LP
582
583 if (isempty(u))
584 return false;
585
586 if (!(u[0] >= 'a' && u[0] <= 'z') &&
587 !(u[0] >= 'A' && u[0] <= 'Z') &&
588 u[0] != '_')
589 return false;
590
591 for (i = u+1; *i; i++) {
592 if (!(*i >= 'a' && *i <= 'z') &&
593 !(*i >= 'A' && *i <= 'Z') &&
594 !(*i >= '0' && *i <= '9') &&
4c701096 595 !IN_SET(*i, '_', '-'))
e4631b48
LP
596 return false;
597 }
598
599 sz = sysconf(_SC_LOGIN_NAME_MAX);
600 assert_se(sz > 0);
601
602 if ((size_t) (i-u) > (size_t) sz)
603 return false;
604
605 if ((size_t) (i-u) > UT_NAMESIZE - 1)
606 return false;
607
608 return true;
609}
610
611bool valid_user_group_name_or_id(const char *u) {
612
613 /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right
614 * range, and not the invalid user ids. */
615
616 if (isempty(u))
617 return false;
618
619 if (valid_user_group_name(u))
620 return true;
621
622 return parse_uid(u, NULL) >= 0;
623}
624
625bool valid_gecos(const char *d) {
626
627 if (!d)
628 return false;
629
630 if (!utf8_is_valid(d))
631 return false;
632
633 if (string_has_cc(d, NULL))
634 return false;
635
636 /* Colons are used as field separators, and hence not OK */
637 if (strchr(d, ':'))
638 return false;
639
640 return true;
641}
642
643bool valid_home(const char *p) {
7b1aaf66
ZJS
644 /* Note that this function is also called by valid_shell(), any
645 * changes must account for that. */
e4631b48
LP
646
647 if (isempty(p))
648 return false;
649
650 if (!utf8_is_valid(p))
651 return false;
652
653 if (string_has_cc(p, NULL))
654 return false;
655
656 if (!path_is_absolute(p))
657 return false;
658
99be45a4 659 if (!path_is_normalized(p))
e4631b48
LP
660 return false;
661
662 /* Colons are used as field separators, and hence not OK */
663 if (strchr(p, ':'))
664 return false;
665
666 return true;
667}
36d85478
GS
668
669int maybe_setgroups(size_t size, const gid_t *list) {
97f0e76f
LP
670 int r;
671
672 /* Check if setgroups is allowed before we try to drop all the auxiliary groups */
673 if (size == 0) { /* Dropping all aux groups? */
674 _cleanup_free_ char *setgroups_content = NULL;
675 bool can_setgroups;
676
677 r = read_one_line_file("/proc/self/setgroups", &setgroups_content);
678 if (r == -ENOENT)
679 /* Old kernels don't have /proc/self/setgroups, so assume we can use setgroups */
680 can_setgroups = true;
681 else if (r < 0)
682 return r;
683 else
684 can_setgroups = streq(setgroups_content, "allow");
685
686 if (!can_setgroups) {
687 log_debug("Skipping setgroups(), /proc/self/setgroups is set to 'deny'");
36d85478 688 return 0;
97f0e76f 689 }
36d85478 690 }
97f0e76f
LP
691
692 if (setgroups(size, list) < 0)
693 return -errno;
694
695 return 0;
36d85478 696}
24eccc34
LP
697
698bool synthesize_nobody(void) {
699
700#ifdef NOLEGACY
701 return true;
702#else
703 /* Returns true when we shall synthesize the "nobody" user (which we do by default). This can be turned off by
704 * touching /etc/systemd/dont-synthesize-nobody in order to provide upgrade compatibility with legacy systems
705 * that used the "nobody" user name and group name for other UIDs/GIDs than 65534.
706 *
707 * Note that we do not employ any kind of synchronization on the following caching variable. If the variable is
708 * accessed in multi-threaded programs in the worst case it might happen that we initialize twice, but that
709 * shouldn't matter as each initialization should come to the same result. */
710 static int cache = -1;
711
712 if (cache < 0)
713 cache = access("/etc/systemd/dont-synthesize-nobody", F_OK) < 0;
714
715 return cache;
716#endif
717}
100d5f6e
FB
718
719int putpwent_sane(const struct passwd *pw, FILE *stream) {
720 assert(pw);
721 assert(stream);
722
723 errno = 0;
724 if (putpwent(pw, stream) != 0)
725 return errno > 0 ? -errno : -EIO;
726
727 return 0;
728}
729
730int putspent_sane(const struct spwd *sp, FILE *stream) {
731 assert(sp);
732 assert(stream);
733
734 errno = 0;
735 if (putspent(sp, stream) != 0)
736 return errno > 0 ? -errno : -EIO;
737
738 return 0;
739}
740
741int putgrent_sane(const struct group *gr, FILE *stream) {
742 assert(gr);
743 assert(stream);
744
745 errno = 0;
746 if (putgrent(gr, stream) != 0)
747 return errno > 0 ? -errno : -EIO;
748
749 return 0;
750}
751
752#if ENABLE_GSHADOW
753int putsgent_sane(const struct sgrp *sg, FILE *stream) {
754 assert(sg);
755 assert(stream);
756
757 errno = 0;
758 if (putsgent(sg, stream) != 0)
759 return errno > 0 ? -errno : -EIO;
760
761 return 0;
762}
763#endif
764
765int fgetpwent_sane(FILE *stream, struct passwd **pw) {
766 struct passwd *p;
767
768 assert(pw);
769 assert(stream);
770
771 errno = 0;
772 p = fgetpwent(stream);
ad80c6a6 773 if (!p && errno != ENOENT)
100d5f6e 774 return errno > 0 ? -errno : -EIO;
100d5f6e
FB
775
776 *pw = p;
ad80c6a6 777 return !!p;
100d5f6e
FB
778}
779
780int fgetspent_sane(FILE *stream, struct spwd **sp) {
781 struct spwd *s;
782
783 assert(sp);
784 assert(stream);
785
786 errno = 0;
787 s = fgetspent(stream);
ad80c6a6 788 if (!s && errno != ENOENT)
100d5f6e 789 return errno > 0 ? -errno : -EIO;
100d5f6e
FB
790
791 *sp = s;
ad80c6a6 792 return !!s;
100d5f6e
FB
793}
794
795int fgetgrent_sane(FILE *stream, struct group **gr) {
796 struct group *g;
797
798 assert(gr);
799 assert(stream);
800
801 errno = 0;
802 g = fgetgrent(stream);
ad80c6a6 803 if (!g && errno != ENOENT)
100d5f6e 804 return errno > 0 ? -errno : -EIO;
100d5f6e
FB
805
806 *gr = g;
ad80c6a6 807 return !!g;
100d5f6e
FB
808}
809
810#if ENABLE_GSHADOW
811int fgetsgent_sane(FILE *stream, struct sgrp **sg) {
812 struct sgrp *s;
813
814 assert(sg);
815 assert(stream);
816
817 errno = 0;
818 s = fgetsgent(stream);
ad80c6a6 819 if (!s && errno != ENOENT)
100d5f6e 820 return errno > 0 ? -errno : -EIO;
100d5f6e
FB
821
822 *sg = s;
ad80c6a6 823 return !!s;
100d5f6e
FB
824}
825#endif