diff options
author | Cameron Katri <me@cameronkatri.com> | 2021-02-10 11:29:12 -0500 |
---|---|---|
committer | Cameron Katri <me@cameronkatri.com> | 2021-02-10 11:29:12 -0500 |
commit | ec831d9055a20b69cb9a7300990b8e4414cbd474 (patch) | |
tree | 2ad72d41955388fbb826a51500eb6bad89303708 /pw | |
parent | 66124b92b65dcc15b9695c7e05d72c7c7e174c39 (diff) | |
download | pw-darwin-ec831d9055a20b69cb9a7300990b8e4414cbd474.tar.gz pw-darwin-ec831d9055a20b69cb9a7300990b8e4414cbd474.tar.zst pw-darwin-ec831d9055a20b69cb9a7300990b8e4414cbd474.zip |
pw: fix building for iOS, completely untested
Diffstat (limited to 'pw')
-rw-r--r-- | pw/cpdir.c | 2 | ||||
-rw-r--r-- | pw/libutil/_secure_path.c | 74 | ||||
-rw-r--r-- | pw/libutil/flopen.c | 147 | ||||
-rw-r--r-- | pw/libutil/gr_util.c | 667 | ||||
-rw-r--r-- | pw/libutil/libutil.h | 234 | ||||
-rw-r--r-- | pw/libutil/login_cap.c | 821 | ||||
-rw-r--r-- | pw/libutil/login_cap.h | 164 | ||||
-rw-r--r-- | pw/libutil/login_crypt.c | 50 | ||||
-rw-r--r-- | pw/libutil/pw_scan.c | 207 | ||||
-rw-r--r-- | pw/libutil/pw_scan.h | 39 | ||||
-rw-r--r-- | pw/libutil/pw_util.c | 685 | ||||
-rw-r--r-- | pw/pw_log.c | 1 | ||||
-rw-r--r-- | pw/pw_user.c | 183 | ||||
-rw-r--r-- | pw/reallocarray.c | 11 | ||||
-rw-r--r-- | pw/reallocarray.h | 5 | ||||
-rw-r--r-- | pw/sbuf/subr_prf.c | 1310 | ||||
-rw-r--r-- | pw/sbuf/subr_sbuf.c | 950 | ||||
-rw-r--r-- | pw/sbuf/sys/sbuf.h | 123 |
18 files changed, 5646 insertions, 27 deletions
@@ -60,7 +60,7 @@ copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid, } fchownat(rootfd, dir, uid, gid, AT_SYMLINK_NOFOLLOW); if (flags > 0) - chflagsat(rootfd, dir, flags, AT_SYMLINK_NOFOLLOW); + lchflags(dir, flags); if (skelfd == -1) return; diff --git a/pw/libutil/_secure_path.c b/pw/libutil/_secure_path.c new file mode 100644 index 0000000..363378b --- /dev/null +++ b/pw/libutil/_secure_path.c @@ -0,0 +1,74 @@ +/*- + * Based on code copyright (c) 1995,1997 by + * Berkeley Software Design, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. This work was done expressly for inclusion into FreeBSD. Other use + * is permitted provided this notation is included. + * 4. Absolutely no warranty of function or purpose is made by the authors. + * 5. Modifications may be freely made to this file providing the above + * conditions are met. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <libutil.h> +#include <stddef.h> +#include <syslog.h> + +/* + * Check for common security problems on a given path + * It must be: + * 1. A regular file, and exists + * 2. Owned and writable only by root (or given owner) + * 3. Group ownership is given group or is non-group writable + * + * Returns: -2 if file does not exist, + * -1 if security test failure + * 0 otherwise + */ + +int +_secure_path(const char *path, uid_t uid, gid_t gid) +{ + int r = -1; + struct stat sb; + const char *msg = NULL; + + if (lstat(path, &sb) < 0) { + if (errno == ENOENT) /* special case */ + r = -2; /* if it is just missing, skip the log entry */ + else + msg = "%s: cannot stat %s: %m"; + } + else if (!S_ISREG(sb.st_mode)) + msg = "%s: %s is not a regular file"; + else if (sb.st_mode & S_IWOTH) + msg = "%s: %s is world writable"; + else if ((int)uid != -1 && sb.st_uid != uid && sb.st_uid != 0) { + if (uid == 0) + msg = "%s: %s is not owned by root"; + else + msg = "%s: %s is not owned by uid %d"; + } else if ((int)gid != -1 && sb.st_gid != gid && (sb.st_mode & S_IWGRP)) + msg = "%s: %s is group writeable by non-authorised groups"; + else + r = 0; + if (msg != NULL) + syslog(LOG_ERR, msg, "_secure_path", path, uid); + return r; +} diff --git a/pw/libutil/flopen.c b/pw/libutil/flopen.c new file mode 100644 index 0000000..485eee4 --- /dev/null +++ b/pw/libutil/flopen.c @@ -0,0 +1,147 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2007-2009 Dag-Erling Coïdan Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/file.h> +#include <sys/stat.h> + +#include <errno.h> +#include <stdarg.h> +#include <unistd.h> + +#include <libutil.h> + +/* + * Reliably open and lock a file. + * + * Please do not modify this code without first reading the revision history + * and discussing your changes with <des@freebsd.org>. Don't be fooled by the + * code's apparent simplicity; there would be no need for this function if it + * was easy to get right. + */ +static int +vflopenat(int dirfd, const char *path, int flags, va_list ap) +{ + int fd, operation, serrno, trunc; + struct stat sb, fsb; + mode_t mode; + +#ifdef O_EXLOCK + flags &= ~O_EXLOCK; +#endif + + mode = 0; + if (flags & O_CREAT) { + mode = (mode_t)va_arg(ap, int); /* mode_t promoted to int */ + } + + operation = LOCK_EX; + if (flags & O_NONBLOCK) + operation |= LOCK_NB; + + trunc = (flags & O_TRUNC); + flags &= ~O_TRUNC; + + for (;;) { + if ((fd = openat(dirfd, path, flags, mode)) == -1) + /* non-existent or no access */ + return (-1); + if (flock(fd, operation) == -1) { + /* unsupported or interrupted */ + serrno = errno; + (void)close(fd); + errno = serrno; + return (-1); + } + if (fstatat(dirfd, path, &sb, 0) == -1) { + /* disappeared from under our feet */ + (void)close(fd); + continue; + } + if (fstat(fd, &fsb) == -1) { + /* can't happen [tm] */ + serrno = errno; + (void)close(fd); + errno = serrno; + return (-1); + } + if (sb.st_dev != fsb.st_dev || + sb.st_ino != fsb.st_ino) { + /* changed under our feet */ + (void)close(fd); + continue; + } + if (trunc && ftruncate(fd, 0) != 0) { + /* can't happen [tm] */ + serrno = errno; + (void)close(fd); + errno = serrno; + return (-1); + } + /* + * The following change is provided as a specific example to + * avoid. + */ +#if 0 + if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) { + serrno = errno; + (void)close(fd); + errno = serrno; + return (-1); + } +#endif + return (fd); + } +} + +int +flopen(const char *path, int flags, ...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = vflopenat(AT_FDCWD, path, flags, ap); + va_end(ap); + return (ret); +} + +int +flopenat(int dirfd, const char *path, int flags, ...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = vflopenat(dirfd, path, flags, ap); + va_end(ap); + return (ret); +} diff --git a/pw/libutil/gr_util.c b/pw/libutil/gr_util.c new file mode 100644 index 0000000..cfb9b7e --- /dev/null +++ b/pw/libutil/gr_util.c @@ -0,0 +1,667 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2008 Sean C. Farley <scf@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <grp.h> +#include <inttypes.h> +#include <libutil.h> +#include <paths.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#include "reallocarray.h" + +static int lockfd = -1; +static char group_dir[PATH_MAX]; +static char group_file[PATH_MAX]; +static char tempname[PATH_MAX]; +static int initialized; +static size_t grmemlen(const struct group *, const char *, int *); +static struct group *grcopy(const struct group *gr, char *mem, const char *, int ndx); + +/* + * Initialize statics + */ +int +gr_init(const char *dir, const char *group) +{ + + if (dir == NULL) { + strcpy(group_dir, _PATH_PWD); + } else { + if (strlen(dir) >= sizeof(group_dir)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(group_dir, dir); + } + + if (group == NULL) { + if (dir == NULL) { + strcpy(group_file, _PATH_GROUP); + } else if (snprintf(group_file, sizeof(group_file), "%s/group", + group_dir) > (int)sizeof(group_file)) { + errno = ENAMETOOLONG; + return (-1); + } + } else { + if (strlen(group) >= sizeof(group_file)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(group_file, group); + } + + initialized = 1; + return (0); +} + +/* + * Lock the group file + */ +int +gr_lock(void) +{ + if (*group_file == '\0') + return (-1); + + for (;;) { + struct stat st; + + lockfd = flopen(group_file, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0); + if (lockfd == -1) { + if (errno == EWOULDBLOCK) { + errx(1, "the group file is busy"); + } else { + err(1, "could not lock the group file"); + } + } + if (fstat(lockfd, &st) == -1) + err(1, "fstat() failed"); + if (st.st_nlink != 0) + break; + close(lockfd); + lockfd = -1; + } + return (lockfd); +} + +/* + * Create and open a presmuably safe temp file for editing group data + */ +int +gr_tmp(int mfd) +{ + char buf[8192]; + ssize_t nr; + const char *p; + int tfd; + + if (*group_file == '\0') + return (-1); + if ((p = strrchr(group_file, '/'))) + ++p; + else + p = group_file; + if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX", + (int)(p - group_file), group_file) >= (int)sizeof(tempname)) { + errno = ENAMETOOLONG; + return (-1); + } + if ((tfd = mkostemp(tempname, 0)) == -1) + return (-1); + if (mfd != -1) { + while ((nr = read(mfd, buf, sizeof(buf))) > 0) + if (write(tfd, buf, (size_t)nr) != nr) + break; + if (nr != 0) { + unlink(tempname); + *tempname = '\0'; + close(tfd); + return (-1); + } + } + return (tfd); +} + +/* + * Copy the group file from one descriptor to another, replacing, deleting + * or adding a single record on the way. + */ +int +gr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr) +{ + char *buf, *end, *line, *p, *q, *r, *tmp; + struct group *fgr; + const struct group *sgr; + size_t len, size; + int eof, readlen; + char t; + + if (old_gr == NULL && gr == NULL) + return(-1); + + sgr = old_gr; + /* deleting a group */ + if (gr == NULL) { + line = NULL; + } else { + if ((line = gr_make(gr)) == NULL) + return (-1); + } + + /* adding a group */ + if (sgr == NULL) + sgr = gr; + + /* initialize the buffer */ + if ((buf = malloc(size = 1024)) == NULL) + goto err; + + eof = 0; + len = 0; + p = q = end = buf; + for (;;) { + /* find the end of the current line */ + for (p = q; q < end && *q != '\0'; ++q) + if (*q == '\n') + break; + + /* if we don't have a complete line, fill up the buffer */ + if (q >= end) { + if (eof) + break; + while ((size_t)(q - p) >= size) { + if ((tmp = reallocarray(buf, 2, size)) == NULL) { + warnx("group line too long"); + goto err; + } + p = tmp + (p - buf); + q = tmp + (q - buf); + end = tmp + (end - buf); + buf = tmp; + size = size * 2; + } + if (p < end) { + q = memmove(buf, p, end -p); + end -= p - buf; + } else { + p = q = end = buf; + } + readlen = read(ffd, end, size - (end - buf)); + if (readlen == -1) + goto err; + else + len = (size_t)readlen; + if (len == 0 && p == buf) + break; + end += len; + len = end - buf; + if (len < size) { + eof = 1; + if (len > 0 && buf[len -1] != '\n') + ++len, *end++ = '\n'; + } + continue; + } + + /* is it a blank line or a comment? */ + for (r = p; r < q && isspace(*r); ++r) + /* nothing */; + if (r == q || *r == '#') { + /* yep */ + if (write(tfd, p, q -p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + + /* is it the one we're looking for? */ + + t = *q; + *q = '\0'; + + fgr = gr_scan(r); + + /* fgr is either a struct group for the current line, + * or NULL if the line is malformed. + */ + + *q = t; + if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) { + /* nope */ + if (fgr != NULL) + free(fgr); + if (write(tfd, p, q - p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + if (old_gr && !gr_equal(fgr, old_gr)) { + warnx("entry inconsistent"); + free(fgr); + errno = EINVAL; /* hack */ + goto err; + } + free(fgr); + + /* it is, replace or remove it */ + if (line != NULL) { + len = strlen(line); + if (write(tfd, line, len) != (int) len) + goto err; + } else { + /* when removed, avoid the \n */ + q++; + } + /* we're done, just copy the rest over */ + for (;;) { + if (write(tfd, q, end - q) != end - q) + goto err; + q = buf; + readlen = read(ffd, buf, size); + if (readlen == 0) + break; + else + len = (size_t)readlen; + if (readlen == -1) + goto err; + end = buf + len; + } + goto done; + } + + /* if we got here, we didn't find the old entry */ + if (line == NULL) { + errno = ENOENT; + goto err; + } + len = strlen(line); + if ((size_t)write(tfd, line, len) != len || + write(tfd, "\n", 1) != 1) + goto err; + done: + free(line); + free(buf); + return (0); + err: + free(line); + free(buf); + return (-1); +} + +/* + * Regenerate the group file + */ +int +gr_mkdb(void) +{ + int fd; + + if (chmod(tempname, 0644) != 0) + return (-1); + + if (rename(tempname, group_file) != 0) + return (-1); + + /* + * Make sure new group file is safe on disk. To improve performance we + * will call fsync() to the directory where file lies + */ + if ((fd = open(group_dir, O_RDONLY|O_DIRECTORY)) == -1) + return (-1); + + if (fsync(fd) != 0) { + close(fd); + return (-1); + } + + close(fd); + return(0); +} + +/* + * Clean up. Preserves errno for the caller's convenience. + */ +void +gr_fini(void) +{ + int serrno; + + if (!initialized) + return; + initialized = 0; + serrno = errno; + if (*tempname != '\0') { + unlink(tempname); + *tempname = '\0'; + } + if (lockfd != -1) + close(lockfd); + errno = serrno; +} + +/* + * Compares two struct group's. + */ +int +gr_equal(const struct group *gr1, const struct group *gr2) +{ + + /* Check that the non-member information is the same. */ + if (gr1->gr_name == NULL || gr2->gr_name == NULL) { + if (gr1->gr_name != gr2->gr_name) + return (false); + } else if (strcmp(gr1->gr_name, gr2->gr_name) != 0) + return (false); + if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) { + if (gr1->gr_passwd != gr2->gr_passwd) + return (false); + } else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0) + return (false); + if (gr1->gr_gid != gr2->gr_gid) + return (false); + + /* + * Check all members in both groups. + * getgrnam can return gr_mem with a pointer to NULL. + * gr_dup and gr_add strip out this superfluous NULL, setting + * gr_mem to NULL for no members. + */ + if (gr1->gr_mem != NULL && gr2->gr_mem != NULL) { + int i; + + for (i = 0; + gr1->gr_mem[i] != NULL && gr2->gr_mem[i] != NULL; i++) { + if (strcmp(gr1->gr_mem[i], gr2->gr_mem[i]) != 0) + return (false); + } + if (gr1->gr_mem[i] != NULL || gr2->gr_mem[i] != NULL) + return (false); + } else if (gr1->gr_mem != NULL && gr1->gr_mem[0] != NULL) { + return (false); + } else if (gr2->gr_mem != NULL && gr2->gr_mem[0] != NULL) { + return (false); + } + + return (true); +} + +/* + * Make a group line out of a struct group. + */ +char * +gr_make(const struct group *gr) +{ + const char *group_line_format = "%s:%s:%ju:"; + const char *sep; + char *line; + char *p; + size_t line_size; + int ndx; + + /* Calculate the length of the group line. */ + line_size = snprintf(NULL, 0, group_line_format, gr->gr_name, + gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1; + if (gr->gr_mem != NULL) { + for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) + line_size += strlen(gr->gr_mem[ndx]) + 1; + if (ndx > 0) + line_size--; + } + + /* Create the group line and fill it. */ + if ((line = p = malloc(line_size)) == NULL) + return (NULL); + p += sprintf(p, group_line_format, gr->gr_name, gr->gr_passwd, + (uintmax_t)gr->gr_gid); + if (gr->gr_mem != NULL) { + sep = ""; + for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) { + p = stpcpy(p, sep); + p = stpcpy(p, gr->gr_mem[ndx]); + sep = ","; + } + } + + return (line); +} + +/* + * Duplicate a struct group. + */ +struct group * +gr_dup(const struct group *gr) +{ + return (gr_add(gr, NULL)); +} +/* + * Add a new member name to a struct group. + */ +struct group * +gr_add(const struct group *gr, const char *newmember) +{ + char *mem; + size_t len; + int num_mem; + + num_mem = 0; + len = grmemlen(gr, newmember, &num_mem); + /* Create new group and copy old group into it. */ + if ((mem = malloc(len)) == NULL) + return (NULL); + return (grcopy(gr, mem, newmember, num_mem)); +} + +/* It is safer to walk the pointers given at gr_mem since there is no + * guarantee the gr_mem + strings are contiguous in the given struct group + * but compactify the new group into the following form. + * + * The new struct is laid out like this in memory. The example given is + * for a group with two members only. + * + * { + * (char *name) + * (char *passwd) + * (int gid) + * (gr_mem * newgrp + sizeof(struct group) + sizeof(**)) points to gr_mem area + * gr_mem area + * (member1 *) + * (member2 *) + * (NULL) + * (name string) + * (passwd string) + * (member1 string) + * (member2 string) + * } + */ +/* + * Copy the contents of a group plus given name to a preallocated group struct + */ +static struct group * +grcopy(const struct group *gr, char *dst, const char *name, int ndx) +{ + int i; + struct group *newgr; + + newgr = (struct group *)(void *)dst; /* avoid alignment warning */ + dst += sizeof(*newgr); + if (ndx != 0) { + newgr->gr_mem = (char **)(void *)(dst); /* avoid alignment warning */ + dst += (ndx + 1) * sizeof(*newgr->gr_mem); + } else + newgr->gr_mem = NULL; + if (gr->gr_name != NULL) { + newgr->gr_name = dst; + dst = stpcpy(dst, gr->gr_name) + 1; + } else + newgr->gr_name = NULL; + if (gr->gr_passwd != NULL) { + newgr->gr_passwd = dst; + dst = stpcpy(dst, gr->gr_passwd) + 1; + } else + newgr->gr_passwd = NULL; + newgr->gr_gid = gr->gr_gid; + i = 0; + /* Original group struct might have a NULL gr_mem */ + if (gr->gr_mem != NULL) { + for (; gr->gr_mem[i] != NULL; i++) { + newgr->gr_mem[i] = dst; + dst = stpcpy(dst, gr->gr_mem[i]) + 1; + } + } + /* If name is not NULL, newgr->gr_mem is known to be not NULL */ + if (name != NULL) { + newgr->gr_mem[i++] = dst; + dst = stpcpy(dst, name) + 1; + } + /* if newgr->gr_mem is not NULL add NULL marker */ + if (newgr->gr_mem != NULL) + newgr->gr_mem[i] = NULL; + + return (newgr); +} + +/* + * Calculate length of a struct group + given name + */ +static size_t +grmemlen(const struct group *gr, const char *name, int *num_mem) +{ + size_t len; + int i; + + if (gr == NULL) + return (0); + /* Calculate size of the group. */ + len = sizeof(*gr); + if (gr->gr_name != NULL) + len += strlen(gr->gr_name) + 1; + if (gr->gr_passwd != NULL) + len += strlen(gr->gr_passwd) + 1; + i = 0; + if (gr->gr_mem != NULL) { + for (; gr->gr_mem[i] != NULL; i++) { + len += strlen(gr->gr_mem[i]) + 1; + len += sizeof(*gr->gr_mem); + } + } + if (name != NULL) { + i++; + len += strlen(name) + 1; + len += sizeof(*gr->gr_mem); + } + /* Allow for NULL pointer */ + if (i != 0) + len += sizeof(*gr->gr_mem); + *num_mem = i; + return(len); +} + +/* + * Scan a line and place it into a group structure. + */ +static bool +__gr_scan(char *line, struct group *gr) +{ + char *loc; + int ndx; + + /* Assign non-member information to structure. */ + gr->gr_name = line; + if ((loc = strchr(line, ':')) == NULL) + return (false); + *loc = '\0'; + gr->gr_passwd = loc + 1; + if (*gr->gr_passwd == ':') + *gr->gr_passwd = '\0'; + else { + if ((loc = strchr(loc + 1, ':')) == NULL) + return (false); + *loc = '\0'; + } + if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1) + return (false); + + /* Assign member information to structure. */ + if ((loc = strchr(loc + 1, ':')) == NULL) + return (false); + line = loc + 1; + gr->gr_mem = NULL; + ndx = 0; + do { + gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) * + (ndx + 1)); + if (gr->gr_mem == NULL) + return (false); + + /* Skip locations without members (i.e., empty string). */ + do { + gr->gr_mem[ndx] = strsep(&line, ","); + } while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0'); + } while (gr->gr_mem[ndx++] != NULL); + + return (true); +} + +/* + * Create a struct group from a line. + */ +struct group * +gr_scan(const char *line) +{ + struct group gr; + char *line_copy; + struct group *new_gr; + + if ((line_copy = strdup(line)) == NULL) + return (NULL); + if (!__gr_scan(line_copy, &gr)) { + free(line_copy); + return (NULL); + } + new_gr = gr_dup(&gr); + free(line_copy); + if (gr.gr_mem != NULL) + free(gr.gr_mem); + + return (new_gr); +} diff --git a/pw/libutil/libutil.h b/pw/libutil/libutil.h new file mode 100644 index 0000000..6c1967e --- /dev/null +++ b/pw/libutil/libutil.h @@ -0,0 +1,234 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1996 Peter Wemm <peter@FreeBSD.org>. + * All rights reserved. + * Copyright (c) 2002 Networks Associates Technology, Inc. + * All rights reserved. + * + * Portions of this software were developed for the FreeBSD Project by + * ThinkSec AS and NAI Labs, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 + * ("CBOSS"), as part of the DARPA CHATS research program. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _LIBUTIL_H_ +#define _LIBUTIL_H_ + +#include <sys/cdefs.h> +#include <sys/_types.h> +#include <sys/types.h> +#include <stdint.h> + +#define PROPERTY_MAX_NAME 64 +#define PROPERTY_MAX_VALUE 512 + +/* For properties.c. */ +typedef struct _property { + struct _property *next; + char *name; + char *value; +} *properties; + +/* Avoid pulling in all the include files for no need. */ +struct in_addr; +struct pidfh; +struct sockaddr; +struct termios; +struct winsize; + +__BEGIN_DECLS +char *auth_getval(const char *_name); +void clean_environment(const char * const *_white, + const char * const *_more_white); +int expand_number(const char *_buf, uint64_t *_num); +int extattr_namespace_to_string(int _attrnamespace, char **_string); +int extattr_string_to_namespace(const char *_string, int *_attrnamespace); +int flopen(const char *_path, int _flags, ...); +int flopenat(int _dirfd, const char *_path, int _flags, ...); +int forkpty(int *_amaster, char *_name, + struct termios *_termp, struct winsize *_winp); +const char * + getlocalbase(void); +void hexdump(const void *_ptr, int _length, const char *_hdr, int _flags); +int humanize_number(char *_buf, size_t _len, int64_t _number, + const char *_suffix, int _scale, int _flags); +struct kinfo_file * + kinfo_getfile(pid_t _pid, int *_cntp); +struct kinfo_vmentry * + kinfo_getvmmap(pid_t _pid, int *_cntp); +struct kinfo_vmobject * + kinfo_getvmobject(int *_cntp); +struct kinfo_proc * + kinfo_getallproc(int *_cntp); +struct kinfo_proc * + kinfo_getproc(pid_t _pid); +int kld_isloaded(const char *_name); +int kld_load(const char *_name); +int login_tty(int _fd); +int openpty(int *_amaster, int *_aslave, char *_name, + struct termios *_termp, struct winsize *_winp); +int pidfile_close(struct pidfh *_pfh); +int pidfile_fileno(const struct pidfh *_pfh); +struct pidfh * + pidfile_open(const char *_path, mode_t _mode, pid_t *_pidptr); +int pidfile_remove(struct pidfh *_pfh); +int pidfile_write(struct pidfh *_pfh); +void properties_free(properties _list); +char *property_find(properties _list, const char *_name); +properties + properties_read(int _fd); +int realhostname(char *_host, size_t _hsize, const struct in_addr *_ip); +int realhostname_sa(char *_host, size_t _hsize, struct sockaddr *_addr, + int _addrlen); +int _secure_path(const char *_path, uid_t _uid, gid_t _gid); +void trimdomain(char *_fullhost, int _hostsize); +const char * + uu_lockerr(int _uu_lockresult); +int uu_lock(const char *_ttyname); +int uu_unlock(const char *_ttyname); +int uu_lock_txfr(const char *_ttyname, pid_t _pid); + +/* + * Conditionally prototype the following functions if the include + * files upon which they depend have been included. + */ +#ifdef _STDIO_H_ +char *fparseln(FILE *_fp, size_t *_len, size_t *_lineno, + const char _delim[3], int _flags); +#endif + +#ifdef _PWD_H_ +int pw_copy(int _ffd, int _tfd, const struct passwd *_pw, + struct passwd *_old_pw); +struct passwd + *pw_dup(const struct passwd *_pw); +int pw_edit(int _notsetuid); +int pw_equal(const struct passwd *_pw1, const struct passwd *_pw2); +void pw_fini(void); +int pw_init(const char *_dir, const char *_master); +void pw_initpwd(struct passwd *_pw); +char *pw_make(const struct passwd *_pw); +char *pw_make_v7(const struct passwd *_pw); +int pw_mkdb(const char *_user); +int pw_lock(void); +struct passwd * + pw_scan(const char *_line, int _flags); +const char * + pw_tempname(void); +int pw_tmp(int _mfd); +#endif + +#ifdef _GRP_H_ +int gr_copy(int __ffd, int _tfd, const struct group *_gr, + struct group *_old_gr); +struct group * + gr_dup(const struct group *_gr); +struct group * + gr_add(const struct group *_gr, const char *_newmember); +int gr_equal(const struct group *_gr1, const struct group *_gr2); +void gr_fini(void); +int gr_init(const char *_dir, const char *_master); +int gr_lock(void); +char *gr_make(const struct group *_gr); +int gr_mkdb(void); +struct group * + gr_scan(const char *_line); +int gr_tmp(int _mdf); +#endif + +#ifdef _UFS_UFS_QUOTA_H_ +struct fstab; +struct quotafile; +int quota_check_path(const struct quotafile *_qf, const char *_path); +void quota_close(struct quotafile *_qf); +int quota_convert(struct quotafile *_qf, int _wordsize); +const char * + quota_fsname(const struct quotafile *_qf); +int quota_maxid(struct quotafile *_qf); +int quota_off(struct quotafile *_qf); +int quota_on(struct quotafile *_qf); +struct quotafile * + quota_open(struct fstab *_fs, int _quotatype, int _openflags); +const char * + quota_qfname(const struct quotafile *_qf); +int quota_read(struct quotafile *_qf, struct dqblk *_dqb, int _id); +int quota_write_limits(struct quotafile *_qf, struct dqblk *_dqb, int _id); +int quota_write_usage(struct quotafile *_qf, struct dqblk *_dqb, int _id); +#endif + +__END_DECLS + +/* fparseln(3) */ +#define FPARSELN_UNESCESC 0x01 +#define FPARSELN_UNESCCONT 0x02 +#define FPARSELN_UNESCCOMM 0x04 +#define FPARSELN_UNESCREST 0x08 +#define FPARSELN_UNESCALL 0x0f + +/* Flags for hexdump(3). */ +#define HD_COLUMN_MASK 0xff +#define HD_DELIM_MASK 0xff00 +#define HD_OMIT_COUNT (1 << 16) +#define HD_OMIT_HEX (1 << 17) +#define HD_OMIT_CHARS (1 << 18) + +/* Values for humanize_number(3)'s flags parameter. */ +#define HN_DECIMAL 0x01 +#define HN_NOSPACE 0x02 +#define HN_B 0x04 +#define HN_DIVISOR_1000 0x08 +#define HN_IEC_PREFIXES 0x10 + +/* Values for humanize_number(3)'s scale parameter. */ +#define HN_GETSCALE 0x10 +#define HN_AUTOSCALE 0x20 + +/* Return values from realhostname(). */ +#define HOSTNAME_FOUND 0 +#define HOSTNAME_INCORRECTNAME 1 +#define HOSTNAME_INVALIDADDR 2 +#define HOSTNAME_INVALIDNAME 3 + +/* Flags for pw_scan(). */ +#define PWSCAN_MASTER 0x01 +#define PWSCAN_WARN 0x02 + +/* Return values from uu_lock(). */ +#define UU_LOCK_INUSE 1 +#define UU_LOCK_OK 0 +#define UU_LOCK_OPEN_ERR (-1) +#define UU_LOCK_READ_ERR (-2) +#define UU_LOCK_CREAT_ERR (-3) +#define UU_LOCK_WRITE_ERR (-4) +#define UU_LOCK_LINK_ERR (-5) +#define UU_LOCK_TRY_ERR (-6) +#define UU_LOCK_OWNER_ERR (-7) + +#endif /* !_LIBUTIL_H_ */ diff --git a/pw/libutil/login_cap.c b/pw/libutil/login_cap.c new file mode 100644 index 0000000..b544b69 --- /dev/null +++ b/pw/libutil/login_cap.c @@ -0,0 +1,821 @@ +/*- + * Copyright (c) 1996 by + * Sean Eric Fagan <sef@kithrup.com> + * David Nugent <davidn@blaze.net.au> + * All rights reserved. + * + * Portions copyright (c) 1995,1997 + * Berkeley Software Design, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. This work was done expressly for inclusion into FreeBSD. Other use + * is permitted provided this notation is included. + * 4. Absolutely no warranty of function or purpose is made by the authors. + * 5. Modifications may be freely made to this file providing the above + * conditions are met. + * + * Low-level routines relating to the user capabilities database + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/param.h> +#include <errno.h> +#include <fcntl.h> +#include <libutil.h> +#include <login_cap.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "reallocarray.h" + +/* + * allocstr() + * Manage a single static pointer for handling a local char* buffer, + * resizing as necessary to contain the string. + * + * allocarray() + * Manage a static array for handling a group of strings, resizing + * when necessary. + */ + +static int lc_object_count = 0; + +static size_t internal_stringsz = 0; +static char * internal_string = NULL; +static size_t internal_arraysz = 0; +static const char ** internal_array = NULL; + +static char path_login_conf[] = _PATH_LOGIN_CONF; + +static char * +allocstr(const char *str) +{ + char *p; + + size_t sz = strlen(str) + 1; /* realloc() only if necessary */ + if (sz <= internal_stringsz) + p = strcpy(internal_string, str); + else if ((p = realloc(internal_string, sz)) != NULL) { + internal_stringsz = sz; + internal_string = strcpy(p, str); + } + return p; +} + + +static const char ** +allocarray(size_t sz) +{ + static const char **p; + + if (sz <= internal_arraysz) + p = internal_array; + else if ((p = reallocarray(internal_array, sz, sizeof(char*))) != NULL) { + internal_arraysz = sz; + internal_array = p; + } + return p; +} + + +/* + * arrayize() + * Turn a simple string <str> separated by any of + * the set of <chars> into an array. The last element + * of the array will be NULL, as is proper. + * Free using freearraystr() + */ + +static const char ** +arrayize(const char *str, const char *chars, int *size) +{ + int i; + char *ptr; + const char *cptr; + const char **res = NULL; + + /* count the sub-strings */ + for (i = 0, cptr = str; *cptr; i++) { + int count = strcspn(cptr, chars); + cptr += count; + if (*cptr) + ++cptr; + } + + /* alloc the array */ + if ((ptr = allocstr(str)) != NULL) { + if ((res = allocarray(++i)) == NULL) + free((void *)(uintptr_t)(const void *)str); + else { + /* now split the string */ + i = 0; + while (*ptr) { + int count = strcspn(ptr, chars); + res[i++] = ptr; + ptr += count; + if (*ptr) + *ptr++ = '\0'; + } + res[i] = NULL; + } + } + + if (size) + *size = i; + + return res; +} + + +/* + * login_close() + * Frees up all resources relating to a login class + * + */ + +void +login_close(login_cap_t * lc) +{ + if (lc) { + free(lc->lc_style); + free(lc->lc_class); + free(lc->lc_cap); + free(lc); + if (--lc_object_count == 0) { + free(internal_string); + free(internal_array); + internal_array = NULL; + internal_arraysz = 0; + internal_string = NULL; + internal_stringsz = 0; + cgetclose(); + } + } +} + + +/* + * login_getclassbyname() + * Get the login class by its name. + * If the name given is NULL or empty, the default class + * LOGIN_DEFCLASS (i.e., "default") is fetched. + * If the name given is LOGIN_MECLASS and + * 'pwd' argument is non-NULL and contains an non-NULL + * dir entry, then the file _FILE_LOGIN_CONF is picked + * up from that directory and used before the system + * login database. In that case the system login database + * is looked up using LOGIN_MECLASS, too, which is a bug. + * Return a filled-out login_cap_t structure, including + * class name, and the capability record buffer. + */ + +login_cap_t * +login_getclassbyname(char const *name, const struct passwd *pwd) +{ + login_cap_t *lc; + + if ((lc = malloc(sizeof(login_cap_t))) != NULL) { + int r, me, i = 0; + uid_t euid = 0; + gid_t egid = 0; + const char *msg = NULL; + const char *dir; + char userpath[MAXPATHLEN]; + + static char *login_dbarray[] = { NULL, NULL, NULL }; + + me = (name != NULL && strcmp(name, LOGIN_MECLASS) == 0); + dir = (!me || pwd == NULL) ? NULL : pwd->pw_dir; + /* + * Switch to user mode before checking/reading its ~/.login_conf + * - some NFSes have root read access disabled. + * + * XXX: This fails to configure additional groups. + */ + if (dir) { + euid = geteuid(); + egid = getegid(); + (void)setegid(pwd->pw_gid); + (void)seteuid(pwd->pw_uid); + } + + if (dir && snprintf(userpath, MAXPATHLEN, "%s/%s", dir, + _FILE_LOGIN_CONF) < MAXPATHLEN) { + if (_secure_path(userpath, pwd->pw_uid, pwd->pw_gid) != -1) + login_dbarray[i++] = userpath; + } + /* + * XXX: Why to add the system database if the class is `me'? + */ + if (_secure_path(path_login_conf, 0, 0) != -1) + login_dbarray[i++] = path_login_conf; + login_dbarray[i] = NULL; + + memset(lc, 0, sizeof(login_cap_t)); + lc->lc_cap = lc->lc_class = lc->lc_style = NULL; + + if (name == NULL || *name == '\0') + name = LOGIN_DEFCLASS; + + switch (cgetent(&lc->lc_cap, login_dbarray, name)) { + case -1: /* Failed, entry does not exist */ + if (me) + break; /* Don't retry default on 'me' */ + if (i == 0) + r = -1; + else if ((r = open(login_dbarray[0], O_RDONLY | O_CLOEXEC)) >= 0) + close(r); + /* + * If there's at least one login class database, + * and we aren't searching for a default class + * then complain about a non-existent class. + */ + if (r >= 0 || strcmp(name, LOGIN_DEFCLASS) != 0) + syslog(LOG_ERR, "login_getclass: unknown class '%s'", name); + /* fall-back to default class */ + name = LOGIN_DEFCLASS; + msg = "%s: no default/fallback class '%s'"; + if (cgetent(&lc->lc_cap, login_dbarray, name) != 0 && r >= 0) + break; + /* FALLTHROUGH - just return system defaults */ + case 0: /* success! */ + if ((lc->lc_class = strdup(name)) != NULL) { + if (dir) { + (void)seteuid(euid); + (void)setegid(egid); + } + ++lc_object_count; + return lc; + } + msg = "%s: strdup: %m"; + break; + case -2: + msg = "%s: retrieving class information: %m"; + break; + case -3: + msg = "%s: 'tc=' reference loop '%s'"; + break; + case 1: + msg = "couldn't resolve 'tc=' reference in '%s'"; + break; + default: + msg = "%s: unexpected cgetent() error '%s': %m"; + break; + } + if (dir) { + (void)seteuid(euid); + (void)setegid(egid); + } + if (msg != NULL) + syslog(LOG_ERR, msg, "login_getclass", name); + free(lc); + } + + return NULL; +} + + + +/* + * login_getclass() + * Get the login class for the system (only) login class database. + * Return a filled-out login_cap_t structure, including + * class name, and the capability record buffer. + */ + +login_cap_t * +login_getclass(const char *cls) +{ + return login_getclassbyname(cls, NULL); +} + + +/* + * login_getpwclass() + * Get the login class for a given password entry from + * the system (only) login class database. + * If the password entry's class field is not set, or + * the class specified does not exist, then use the + * default of LOGIN_DEFCLASS (i.e., "default") for an unprivileged + * user or that of LOGIN_DEFROOTCLASS (i.e., "root") for a super-user. + * Return a filled-out login_cap_t structure, including + * class name, and the capability record buffer. + */ + +login_cap_t * +login_getpwclass(const struct passwd *pwd) +{ + const char *cls = NULL; + + if (pwd != NULL) { + cls = pwd->pw_class; + if (cls == NULL || *cls == '\0') + cls = (pwd->pw_uid == 0) ? LOGIN_DEFROOTCLASS : LOGIN_DEFCLASS; + } + /* + * XXX: pwd should be unused by login_getclassbyname() unless cls is `me', + * so NULL can be passed instead of pwd for more safety. + */ + return login_getclassbyname(cls, pwd); +} + + +/* + * login_getuserclass() + * Get the `me' login class, allowing user overrides via ~/.login_conf. + * Note that user overrides are allowed only in the `me' class. + */ + +login_cap_t * +login_getuserclass(const struct passwd *pwd) +{ + return login_getclassbyname(LOGIN_MECLASS, pwd); +} + + +/* + * login_getcapstr() + * Given a login_cap entry, and a capability name, return the + * value defined for that capability, a default if not found, or + * an error string on error. + */ + +const char * +login_getcapstr(login_cap_t *lc, const char *cap, const char *def, const char *error) +{ + char *res; + int ret; + + if (lc == NULL || cap == NULL || lc->lc_cap == NULL || *cap == '\0') + return def; + + if ((ret = cgetstr(lc->lc_cap, cap, &res)) == -1) + return def; + return (ret >= 0) ? res : error; +} + + +/* + * login_getcaplist() + * Given a login_cap entry, and a capability name, return the + * value defined for that capability split into an array of + * strings. + */ + +const char ** +login_getcaplist(login_cap_t *lc, const char *cap, const char *chars) +{ + const char *lstring; + + if (chars == NULL) + chars = ", \t"; + if ((lstring = login_getcapstr(lc, cap, NULL, NULL)) != NULL) + return arrayize(lstring, chars, NULL); + return NULL; +} + + +/* + * login_getpath() + * From the login_cap_t <lc>, get the capability <cap> which is + * formatted as either a space or comma delimited list of paths + * and append them all into a string and separate by semicolons. + * If there is an error of any kind, return <error>. + */ + +const char * +login_getpath(login_cap_t *lc, const char *cap, const char *error) +{ + const char *str; + char *ptr; + int count; + + str = login_getcapstr(lc, cap, NULL, NULL); + if (str == NULL) + return error; + ptr = __DECONST(char *, str); /* XXXX Yes, very dodgy */ + while (*ptr) { + count = strcspn(ptr, ", \t"); + ptr += count; + if (*ptr) + *ptr++ = ':'; + } + return str; +} + + +static int +isinfinite(const char *s) +{ + static const char *infs[] = { + "infinity", + "inf", + "unlimited", + "unlimit", + "-1", + NULL + }; + const char **i = &infs[0]; + + while (*i != NULL) { + if (strcasecmp(s, *i) == 0) + return 1; + ++i; + } + return 0; +} + + +static u_quad_t +rmultiply(u_quad_t n1, u_quad_t n2) +{ + u_quad_t m, r; + int b1, b2; + + static int bpw = 0; + + /* Handle simple cases */ + if (n1 == 0 || n2 == 0) + return 0; + if (n1 == 1) + return n2; + if (n2 == 1) + return n1; + + /* + * sizeof() returns number of bytes needed for storage. + * This may be different from the actual number of useful bits. + */ + if (!bpw) { + bpw = sizeof(u_quad_t) * 8; + while (((u_quad_t)1 << (bpw-1)) == 0) + --bpw; + } + + /* + * First check the magnitude of each number. If the sum of the + * magnatude is way to high, reject the number. (If this test + * is not done then the first multiply below may overflow.) + */ + for (b1 = bpw; (((u_quad_t)1 << (b1-1)) & n1) == 0; --b1) + ; + for (b2 = bpw; (((u_quad_t)1 << (b2-1)) & n2) == 0; --b2) + ; + if (b1 + b2 - 2 > bpw) { + errno = ERANGE; + return (UQUAD_MAX); + } + + /* + * Decompose the multiplication to be: + * h1 = n1 & ~1 + * h2 = n2 & ~1 + * l1 = n1 & 1 + * l2 = n2 & 1 + * (h1 + l1) * (h2 + l2) + * (h1 * h2) + (h1 * l2) + (l1 * h2) + (l1 * l2) + * + * Since h1 && h2 do not have the low bit set, we can then say: + * + * (h1>>1 * h2>>1 * 4) + ... + * + * So if (h1>>1 * h2>>1) > (1<<(bpw - 2)) then the result will + * overflow. + * + * Finally, if MAX - ((h1 * l2) + (l1 * h2) + (l1 * l2)) < (h1*h2) + * then adding in residual amout will cause an overflow. + */ + + m = (n1 >> 1) * (n2 >> 1); + if (m >= ((u_quad_t)1 << (bpw-2))) { + errno = ERANGE; + return (UQUAD_MAX); + } + m *= 4; + + r = (n1 & n2 & 1) + + (n2 & 1) * (n1 & ~(u_quad_t)1) + + (n1 & 1) * (n2 & ~(u_quad_t)1); + + if ((u_quad_t)(m + r) < m) { + errno = ERANGE; + return (UQUAD_MAX); + } + m += r; + + return (m); +} + + +/* + * login_getcaptime() + * From the login_cap_t <lc>, get the capability <cap>, which is + * formatted as a time (e.g., "<cap>=10h3m2s"). If <cap> is not + * present in <lc>, return <def>; if there is an error of some kind, + * return <error>. + */ + +rlim_t +login_getcaptime(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error) +{ + char *res, *ep, *oval; + int r; + rlim_t tot; + + errno = 0; + if (lc == NULL || lc->lc_cap == NULL) + return def; + + /* + * Look for <cap> in lc_cap. + * If it's not there (-1), return <def>. + * If there's an error, return <error>. + */ + + if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1) + return def; + else if (r < 0) { + errno = ERANGE; + return error; + } + + /* "inf" and "infinity" are special cases */ + if (isinfinite(res)) + return RLIM_INFINITY; + + /* + * Now go through the string, turning something like 1h2m3s into + * an integral value. Whee. + */ + + errno = 0; + tot = 0; + oval = res; + while (*res) { + rlim_t tim = strtoq(res, &ep, 0); + rlim_t mult = 1; + + if (ep == NULL || ep == res || errno != 0) { + invalid: + syslog(LOG_WARNING, "login_getcaptime: class '%s' bad value %s=%s", + lc->lc_class, cap, oval); + errno = ERANGE; + return error; + } + /* Look for suffixes */ + switch (*ep++) { + case 0: + ep--; + break; /* end of string */ + case 's': case 'S': /* seconds */ + break; + case 'm': case 'M': /* minutes */ + mult = 60; + break; + case 'h': case 'H': /* hours */ + mult = 60L * 60L; + break; + case 'd': case 'D': /* days */ + mult = 60L * 60L * 24L; + break; + case 'w': case 'W': /* weeks */ + mult = 60L * 60L * 24L * 7L; + break; + case 'y': case 'Y': /* 365-day years */ + mult = 60L * 60L * 24L * 365L; + break; + default: + goto invalid; + } + res = ep; + tot += rmultiply(tim, mult); + if (errno) + goto invalid; + } + + return tot; +} + + +/* + * login_getcapnum() + * From the login_cap_t <lc>, extract the numerical value <cap>. + * If it is not present, return <def> for a default, and return + * <error> if there is an error. + * Like login_getcaptime(), only it only converts to a number, not + * to a time; "infinity" and "inf" are 'special.' + */ + +rlim_t +login_getcapnum(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error) +{ + char *ep, *res; + int r; + rlim_t val; + + if (lc == NULL || lc->lc_cap == NULL) + return def; + + /* + * For BSDI compatibility, try for the tag=<val> first + */ + if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1) { + long lval; + /* string capability not present, so try for tag#<val> as numeric */ + if ((r = cgetnum(lc->lc_cap, cap, &lval)) == -1) + return def; /* Not there, so return default */ + else if (r >= 0) + return (rlim_t)lval; + } + + if (r < 0) { + errno = ERANGE; + return error; + } + + if (isinfinite(res)) + return RLIM_INFINITY; + + errno = 0; + val = strtoq(res, &ep, 0); + if (ep == NULL || ep == res || errno != 0) { + syslog(LOG_WARNING, "login_getcapnum: class '%s' bad value %s=%s", + lc->lc_class, cap, res); + errno = ERANGE; + return error; + } + + return val; +} + + + +/* + * login_getcapsize() + * From the login_cap_t <lc>, extract the capability <cap>, which is + * formatted as a size (e.g., "<cap>=10M"); it can also be "infinity". + * If not present, return <def>, or <error> if there is an error of + * some sort. + */ + +rlim_t +login_getcapsize(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error) +{ + char *ep, *res, *oval; + int r; + rlim_t tot; + + if (lc == NULL || lc->lc_cap == NULL) + return def; + + if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1) + return def; + else if (r < 0) { + errno = ERANGE; + return error; + } + + if (isinfinite(res)) + return RLIM_INFINITY; + + errno = 0; + tot = 0; + oval = res; + while (*res) { + rlim_t siz = strtoq(res, &ep, 0); + rlim_t mult = 1; + + if (ep == NULL || ep == res || errno != 0) { + invalid: + syslog(LOG_WARNING, "login_getcapsize: class '%s' bad value %s=%s", + lc->lc_class, cap, oval); + errno = ERANGE; + return error; + } + switch (*ep++) { + case 0: /* end of string */ + ep--; + break; + case 'b': case 'B': /* 512-byte blocks */ + mult = 512; + break; + case 'k': case 'K': /* 1024-byte Kilobytes */ + mult = 1024; + break; + case 'm': case 'M': /* 1024-k kbytes */ + mult = 1024 * 1024; + break; + case 'g': case 'G': /* 1Gbyte */ + mult = 1024 * 1024 * 1024; + break; + case 't': case 'T': /* 1TBte */ + mult = 1024LL * 1024LL * 1024LL * 1024LL; + break; + default: + goto invalid; + } + res = ep; + tot += rmultiply(siz, mult); + if (errno) + goto invalid; + } + + return tot; +} + + +/* + * login_getcapbool() + * From the login_cap_t <lc>, check for the existence of the capability + * of <cap>. Return <def> if <lc>->lc_cap is NULL, otherwise return + * the whether or not <cap> exists there. + */ + +int +login_getcapbool(login_cap_t *lc, const char *cap, int def) +{ + if (lc == NULL || lc->lc_cap == NULL) + return def; + return (cgetcap(lc->lc_cap, cap, ':') != NULL); +} + + +/* + * login_getstyle() + * Given a login_cap entry <lc>, and optionally a type of auth <auth>, + * and optionally a style <style>, find the style that best suits these + * rules: + * 1. If <auth> is non-null, look for an "auth-<auth>=" string + * in the capability; if not present, default to "auth=". + * 2. If there is no auth list found from (1), default to + * "passwd" as an authorization list. + * 3. If <style> is non-null, look for <style> in the list of + * authorization methods found from (2); if <style> is NULL, default + * to LOGIN_DEFSTYLE ("passwd"). + * 4. If the chosen style is found in the chosen list of authorization + * methods, return that; otherwise, return NULL. + * E.g.: + * login_getstyle(lc, NULL, "ftp"); + * login_getstyle(lc, "login", NULL); + * login_getstyle(lc, "skey", "network"); + */ + +const char * +login_getstyle(login_cap_t *lc, const char *style, const char *auth) +{ + int i; + const char **authtypes = NULL; + char *auths= NULL; + char realauth[64]; + + static const char *defauthtypes[] = { LOGIN_DEFSTYLE, NULL }; + + if (auth != NULL && *auth != '\0') { + if (snprintf(realauth, sizeof realauth, "auth-%s", auth) < (int)sizeof(realauth)) + authtypes = login_getcaplist(lc, realauth, NULL); + } + + if (authtypes == NULL) + authtypes = login_getcaplist(lc, "auth", NULL); + + if (authtypes == NULL) + authtypes = defauthtypes; + + /* + * We have at least one authtype now; auths is a comma-separated + * (or space-separated) list of authentication types. We have to + * convert from this to an array of char*'s; authtypes then gets this. + */ + i = 0; + if (style != NULL && *style != '\0') { + while (authtypes[i] != NULL && strcmp(style, authtypes[i]) != 0) + i++; + } + + lc->lc_style = NULL; + if (authtypes[i] != NULL && (auths = strdup(authtypes[i])) != NULL) + lc->lc_style = auths; + + if (lc->lc_style != NULL) + lc->lc_style = strdup(lc->lc_style); + + return lc->lc_style; +} diff --git a/pw/libutil/login_cap.h b/pw/libutil/login_cap.h new file mode 100644 index 0000000..cc33aa1 --- /dev/null +++ b/pw/libutil/login_cap.h @@ -0,0 +1,164 @@ +/*- + * Copyright (c) 1996 by + * Sean Eric Fagan <sef@kithrup.com> + * David Nugent <davidn@blaze.net.au> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. This work was done expressly for inclusion into FreeBSD. Other use + * is permitted provided this notation is included. + * 4. Absolutely no warranty of function or purpose is made by the authors. + * 5. Modifications may be freely made to this file providing the above + * conditions are met. + * + * Low-level routines relating to the user capabilities database + * + * Was login_cap.h,v 1.9 1997/05/07 20:00:01 eivind Exp + * $FreeBSD$ + */ + +#ifndef _LOGIN_CAP_H_ +#define _LOGIN_CAP_H_ + +#define LOGIN_DEFCLASS "default" +#define LOGIN_DEFROOTCLASS "root" +#define LOGIN_MECLASS "me" +#define LOGIN_DEFSTYLE "passwd" +#define LOGIN_DEFSERVICE "login" +#define LOGIN_DEFUMASK 022 +#define LOGIN_DEFPRI 0 +#define _PATH_LOGIN_CONF "/etc/login.conf" +#define _FILE_LOGIN_CONF ".login_conf" +#define _PATH_AUTHPROG "/usr/libexec/login_" + +#define LOGIN_SETGROUP 0x0001 /* set group */ +#define LOGIN_SETLOGIN 0x0002 /* set login (via setlogin) */ +#define LOGIN_SETPATH 0x0004 /* set path */ +#define LOGIN_SETPRIORITY 0x0008 /* set priority */ +#define LOGIN_SETRESOURCES 0x0010 /* set resources (cputime, etc.) */ +#define LOGIN_SETUMASK 0x0020 /* set umask, obviously */ +#define LOGIN_SETUSER 0x0040 /* set user (via setuid) */ +#define LOGIN_SETENV 0x0080 /* set user environment */ +#define LOGIN_SETMAC 0x0100 /* set user default MAC label */ +#define LOGIN_SETCPUMASK 0x0200 /* set user cpumask */ +#define LOGIN_SETLOGINCLASS 0x0400 /* set login class in the kernel */ +#define LOGIN_SETALL 0x07ff /* set everything */ + +#define BI_AUTH "authorize" /* accepted authentication */ +#define BI_REJECT "reject" /* rejected authentication */ +#define BI_CHALLENG "reject challenge" /* reject with a challenge */ +#define BI_SILENT "reject silent" /* reject silently */ +#define BI_REMOVE "remove" /* remove file on error */ +#define BI_ROOTOKAY "authorize root" /* root authenticated */ +#define BI_SECURE "authorize secure" /* okay on non-secure line */ +#define BI_SETENV "setenv" /* set environment variable */ +#define BI_VALUE "value" /* set local variable */ + +#define AUTH_OKAY 0x01 /* user authenticated */ +#define AUTH_ROOTOKAY 0x02 /* root login okay */ +#define AUTH_SECURE 0x04 /* secure login */ +#define AUTH_SILENT 0x08 /* silent rejection */ +#define AUTH_CHALLENGE 0x10 /* a chellenge was given */ + +#define AUTH_ALLOW (AUTH_OKAY | AUTH_ROOTOKAY | AUTH_SECURE) + +typedef struct login_cap { + char *lc_class; + char *lc_cap; + char *lc_style; +} login_cap_t; + +typedef struct login_time { + u_short lt_start; /* Start time */ + u_short lt_end; /* End time */ +#define LTM_NONE 0x00 +#define LTM_SUN 0x01 +#define LTM_MON 0x02 +#define LTM_TUE 0x04 +#define LTM_WED 0x08 +#define LTM_THU 0x10 +#define LTM_FRI 0x20 +#define LTM_SAT 0x40 +#define LTM_ANY 0x7F +#define LTM_WK 0x3E +#define LTM_WD 0x41 + u_char lt_dow; /* Days of week */ +} login_time_t; + +#define LC_MAXTIMES 64 + +#include <sys/cdefs.h> +__BEGIN_DECLS +struct passwd; + +void login_close(login_cap_t *); +login_cap_t *login_getclassbyname(const char *, const struct passwd *); +login_cap_t *login_getclass(const char *); +login_cap_t *login_getpwclass(const struct passwd *); +login_cap_t *login_getuserclass(const struct passwd *); + +const char *login_getcapstr(login_cap_t *, const char *, const char *, + const char *); +const char **login_getcaplist(login_cap_t *, const char *, const char *); +const char *login_getstyle(login_cap_t *, const char *, const char *); +const char *login_getpath(login_cap_t *, const char *, const char *); +int login_getcapbool(login_cap_t *, const char *, int); +const char *login_setcryptfmt(login_cap_t *, const char *, const char *); + +int setclasscontext(const char *, unsigned int); +void setclasscpumask(login_cap_t *); +int setusercontext(login_cap_t *, const struct passwd *, uid_t, unsigned int); +void setclassresources(login_cap_t *); +void setclassenvironment(login_cap_t *, const struct passwd *, int); + +/* Most of these functions are deprecated */ +int auth_approve(login_cap_t *, const char *, const char *); +int auth_check(const char *, const char *, const char *, const char *, int *); +void auth_env(void); +char *auth_mkvalue(const char *); +int auth_response(const char *, const char *, const char *, const char *, int *, + const char *, const char *); +void auth_rmfiles(void); +int auth_scan(int); +int auth_script(const char *, ...); +int auth_script_data(const char *, int, const char *, ...); +char *auth_valud(const char *); +int auth_setopt(const char *, const char *); +void auth_clropts(void); + +void auth_checknologin(login_cap_t *); +int auth_cat(const char *); + +int auth_ttyok(login_cap_t *, const char *); +int auth_hostok(login_cap_t *, const char *, char const *); +int auth_timeok(login_cap_t *, time_t); + +struct tm; + +login_time_t parse_lt(const char *); +int in_lt(const login_time_t *, time_t *); +int in_ltm(const login_time_t *, struct tm *, time_t *); +int in_ltms(const login_time_t *, struct tm *, time_t *); +int in_lts(const login_time_t *, time_t *); + +/* helper functions */ + +int login_strinlist(const char **, char const *, int); +int login_str2inlist(const char **, const char *, const char *, int); +login_time_t * login_timelist(login_cap_t *, char const *, int *, + login_time_t **); +int login_ttyok(login_cap_t *, const char *, const char *, const char *); +int login_hostok(login_cap_t *, const char *, const char *, const char *, + const char *); + +__END_DECLS + +#endif /* _LOGIN_CAP_H_ */ diff --git a/pw/libutil/login_crypt.c b/pw/libutil/login_crypt.c new file mode 100644 index 0000000..5d65d3a --- /dev/null +++ b/pw/libutil/login_crypt.c @@ -0,0 +1,50 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2000 Brian Fundakowski Feldman + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> + +#include <login_cap.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +const char * +login_setcryptfmt(login_cap_t *lc, const char *def, const char *error) { + const char *cipher; + + cipher = login_getcapstr(lc, "passwd_format", def, NULL); + if (getenv("CRYPT_DEBUG") != NULL) + fprintf(stderr, "login_setcryptfmt: " + "passwd_format = %s\n", cipher); + if (cipher == NULL) + return (error); + return (cipher); +} diff --git a/pw/libutil/pw_scan.c b/pw/libutil/pw_scan.c new file mode 100644 index 0000000..f680a90 --- /dev/null +++ b/pw/libutil/pw_scan.c @@ -0,0 +1,207 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__SCCSID("@(#)pw_scan.c 8.3 (Berkeley) 4/2/94"); +__FBSDID("$FreeBSD$"); + +/* + * This module is used to "verify" password entries by chpass(1) and + * pwd_mkdb(8). + */ + +#include <sys/param.h> + +#include <err.h> +#include <errno.h> +#include <pwd.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "pw_scan.h" + +/* + * Some software assumes that IDs are short. We should emit warnings + * for id's which cannot be stored in a short, but we are more liberal + * by default, warning for IDs greater than USHRT_MAX. + * + * If pw_big_ids_warning is -1 on entry to pw_scan(), it will be set based + * on the existence of PW_SCAN_BIG_IDS in the environment. + * + * It is believed all baseline system software that can not handle the + * normal ID sizes is now gone so pw_big_ids_warning is disabled for now. + * But the code has been left in place in case end-users want to re-enable + * it and/or for the next time the ID sizes get bigger but pieces of the + * system lag behind. + */ +static int pw_big_ids_warning = 0; + +void +__pw_initpwd(struct passwd *pwd) +{ + static char nul[] = ""; + + memset(pwd, 0, sizeof(*pwd)); + pwd->pw_uid = (uid_t)-1; /* Considered least likely to lead to */ + pwd->pw_gid = (gid_t)-1; /* a security issue. */ + pwd->pw_name = nul; + pwd->pw_passwd = nul; + pwd->pw_class = nul; + pwd->pw_gecos = nul; + pwd->pw_dir = nul; + pwd->pw_shell = nul; +} + +int +__pw_scan(char *bp, struct passwd *pw, int flags) +{ + uid_t id; + int root; + char *ep, *p, *sh; + unsigned long temp; + + if (pw_big_ids_warning == -1) + pw_big_ids_warning = getenv("PW_SCAN_BIG_IDS") == NULL ? 1 : 0; + + if (!(pw->pw_name = strsep(&bp, ":"))) /* login */ + goto fmt; + root = !strcmp(pw->pw_name, "root"); + + if (!(pw->pw_passwd = strsep(&bp, ":"))) /* passwd */ + goto fmt; + + if (!(p = strsep(&bp, ":"))) /* uid */ + goto fmt; + if (p[0]) { + } else { + if (pw->pw_name[0] != '+' && pw->pw_name[0] != '-') { + if (flags & _PWSCAN_WARN) + warnx("no uid for user %s", pw->pw_name); + return (0); + } + } + errno = 0; + temp = strtoul(p, &ep, 10); + if ((temp == ULONG_MAX && errno == ERANGE) || temp > UID_MAX) { + if (flags & _PWSCAN_WARN) + warnx("%s > max uid value (%u)", p, UID_MAX); + return (0); + } + id = temp; + if (*ep != '\0') { + if (flags & _PWSCAN_WARN) + warnx("%s uid is incorrect", p); + return (0); + } + if (root && id) { + if (flags & _PWSCAN_WARN) + warnx("root uid should be 0"); + return (0); + } + if (flags & _PWSCAN_WARN && pw_big_ids_warning && id > USHRT_MAX) { + warnx("%s > recommended max uid value (%u)", p, USHRT_MAX); + /*return (0);*/ /* THIS SHOULD NOT BE FATAL! */ + } + pw->pw_uid = id; + + if (!(p = strsep(&bp, ":"))) /* gid */ + goto fmt; + if (p[0]) { + } else { + if (pw->pw_name[0] != '+' && pw->pw_name[0] != '-') { + if (flags & _PWSCAN_WARN) + warnx("no gid for user %s", pw->pw_name); + return (0); + } + } + errno = 0; + temp = strtoul(p, &ep, 10); + if ((temp == ULONG_MAX && errno == ERANGE) || temp > GID_MAX) { + if (flags & _PWSCAN_WARN) + warnx("%s > max gid value (%u)", p, GID_MAX); + return (0); + } + id = temp; + if (*ep != '\0') { + if (flags & _PWSCAN_WARN) + warnx("%s gid is incorrect", p); + return (0); + } + if (flags & _PWSCAN_WARN && pw_big_ids_warning && id > USHRT_MAX) { + warnx("%s > recommended max gid value (%u)", p, USHRT_MAX); + /* return (0); This should not be fatal! */ + } + pw->pw_gid = id; + + if (flags & _PWSCAN_MASTER ) { + if (!(pw->pw_class = strsep(&bp, ":"))) /* class */ + goto fmt; + + if (!(p = strsep(&bp, ":"))) /* change */ + goto fmt; + pw->pw_change = atol(p); + + if (!(p = strsep(&bp, ":"))) /* expire */ + goto fmt; + pw->pw_expire = atol(p); + } + if (!(pw->pw_gecos = strsep(&bp, ":"))) /* gecos */ + goto fmt; + + if (!(pw->pw_dir = strsep(&bp, ":"))) /* directory */ + goto fmt; + + if (!(pw->pw_shell = strsep(&bp, ":"))) /* shell */ + goto fmt; + + p = pw->pw_shell; + if (root && *p) { /* empty == /bin/sh */ + for (setusershell();;) { + if (!(sh = getusershell())) { + if (flags & _PWSCAN_WARN) + warnx("warning, unknown root shell"); + break; + } + if (!strcmp(p, sh)) + break; + } + endusershell(); + } + + if ((p = strsep(&bp, ":"))) { /* too many */ +fmt: + if (flags & _PWSCAN_WARN) + warnx("corrupted entry"); + return (0); + } + return (1); +} diff --git a/pw/libutil/pw_scan.h b/pw/libutil/pw_scan.h new file mode 100644 index 0000000..b567036 --- /dev/null +++ b/pw/libutil/pw_scan.h @@ -0,0 +1,39 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pw_scan.h 8.1 (Berkeley) 4/1/94 + * $FreeBSD$ + */ + +#define _PWSCAN_MASTER 0x01 +#define _PWSCAN_WARN 0x02 + +extern void __pw_initpwd(struct passwd *); +extern int __pw_scan(char *, struct passwd *, int); diff --git a/pw/libutil/pw_util.c b/pw/libutil/pw_util.c new file mode 100644 index 0000000..61d4ef4 --- /dev/null +++ b/pw/libutil/pw_util.c @@ -0,0 +1,685 @@ +/*-- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2002 Networks Associates Technology, Inc. + * All rights reserved. + * + * Portions of this software were developed for the FreeBSD Project by + * ThinkSec AS and NAI Labs, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 + * ("CBOSS"), as part of the DARPA CHATS research program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +__SCCSID("@(#)pw_util.c 8.3 (Berkeley) 4/2/94"); + +/* + * This file is used by all the "password" programs; vipw(8), chpass(1), + * and passwd(1). + */ + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <inttypes.h> +#include <paths.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libutil.h" +#include "reallocarray.h" + +static pid_t editpid = -1; +static int lockfd = -1; +static char masterpasswd[PATH_MAX]; +static char passwd_dir[PATH_MAX]; +static char tempname[PATH_MAX]; +static int initialized; + +#if 0 +void +pw_cont(int sig) +{ + + if (editpid != -1) + kill(editpid, sig); +} +#endif + +/* + * Initialize statics and set limits, signals & umask to try to avoid + * interruptions, crashes etc. that might expose passord data. + */ +int +pw_init(const char *dir, const char *master) +{ +#if 0 + struct rlimit rlim; +#endif + + if (dir == NULL) { + strcpy(passwd_dir, _PATH_PWD); + } else { + if (strlen(dir) >= sizeof(passwd_dir)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(passwd_dir, dir); + } + + if (master == NULL) { + if (dir == NULL) { + strcpy(masterpasswd, _PATH_MASTERPASSWD); + } else if (snprintf(masterpasswd, sizeof(masterpasswd), "%s/%s", + passwd_dir, _MASTERPASSWD) > (int)sizeof(masterpasswd)) { + errno = ENAMETOOLONG; + return (-1); + } + } else { + if (strlen(master) >= sizeof(masterpasswd)) { + errno = ENAMETOOLONG; + return (-1); + } + strcpy(masterpasswd, master); + } + + /* + * The code that follows is extremely disruptive to the calling + * process, and is therefore disabled until someone can conceive + * of a realistic scenario where it would fend off a compromise. + * Race conditions concerning the temporary files can be guarded + * against in other ways than masking signals (by checking stat(2) + * results after creation). + */ +#if 0 + /* Unlimited resource limits. */ + rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; + (void)setrlimit(RLIMIT_CPU, &rlim); + (void)setrlimit(RLIMIT_FSIZE, &rlim); + (void)setrlimit(RLIMIT_STACK, &rlim); + (void)setrlimit(RLIMIT_DATA, &rlim); + (void)setrlimit(RLIMIT_RSS, &rlim); + + /* Don't drop core (not really necessary, but GP's). */ + rlim.rlim_cur = rlim.rlim_max = 0; + (void)setrlimit(RLIMIT_CORE, &rlim); + + /* Turn off signals. */ + (void)signal(SIGALRM, SIG_IGN); + (void)signal(SIGHUP, SIG_IGN); + (void)signal(SIGINT, SIG_IGN); + (void)signal(SIGPIPE, SIG_IGN); + (void)signal(SIGQUIT, SIG_IGN); + (void)signal(SIGTERM, SIG_IGN); + (void)signal(SIGCONT, pw_cont); + + /* Create with exact permissions. */ + (void)umask(0); +#endif + initialized = 1; + return (0); +} + +/* + * Lock the master password file. + */ +int +pw_lock(void) +{ + + if (*masterpasswd == '\0') + return (-1); + + /* + * If the master password file doesn't exist, the system is hosed. + * Might as well try to build one. Set the close-on-exec bit so + * that users can't get at the encrypted passwords while editing. + * Open should allow flock'ing the file; see 4.4BSD. XXX + */ + for (;;) { + struct stat st; + + lockfd = flopen(masterpasswd, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0); + if (lockfd == -1) { + if (errno == EWOULDBLOCK) { + errx(1, "the password db file is busy"); + } else { + err(1, "could not lock the passwd file"); + } + } + + /* + * If the password file was replaced while we were trying to + * get the lock, our hardlink count will be 0 and we have to + * close and retry. + */ + if (fstat(lockfd, &st) == -1) + err(1, "fstat() failed"); + if (st.st_nlink != 0) + break; + close(lockfd); + lockfd = -1; + } + return (lockfd); +} + +/* + * Create and open a presumably safe temp file for editing the password + * data, and copy the master password file into it. + */ +int +pw_tmp(int mfd) +{ + char buf[8192]; + ssize_t nr; + const char *p; + int tfd; + + if (*masterpasswd == '\0') + return (-1); + if ((p = strrchr(masterpasswd, '/'))) + ++p; + else + p = masterpasswd; + if (snprintf(tempname, sizeof(tempname), "%.*spw.XXXXXX", + (int)(p - masterpasswd), masterpasswd) >= (int)sizeof(tempname)) { + errno = ENAMETOOLONG; + return (-1); + } + if ((tfd = mkostemp(tempname, 0)) == -1) + return (-1); + if (mfd != -1) { + while ((nr = read(mfd, buf, sizeof(buf))) > 0) + if (write(tfd, buf, (size_t)nr) != nr) + break; + if (nr != 0) { + unlink(tempname); + *tempname = '\0'; + close(tfd); + return (-1); + } + } + return (tfd); +} + +/* + * Regenerate the password database. + */ +int +pw_mkdb(const char *user) +{ + int pstat; + pid_t pid; + + (void)fflush(stderr); + switch ((pid = fork())) { + case -1: + return (-1); + case 0: + /* child */ + if (user == NULL) + execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", + "-d", passwd_dir, tempname, (char *)NULL); + else + execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", + "-d", passwd_dir, "-u", user, tempname, + (char *)NULL); + _exit(1); + /* NOTREACHED */ + default: + /* parent */ + break; + } + if (waitpid(pid, &pstat, 0) == -1) + return (-1); + if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) + return (0); + errno = 0; + return (-1); +} + +/* + * Edit the temp file. Return -1 on error, >0 if the file was modified, 0 + * if it was not. + */ +int +pw_edit(int notsetuid) +{ + struct sigaction sa, sa_int, sa_quit; + sigset_t oldsigset, nsigset; + struct stat st1, st2; + const char *editor; + int pstat; + + if ((editor = getenv("EDITOR")) == NULL) + editor = _PATH_VI; + if (stat(tempname, &st1) == -1) + return (-1); + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, &sa_int); + sigaction(SIGQUIT, &sa, &sa_quit); + sigemptyset(&nsigset); + sigaddset(&nsigset, SIGCHLD); + sigprocmask(SIG_BLOCK, &nsigset, &oldsigset); + switch ((editpid = fork())) { + case -1: + return (-1); + case 0: + sigaction(SIGINT, &sa_int, NULL); + sigaction(SIGQUIT, &sa_quit, NULL); + sigprocmask(SIG_SETMASK, &oldsigset, NULL); + if (notsetuid) { + if (setgid(getgid()) == -1) + err(1, "setgid"); + if (setuid(getuid()) == -1) + err(1, "setuid"); + } + execlp(editor, editor, tempname, (char *)NULL); + err(1, "%s", editor); + default: + /* parent */ + break; + } + for (;;) { + if (waitpid(editpid, &pstat, WUNTRACED) == -1) { + if (errno == EINTR) + continue; + unlink(tempname); + editpid = -1; + break; + } else if (WIFSTOPPED(pstat)) { + raise(WSTOPSIG(pstat)); + } else if (WIFEXITED(pstat)) { + if (WEXITSTATUS(pstat) != 0) + errx(1, "\"%s\" exited with status %d", editor, WEXITSTATUS(pstat)); + editpid = -1; + break; + } else { + unlink(tempname); + editpid = -1; + break; + } + } + sigaction(SIGINT, &sa_int, NULL); + sigaction(SIGQUIT, &sa_quit, NULL); + sigprocmask(SIG_SETMASK, &oldsigset, NULL); + if (stat(tempname, &st2) == -1) + return (-1); + return (st1.st_mtimespec.tv_sec != st2.st_mtimespec.tv_sec || + st1.st_mtimespec.tv_nsec != st2.st_mtimespec.tv_nsec); +} + +/* + * Clean up. Preserve errno for the caller's convenience. + */ +void +pw_fini(void) +{ + int serrno, status; + + if (!initialized) + return; + initialized = 0; + serrno = errno; + if (editpid != -1) { + kill(editpid, SIGTERM); + kill(editpid, SIGCONT); + waitpid(editpid, &status, 0); + editpid = -1; + } + if (*tempname != '\0') { + unlink(tempname); + *tempname = '\0'; + } + if (lockfd != -1) + close(lockfd); + errno = serrno; +} + +/* + * Compares two struct pwds. + */ +int +pw_equal(const struct passwd *pw1, const struct passwd *pw2) +{ + return (strcmp(pw1->pw_name, pw2->pw_name) == 0 && + pw1->pw_uid == pw2->pw_uid && + pw1->pw_gid == pw2->pw_gid && + strcmp(pw1->pw_class, pw2->pw_class) == 0 && + pw1->pw_change == pw2->pw_change && + pw1->pw_expire == pw2->pw_expire && + strcmp(pw1->pw_gecos, pw2->pw_gecos) == 0 && + strcmp(pw1->pw_dir, pw2->pw_dir) == 0 && + strcmp(pw1->pw_shell, pw2->pw_shell) == 0); +} + +/* + * Make a passwd line out of a struct passwd. + */ +char * +pw_make(const struct passwd *pw) +{ + char *line; + + asprintf(&line, "%s:%s:%ju:%ju:%s:%ju:%ju:%s:%s:%s", pw->pw_name, + pw->pw_passwd, (uintmax_t)pw->pw_uid, (uintmax_t)pw->pw_gid, + pw->pw_class, (uintmax_t)pw->pw_change, (uintmax_t)pw->pw_expire, + pw->pw_gecos, pw->pw_dir, pw->pw_shell); + return (line); +} + +/* + * Make a passwd line (in v7 format) out of a struct passwd + */ +char * +pw_make_v7(const struct passwd *pw) +{ + char *line; + + asprintf(&line, "%s:*:%ju:%ju:%s:%s:%s", pw->pw_name, + (uintmax_t)pw->pw_uid, (uintmax_t)pw->pw_gid, + pw->pw_gecos, pw->pw_dir, pw->pw_shell); + return (line); +} + +/* + * Copy password file from one descriptor to another, replacing, deleting + * or adding a single record on the way. + */ +int +pw_copy(int ffd, int tfd, const struct passwd *pw, struct passwd *old_pw) +{ + char *buf, *end, *line, *p, *q, *r, *tmp; + struct passwd *fpw; + const struct passwd *spw; + size_t len, size; + int eof, readlen; + char t; + + if (old_pw == NULL && pw == NULL) + return (-1); + + spw = old_pw; + /* deleting a user */ + if (pw == NULL) { + line = NULL; + } else { + if ((line = pw_make(pw)) == NULL) + return (-1); + } + + /* adding a user */ + if (spw == NULL) + spw = pw; + + /* initialize the buffer */ + if ((buf = malloc(size = 1024)) == NULL) + goto err; + + eof = 0; + len = 0; + p = q = end = buf; + for (;;) { + /* find the end of the current line */ + for (p = q; q < end && *q != '\0'; ++q) + if (*q == '\n') + break; + + /* if we don't have a complete line, fill up the buffer */ + if (q >= end) { + if (eof) + break; + while ((size_t)(q - p) >= size) { + if ((tmp = reallocarray(buf, 2, size)) == NULL) { + warnx("passwd line too long"); + goto err; + } + p = tmp + (p - buf); + q = tmp + (q - buf); + end = tmp + (end - buf); + buf = tmp; + size = size * 2; + } + if (p < end) { + q = memmove(buf, p, end - p); + end -= p - buf; + } else { + p = q = end = buf; + } + readlen = read(ffd, end, size - (end - buf)); + if (readlen == -1) + goto err; + else + len = (size_t)readlen; + if (len == 0 && p == buf) + break; + end += len; + len = end - buf; + if (len < size) { + eof = 1; + if (len > 0 && buf[len - 1] != '\n') + ++len, *end++ = '\n'; + } + continue; + } + + /* is it a blank line or a comment? */ + for (r = p; r < q && isspace(*r); ++r) + /* nothing */ ; + if (r == q || *r == '#') { + /* yep */ + if (write(tfd, p, q - p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + + /* is it the one we're looking for? */ + + t = *q; + *q = '\0'; + + fpw = pw_scan(r, PWSCAN_MASTER); + + /* + * fpw is either the struct passwd for the current line, + * or NULL if the line is malformed. + */ + + *q = t; + if (fpw == NULL || strcmp(fpw->pw_name, spw->pw_name) != 0) { + /* nope */ + if (fpw != NULL) + free(fpw); + if (write(tfd, p, q - p + 1) != q - p + 1) + goto err; + ++q; + continue; + } + if (old_pw && !pw_equal(fpw, old_pw)) { + warnx("entry inconsistent"); + free(fpw); + errno = EINVAL; /* hack */ + goto err; + } + free(fpw); + + /* it is, replace or remove it */ + if (line != NULL) { + len = strlen(line); + if (write(tfd, line, len) != (int)len) + goto err; + } else { + /* when removed, avoid the \n */ + q++; + } + /* we're done, just copy the rest over */ + for (;;) { + if (write(tfd, q, end - q) != end - q) + goto err; + q = buf; + readlen = read(ffd, buf, size); + if (readlen == 0) + break; + else + len = (size_t)readlen; + if (readlen == -1) + goto err; + end = buf + len; + } + goto done; + } + + /* if we got here, we didn't find the old entry */ + if (line == NULL) { + errno = ENOENT; + goto err; + } + len = strlen(line); + if ((size_t)write(tfd, line, len) != len || + write(tfd, "\n", 1) != 1) + goto err; + done: + free(line); + free(buf); + return (0); + err: + free(line); + free(buf); + return (-1); +} + +/* + * Return the current value of tempname. + */ +const char * +pw_tempname(void) +{ + + return (tempname); +} + +/* + * Duplicate a struct passwd. + */ +struct passwd * +pw_dup(const struct passwd *pw) +{ + char *dst; + struct passwd *npw; + ssize_t len; + + len = sizeof(*npw); + if (pw->pw_name != NULL) + len += strlen(pw->pw_name) + 1; + if (pw->pw_passwd != NULL) + len += strlen(pw->pw_passwd) + 1; + if (pw->pw_class != NULL) + len += strlen(pw->pw_class) + 1; + if (pw->pw_gecos != NULL) + len += strlen(pw->pw_gecos) + 1; + if (pw->pw_dir != NULL) + len += strlen(pw->pw_dir) + 1; + if (pw->pw_shell != NULL) + len += strlen(pw->pw_shell) + 1; + if ((npw = malloc((size_t)len)) == NULL) + return (NULL); + memcpy(npw, pw, sizeof(*npw)); + dst = (char *)npw + sizeof(*npw); + if (pw->pw_name != NULL) { + npw->pw_name = dst; + dst = stpcpy(npw->pw_name, pw->pw_name) + 1; + } + if (pw->pw_passwd != NULL) { + npw->pw_passwd = dst; + dst = stpcpy(npw->pw_passwd, pw->pw_passwd) + 1; + } + if (pw->pw_class != NULL) { + npw->pw_class = dst; + dst = stpcpy(npw->pw_class, pw->pw_class) + 1; + } + if (pw->pw_gecos != NULL) { + npw->pw_gecos = dst; + dst = stpcpy(npw->pw_gecos, pw->pw_gecos) + 1; + } + if (pw->pw_dir != NULL) { + npw->pw_dir = dst; + dst = stpcpy(npw->pw_dir, pw->pw_dir) + 1; + } + if (pw->pw_shell != NULL) { + npw->pw_shell = dst; + dst = stpcpy(npw->pw_shell, pw->pw_shell) + 1; + } + return (npw); +} + +#include "pw_scan.h" + +/* + * Wrapper around some internal libc functions. + */ + +void +pw_initpwd(struct passwd *pw) +{ + + __pw_initpwd(pw); +} + +struct passwd * +pw_scan(const char *line, int flags) +{ + struct passwd pw, *ret; + char *bp; + + if ((bp = strdup(line)) == NULL) + return (NULL); + __pw_initpwd(&pw); + if (!__pw_scan(bp, &pw, flags)) { + free(bp); + return (NULL); + } + ret = pw_dup(&pw); + free(bp); + return (ret); +} diff --git a/pw/pw_log.c b/pw/pw_log.c index 17e14d1..8bff850 100644 --- a/pw/pw_log.c +++ b/pw/pw_log.c @@ -36,6 +36,7 @@ static const char rcsid[] = #include <fcntl.h> #include <string.h> #include <stdarg.h> +#include <time.h> #include "pw.h" diff --git a/pw/pw_user.c b/pw/pw_user.c index 2eec317..826388d 100644 --- a/pw/pw_user.c +++ b/pw/pw_user.c @@ -55,6 +55,161 @@ static const char rcsid[] = #include "bitmap.h" #include "psdate.h" +#include <spawn.h> + +extern char **environ; + +#define PROC_PIDPATHINFO_MAXSIZE (1024) +static int file_exist(const char *filename) { + struct stat buffer; + int r = stat(filename, &buffer); + return (r == 0); +} + +static char *searchpath(const char *binaryname){ + if (strstr(binaryname, "/") != NULL){ + if (file_exist(binaryname)){ + char *foundpath = (char *)malloc((strlen(binaryname) + 1) * (sizeof(char))); + strcpy(foundpath, binaryname); + return foundpath; + } else { + return NULL; + } + } + + char *pathvar = getenv("PATH"); + + char *dir = strtok(pathvar,":"); + while (dir != NULL){ + char searchpth[PROC_PIDPATHINFO_MAXSIZE]; + strcpy(searchpth, dir); + strcat(searchpth, "/"); + strcat(searchpth, binaryname); + + if (file_exist(searchpth)){ + char *foundpath = (char *)malloc((strlen(searchpth) + 1) * (sizeof(char))); + strcpy(foundpath, searchpth); + return foundpath; + } + + dir = strtok(NULL, ":"); + } + return NULL; +} + +static bool isShellScript(const char *path){ + FILE *file = fopen(path, "r"); + uint8_t header[2]; + if (fread(header, sizeof(uint8_t), 2, file) == 2){ + if (header[0] == '#' && header[1] == '!'){ + fclose(file); + return true; + } + } + fclose(file); + return false; +} + +static char *getInterpreter(char *path){ + FILE *file = fopen(path, "r"); + char *interpreterLine = NULL; + unsigned long lineSize = 0; + getline(&interpreterLine, &lineSize, file); + + char *rawInterpreter = (interpreterLine+2); + rawInterpreter = strtok(rawInterpreter, " "); + rawInterpreter = strtok(rawInterpreter, "\n"); + + char *interpreter = (char *)malloc((strlen(rawInterpreter)+1) * sizeof(char)); + strcpy(interpreter, rawInterpreter); + + free(interpreterLine); + fclose(file); + return interpreter; +} + +static char *fixedCmd(const char *cmdStr){ + char *cmdCpy = (char *)malloc((strlen(cmdStr)+1) * sizeof(char)); + strcpy(cmdCpy, cmdStr); + + char *cmd = strtok(cmdCpy, " "); + + uint8_t size = strlen(cmd) + 1; + + char *args = cmdCpy + size; + if ((strlen(cmdStr) - strlen(cmd)) == 0) + args = NULL; + + char *abs_path = searchpath(cmd); + if (abs_path){ + bool isScript = isShellScript(abs_path); + if (isScript){ + char *interpreter = getInterpreter(abs_path); + + uint8_t commandSize = strlen(interpreter) + 1 + strlen(abs_path); + + if (args){ + commandSize += 1 + strlen(args); + } + + char *rawCommand = (char *)malloc(sizeof(char) * (commandSize + 1)); + strcpy(rawCommand, interpreter); + strcat(rawCommand, " "); + strcat(rawCommand, abs_path); + + if (args){ + strcat(rawCommand, " "); + strcat(rawCommand, args); + } + rawCommand[(commandSize)+1] = '\0'; + + free(interpreter); + free(abs_path); + free(cmdCpy); + + return rawCommand; + } else { + uint8_t commandSize = strlen(abs_path); + + if (args){ + commandSize += 1 + strlen(args); + } + + char *rawCommand = (char *)malloc(sizeof(char) * (commandSize + 1)); + strcat(rawCommand, abs_path); + + if (args){ + strcat(rawCommand, " "); + strcat(rawCommand, args); + } + rawCommand[(commandSize)+1] = '\0'; + + free(abs_path); + free(cmdCpy); + + return rawCommand; + } + } + return cmdCpy; +} + +int RunCmd(const char *cmd) { + pid_t pid; + char *rawCmd = fixedCmd(cmd); + char *argv[] = {"sh", "-c", (char*)rawCmd, NULL}; + int status; + status = posix_spawn(&pid, "/bin/sh", NULL, NULL, argv, environ); + if (status == 0) { + if (waitpid(pid, &status, 0) == -1) { + perror("waitpid"); + } + } else { + printf("posix_spawn: %s\n", strerror(status)); + } + free(rawCmd); + return status; +} + #define LOGNAMESIZE (MAXLOGNAME-1) static char locked_str[] = "*LOCKED*"; @@ -700,7 +855,7 @@ rmat(uid_t uid) snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", e->d_name); - system(tmp); + RunCmd(tmp); } } closedir(d); @@ -918,18 +1073,6 @@ pw_user_del(int argc, char **argv, char *arg1) errx(EX_NOUSER, "no such user `%s'", name); } - if (PWF._altdir == PWF_REGULAR && - ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { - if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { - if (!nis && nispasswd && *nispasswd != '/') - errx(EX_NOUSER, "Cannot remove NIS user `%s'", - name); - } else { - errx(EX_NOUSER, "Cannot remove non local user `%s'", - name); - } - } - id = pwd->pw_uid; if (name == NULL) name = pwd->pw_name; @@ -947,7 +1090,7 @@ pw_user_del(int argc, char **argv, char *arg1) if (access(file, F_OK) == 0) { snprintf(file, sizeof(file), "crontab -u %s -r", pwd->pw_name); - system(file); + RunCmd(file); } } @@ -1658,18 +1801,6 @@ pw_user_mod(int argc, char **argv, char *arg1) if (nis && nispasswd == NULL) nispasswd = cnf->nispasswd; - if (PWF._altdir == PWF_REGULAR && - ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) { - if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { - if (!nis && nispasswd && *nispasswd != '/') - errx(EX_NOUSER, "Cannot modify NIS user `%s'", - name); - } else { - errx(EX_NOUSER, "Cannot modify non local user `%s'", - name); - } - } - if (newname) { if (strcmp(pwd->pw_name, "root") == 0) errx(EX_DATAERR, "can't rename `root' account"); diff --git a/pw/reallocarray.c b/pw/reallocarray.c new file mode 100644 index 0000000..7310133 --- /dev/null +++ b/pw/reallocarray.c @@ -0,0 +1,11 @@ +#include "reallocarray.h" + +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) +void *reallocarray(void *optr, size_t nmemb, size_t size) { + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); +} diff --git a/pw/reallocarray.h b/pw/reallocarray.h new file mode 100644 index 0000000..eacd3cc --- /dev/null +++ b/pw/reallocarray.h @@ -0,0 +1,5 @@ +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +void *reallocarray(void *optr, size_t nmemb, size_t size); diff --git a/pw/sbuf/subr_prf.c b/pw/sbuf/subr_prf.c new file mode 100644 index 0000000..de4ff4f --- /dev/null +++ b/pw/sbuf/subr_prf.c @@ -0,0 +1,1310 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1986, 1988, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)subr_prf.c 8.3 (Berkeley) 1/21/94 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#ifdef _KERNEL +#include "opt_ddb.h" +#include "opt_printf.h" +#endif /* _KERNEL */ + +#include <sys/param.h> +#ifdef _KERNEL +#include <sys/systm.h> +#include <sys/lock.h> +#include <sys/kdb.h> +#include <sys/mutex.h> +#include <sys/sx.h> +#include <sys/kernel.h> +#include <sys/msgbuf.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#include <sys/proc.h> +#include <sys/stddef.h> +#include <sys/sysctl.h> +#include <sys/tty.h> +#include <sys/syslog.h> +#include <sys/cons.h> +#include <sys/uio.h> +#else /* !_KERNEL */ +#include <errno.h> +#endif +#include <ctype.h> +#include <sys/sbuf.h> + +#ifdef DDB +#include <ddb/ddb.h> +#endif + +/* + * Note that stdarg.h and the ANSI style va_start macro is used for both + * ANSI and traditional C compilers. + */ +#ifdef _KERNEL +#include <machine/stdarg.h> +#else +#include <stdarg.h> +#endif + +/* + * This is needed for sbuf_putbuf() when compiled into userland. Due to the + * shared nature of this file, it's the only place to put it. + */ +#ifndef _KERNEL +#include <stdio.h> +#endif + +#ifdef _KERNEL + +#define TOCONS 0x01 +#define TOTTY 0x02 +#define TOLOG 0x04 + +/* Max number conversion buffer length: a u_quad_t in base 2, plus NUL byte. */ +#define MAXNBUF (sizeof(intmax_t) * NBBY + 1) + +struct putchar_arg { + int flags; + int pri; + struct tty *tty; + char *p_bufr; + size_t n_bufr; + char *p_next; + size_t remain; +}; + +struct snprintf_arg { + char *str; + size_t remain; +}; + +extern int log_open; + +static void msglogchar(int c, int pri); +static void msglogstr(char *str, int pri, int filter_cr); +static void putchar(int ch, void *arg); +static char *ksprintn(char *nbuf, uintmax_t num, int base, int *len, int upper); +static void snprintf_func(int ch, void *arg); + +static bool msgbufmapped; /* Set when safe to use msgbuf */ +int msgbuftrigger; +struct msgbuf *msgbufp; + +#ifndef BOOT_TAG_SZ +#define BOOT_TAG_SZ 32 +#endif +#ifndef BOOT_TAG +/* Tag used to mark the start of a boot in dmesg */ +#define BOOT_TAG "---<<BOOT>>---" +#endif + +static char current_boot_tag[BOOT_TAG_SZ + 1] = BOOT_TAG; +SYSCTL_STRING(_kern, OID_AUTO, boot_tag, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, + current_boot_tag, 0, "Tag added to dmesg at start of boot"); + +static int log_console_output = 1; +SYSCTL_INT(_kern, OID_AUTO, log_console_output, CTLFLAG_RWTUN, + &log_console_output, 0, "Duplicate console output to the syslog"); + +/* + * See the comment in log_console() below for more explanation of this. + */ +static int log_console_add_linefeed; +SYSCTL_INT(_kern, OID_AUTO, log_console_add_linefeed, CTLFLAG_RWTUN, + &log_console_add_linefeed, 0, "log_console() adds extra newlines"); + +static int always_console_output; +SYSCTL_INT(_kern, OID_AUTO, always_console_output, CTLFLAG_RWTUN, + &always_console_output, 0, "Always output to console despite TIOCCONS"); + +/* + * Warn that a system table is full. + */ +void +tablefull(const char *tab) +{ + + log(LOG_ERR, "%s: table is full\n", tab); +} + +/* + * Uprintf prints to the controlling terminal for the current process. + */ +int +uprintf(const char *fmt, ...) +{ + va_list ap; + struct putchar_arg pca; + struct proc *p; + struct thread *td; + int retval; + + td = curthread; + if (TD_IS_IDLETHREAD(td)) + return (0); + + if (td->td_proc == initproc) { + /* Produce output when we fail to load /sbin/init: */ + va_start(ap, fmt); + retval = vprintf(fmt, ap); + va_end(ap); + return (retval); + } + + sx_slock(&proctree_lock); + p = td->td_proc; + PROC_LOCK(p); + if ((p->p_flag & P_CONTROLT) == 0) { + PROC_UNLOCK(p); + sx_sunlock(&proctree_lock); + return (0); + } + SESS_LOCK(p->p_session); + pca.tty = p->p_session->s_ttyp; + SESS_UNLOCK(p->p_session); + PROC_UNLOCK(p); + if (pca.tty == NULL) { + sx_sunlock(&proctree_lock); + return (0); + } + pca.flags = TOTTY; + pca.p_bufr = NULL; + va_start(ap, fmt); + tty_lock(pca.tty); + sx_sunlock(&proctree_lock); + retval = kvprintf(fmt, putchar, &pca, 10, ap); + tty_unlock(pca.tty); + va_end(ap); + return (retval); +} + +/* + * tprintf and vtprintf print on the controlling terminal associated with the + * given session, possibly to the log as well. + */ +void +tprintf(struct proc *p, int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vtprintf(p, pri, fmt, ap); + va_end(ap); +} + +void +vtprintf(struct proc *p, int pri, const char *fmt, va_list ap) +{ + struct tty *tp = NULL; + int flags = 0; + struct putchar_arg pca; + struct session *sess = NULL; + + sx_slock(&proctree_lock); + if (pri != -1) + flags |= TOLOG; + if (p != NULL) { + PROC_LOCK(p); + if (p->p_flag & P_CONTROLT && p->p_session->s_ttyvp) { + sess = p->p_session; + sess_hold(sess); + PROC_UNLOCK(p); + tp = sess->s_ttyp; + if (tp != NULL && tty_checkoutq(tp)) + flags |= TOTTY; + else + tp = NULL; + } else + PROC_UNLOCK(p); + } + pca.pri = pri; + pca.tty = tp; + pca.flags = flags; + pca.p_bufr = NULL; + if (pca.tty != NULL) + tty_lock(pca.tty); + sx_sunlock(&proctree_lock); + kvprintf(fmt, putchar, &pca, 10, ap); + if (pca.tty != NULL) + tty_unlock(pca.tty); + if (sess != NULL) + sess_release(sess); + msgbuftrigger = 1; +} + +static int +_vprintf(int level, int flags, const char *fmt, va_list ap) +{ + struct putchar_arg pca; + int retval; +#ifdef PRINTF_BUFR_SIZE + char bufr[PRINTF_BUFR_SIZE]; +#endif + + TSENTER(); + pca.tty = NULL; + pca.pri = level; + pca.flags = flags; +#ifdef PRINTF_BUFR_SIZE + pca.p_bufr = bufr; + pca.p_next = pca.p_bufr; + pca.n_bufr = sizeof(bufr); + pca.remain = sizeof(bufr); + *pca.p_next = '\0'; +#else + /* Don't buffer console output. */ + pca.p_bufr = NULL; +#endif + + retval = kvprintf(fmt, putchar, &pca, 10, ap); + +#ifdef PRINTF_BUFR_SIZE + /* Write any buffered console/log output: */ + if (*pca.p_bufr != '\0') { + if (pca.flags & TOLOG) + msglogstr(pca.p_bufr, level, /*filter_cr*/1); + + if (pca.flags & TOCONS) + cnputs(pca.p_bufr); + } +#endif + + TSEXIT(); + return (retval); +} + +/* + * Log writes to the log buffer, and guarantees not to sleep (so can be + * called by interrupt routines). If there is no process reading the + * log yet, it writes to the console also. + */ +void +log(int level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(level, fmt, ap); + va_end(ap); +} + +void +vlog(int level, const char *fmt, va_list ap) +{ + + (void)_vprintf(level, log_open ? TOLOG : TOCONS | TOLOG, fmt, ap); + msgbuftrigger = 1; +} + +#define CONSCHUNK 128 + +void +log_console(struct uio *uio) +{ + int c, error, nl; + char *consbuffer; + int pri; + + if (!log_console_output) + return; + + pri = LOG_INFO | LOG_CONSOLE; + uio = cloneuio(uio); + consbuffer = malloc(CONSCHUNK, M_TEMP, M_WAITOK); + + nl = 0; + while (uio->uio_resid > 0) { + c = imin(uio->uio_resid, CONSCHUNK - 1); + error = uiomove(consbuffer, c, uio); + if (error != 0) + break; + /* Make sure we're NUL-terminated */ + consbuffer[c] = '\0'; + if (consbuffer[c - 1] == '\n') + nl = 1; + else + nl = 0; + msglogstr(consbuffer, pri, /*filter_cr*/ 1); + } + /* + * The previous behavior in log_console() is preserved when + * log_console_add_linefeed is non-zero. For that behavior, if an + * individual console write came in that was not terminated with a + * line feed, it would add a line feed. + * + * This results in different data in the message buffer than + * appears on the system console (which doesn't add extra line feed + * characters). + * + * A number of programs and rc scripts write a line feed, or a period + * and a line feed when they have completed their operation. On + * the console, this looks seamless, but when displayed with + * 'dmesg -a', you wind up with output that looks like this: + * + * Updating motd: + * . + * + * On the console, it looks like this: + * Updating motd:. + * + * We could add logic to detect that situation, or just not insert + * the extra newlines. Set the kern.log_console_add_linefeed + * sysctl/tunable variable to get the old behavior. + */ + if (!nl && log_console_add_linefeed) { + consbuffer[0] = '\n'; + consbuffer[1] = '\0'; + msglogstr(consbuffer, pri, /*filter_cr*/ 1); + } + msgbuftrigger = 1; + free(uio, M_IOV); + free(consbuffer, M_TEMP); +} + +int +printf(const char *fmt, ...) +{ + va_list ap; + int retval; + + va_start(ap, fmt); + retval = vprintf(fmt, ap); + va_end(ap); + + return (retval); +} + +int +vprintf(const char *fmt, va_list ap) +{ + int retval; + + retval = _vprintf(-1, TOCONS | TOLOG, fmt, ap); + + if (!KERNEL_PANICKED()) + msgbuftrigger = 1; + + return (retval); +} + +static void +prf_putbuf(char *bufr, int flags, int pri) +{ + + if (flags & TOLOG) + msglogstr(bufr, pri, /*filter_cr*/1); + + if (flags & TOCONS) { + if ((!KERNEL_PANICKED()) && (constty != NULL)) + msgbuf_addstr(&consmsgbuf, -1, + bufr, /*filter_cr*/ 0); + + if ((constty == NULL) ||(always_console_output)) + cnputs(bufr); + } +} + +static void +putbuf(int c, struct putchar_arg *ap) +{ + /* Check if no console output buffer was provided. */ + if (ap->p_bufr == NULL) { + /* Output direct to the console. */ + if (ap->flags & TOCONS) + cnputc(c); + + if (ap->flags & TOLOG) + msglogchar(c, ap->pri); + } else { + /* Buffer the character: */ + *ap->p_next++ = c; + ap->remain--; + + /* Always leave the buffer zero terminated. */ + *ap->p_next = '\0'; + + /* Check if the buffer needs to be flushed. */ + if (ap->remain == 2 || c == '\n') { + prf_putbuf(ap->p_bufr, ap->flags, ap->pri); + + ap->p_next = ap->p_bufr; + ap->remain = ap->n_bufr; + *ap->p_next = '\0'; + } + + /* + * Since we fill the buffer up one character at a time, + * this should not happen. We should always catch it when + * ap->remain == 2 (if not sooner due to a newline), flush + * the buffer and move on. One way this could happen is + * if someone sets PRINTF_BUFR_SIZE to 1 or something + * similarly silly. + */ + KASSERT(ap->remain > 2, ("Bad buffer logic, remain = %zd", + ap->remain)); + } +} + +/* + * Print a character on console or users terminal. If destination is + * the console then the last bunch of characters are saved in msgbuf for + * inspection later. + */ +static void +putchar(int c, void *arg) +{ + struct putchar_arg *ap = (struct putchar_arg*) arg; + struct tty *tp = ap->tty; + int flags = ap->flags; + + /* Don't use the tty code after a panic or while in ddb. */ + if (kdb_active) { + if (c != '\0') + cnputc(c); + return; + } + + if ((flags & TOTTY) && tp != NULL && !KERNEL_PANICKED()) + tty_putchar(tp, c); + + if ((flags & (TOCONS | TOLOG)) && c != '\0') + putbuf(c, ap); +} + +/* + * Scaled down version of sprintf(3). + */ +int +sprintf(char *buf, const char *cfmt, ...) +{ + int retval; + va_list ap; + + va_start(ap, cfmt); + retval = kvprintf(cfmt, NULL, (void *)buf, 10, ap); + buf[retval] = '\0'; + va_end(ap); + return (retval); +} + +/* + * Scaled down version of vsprintf(3). + */ +int +vsprintf(char *buf, const char *cfmt, va_list ap) +{ + int retval; + + retval = kvprintf(cfmt, NULL, (void *)buf, 10, ap); + buf[retval] = '\0'; + return (retval); +} + +/* + * Scaled down version of snprintf(3). + */ +int +snprintf(char *str, size_t size, const char *format, ...) +{ + int retval; + va_list ap; + + va_start(ap, format); + retval = vsnprintf(str, size, format, ap); + va_end(ap); + return(retval); +} + +/* + * Scaled down version of vsnprintf(3). + */ +int +vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + struct snprintf_arg info; + int retval; + + info.str = str; + info.remain = size; + retval = kvprintf(format, snprintf_func, &info, 10, ap); + if (info.remain >= 1) + *info.str++ = '\0'; + return (retval); +} + +/* + * Kernel version which takes radix argument vsnprintf(3). + */ +int +vsnrprintf(char *str, size_t size, int radix, const char *format, va_list ap) +{ + struct snprintf_arg info; + int retval; + + info.str = str; + info.remain = size; + retval = kvprintf(format, snprintf_func, &info, radix, ap); + if (info.remain >= 1) + *info.str++ = '\0'; + return (retval); +} + +static void +snprintf_func(int ch, void *arg) +{ + struct snprintf_arg *const info = arg; + + if (info->remain >= 2) { + *info->str++ = ch; + info->remain--; + } +} + +/* + * Put a NUL-terminated ASCII number (base <= 36) in a buffer in reverse + * order; return an optional length and a pointer to the last character + * written in the buffer (i.e., the first character of the string). + * The buffer pointed to by `nbuf' must have length >= MAXNBUF. + */ +static char * +ksprintn(char *nbuf, uintmax_t num, int base, int *lenp, int upper) +{ + char *p, c; + + p = nbuf; + *p = '\0'; + do { + c = hex2ascii(num % base); + *++p = upper ? toupper(c) : c; + } while (num /= base); + if (lenp) + *lenp = p - nbuf; + return (p); +} + +/* + * Scaled down version of printf(3). + * + * Two additional formats: + * + * The format %b is supported to decode error registers. + * Its usage is: + * + * printf("reg=%b\n", regval, "<base><arg>*"); + * + * where <base> is the output base expressed as a control character, e.g. + * \10 gives octal; \20 gives hex. Each arg is a sequence of characters, + * the first of which gives the bit number to be inspected (origin 1), and + * the next characters (up to a control character, i.e. a character <= 32), + * give the name of the register. Thus: + * + * kvprintf("reg=%b\n", 3, "\10\2BITTWO\1BITONE"); + * + * would produce output: + * + * reg=3<BITTWO,BITONE> + * + * XXX: %D -- Hexdump, takes pointer and separator string: + * ("%6D", ptr, ":") -> XX:XX:XX:XX:XX:XX + * ("%*D", len, ptr, " " -> XX XX XX XX ... + */ +int +kvprintf(char const *fmt, void (*func)(int, void*), void *arg, int radix, va_list ap) +{ +#define PCHAR(c) {int cc=(c); if (func) (*func)(cc,arg); else *d++ = cc; retval++; } + char nbuf[MAXNBUF]; + char *d; + const char *p, *percent, *q; + u_char *up; + int ch, n; + uintmax_t num; + int base, lflag, qflag, tmp, width, ladjust, sharpflag, neg, sign, dot; + int cflag, hflag, jflag, tflag, zflag; + int bconv, dwidth, upper; + char padc; + int stop = 0, retval = 0; + + num = 0; + q = NULL; + if (!func) + d = (char *) arg; + else + d = NULL; + + if (fmt == NULL) + fmt = "(fmt null)\n"; + + if (radix < 2 || radix > 36) + radix = 10; + + for (;;) { + padc = ' '; + width = 0; + while ((ch = (u_char)*fmt++) != '%' || stop) { + if (ch == '\0') + return (retval); + PCHAR(ch); + } + percent = fmt - 1; + qflag = 0; lflag = 0; ladjust = 0; sharpflag = 0; neg = 0; + sign = 0; dot = 0; bconv = 0; dwidth = 0; upper = 0; + cflag = 0; hflag = 0; jflag = 0; tflag = 0; zflag = 0; +reswitch: switch (ch = (u_char)*fmt++) { + case '.': + dot = 1; + goto reswitch; + case '#': + sharpflag = 1; + goto reswitch; + case '+': + sign = 1; + goto reswitch; + case '-': + ladjust = 1; + goto reswitch; + case '%': + PCHAR(ch); + break; + case '*': + if (!dot) { + width = va_arg(ap, int); + if (width < 0) { + ladjust = !ladjust; + width = -width; + } + } else { + dwidth = va_arg(ap, int); + } + goto reswitch; + case '0': + if (!dot) { + padc = '0'; + goto reswitch; + } + /* FALLTHROUGH */ + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + for (n = 0;; ++fmt) { + n = n * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') + break; + } + if (dot) + dwidth = n; + else + width = n; + goto reswitch; + case 'b': + ladjust = 1; + bconv = 1; + goto handle_nosign; + case 'c': + width -= 1; + + if (!ladjust && width > 0) + while (width--) + PCHAR(padc); + PCHAR(va_arg(ap, int)); + if (ladjust && width > 0) + while (width--) + PCHAR(padc); + break; + case 'D': + up = va_arg(ap, u_char *); + p = va_arg(ap, char *); + if (!width) + width = 16; + while(width--) { + PCHAR(hex2ascii(*up >> 4)); + PCHAR(hex2ascii(*up & 0x0f)); + up++; + if (width) + for (q=p;*q;q++) + PCHAR(*q); + } + break; + case 'd': + case 'i': + base = 10; + sign = 1; + goto handle_sign; + case 'h': + if (hflag) { + hflag = 0; + cflag = 1; + } else + hflag = 1; + goto reswitch; + case 'j': + jflag = 1; + goto reswitch; + case 'l': + if (lflag) { + lflag = 0; + qflag = 1; + } else + lflag = 1; + goto reswitch; + case 'n': + /* + * We do not support %n in kernel, but consume the + * argument. + */ + if (jflag) + (void)va_arg(ap, intmax_t *); + else if (qflag) + (void)va_arg(ap, quad_t *); + else if (lflag) + (void)va_arg(ap, long *); + else if (zflag) + (void)va_arg(ap, size_t *); + else if (hflag) + (void)va_arg(ap, short *); + else if (cflag) + (void)va_arg(ap, char *); + else + (void)va_arg(ap, int *); + break; + case 'o': + base = 8; + goto handle_nosign; + case 'p': + base = 16; + sharpflag = (width == 0); + sign = 0; + num = (uintptr_t)va_arg(ap, void *); + goto number; + case 'q': + qflag = 1; + goto reswitch; + case 'r': + base = radix; + if (sign) + goto handle_sign; + goto handle_nosign; + case 's': + p = va_arg(ap, char *); + if (p == NULL) + p = "(null)"; + if (!dot) + n = strlen (p); + else + for (n = 0; n < dwidth && p[n]; n++) + continue; + + width -= n; + + if (!ladjust && width > 0) + while (width--) + PCHAR(padc); + while (n--) + PCHAR(*p++); + if (ladjust && width > 0) + while (width--) + PCHAR(padc); + break; + case 't': + tflag = 1; + goto reswitch; + case 'u': + base = 10; + goto handle_nosign; + case 'X': + upper = 1; + /* FALLTHROUGH */ + case 'x': + base = 16; + goto handle_nosign; + case 'y': + base = 16; + sign = 1; + goto handle_sign; + case 'z': + zflag = 1; + goto reswitch; +handle_nosign: + sign = 0; + if (jflag) + num = va_arg(ap, uintmax_t); + else if (qflag) + num = va_arg(ap, u_quad_t); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (lflag) + num = va_arg(ap, u_long); + else if (zflag) + num = va_arg(ap, size_t); + else if (hflag) + num = (u_short)va_arg(ap, int); + else if (cflag) + num = (u_char)va_arg(ap, int); + else + num = va_arg(ap, u_int); + if (bconv) { + q = va_arg(ap, char *); + base = *q++; + } + goto number; +handle_sign: + if (jflag) + num = va_arg(ap, intmax_t); + else if (qflag) + num = va_arg(ap, quad_t); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (lflag) + num = va_arg(ap, long); + else if (zflag) + num = va_arg(ap, ssize_t); + else if (hflag) + num = (short)va_arg(ap, int); + else if (cflag) + num = (char)va_arg(ap, int); + else + num = va_arg(ap, int); +number: + if (sign && (intmax_t)num < 0) { + neg = 1; + num = -(intmax_t)num; + } + p = ksprintn(nbuf, num, base, &n, upper); + tmp = 0; + if (sharpflag && num != 0) { + if (base == 8) + tmp++; + else if (base == 16) + tmp += 2; + } + if (neg) + tmp++; + + if (!ladjust && padc == '0') + dwidth = width - tmp; + width -= tmp + imax(dwidth, n); + dwidth -= n; + if (!ladjust) + while (width-- > 0) + PCHAR(' '); + if (neg) + PCHAR('-'); + if (sharpflag && num != 0) { + if (base == 8) { + PCHAR('0'); + } else if (base == 16) { + PCHAR('0'); + PCHAR('x'); + } + } + while (dwidth-- > 0) + PCHAR('0'); + + while (*p) + PCHAR(*p--); + + if (bconv && num != 0) { + /* %b conversion flag format. */ + tmp = retval; + while (*q) { + n = *q++; + if (num & (1 << (n - 1))) { + PCHAR(retval != tmp ? + ',' : '<'); + for (; (n = *q) > ' '; ++q) + PCHAR(n); + } else + for (; *q > ' '; ++q) + continue; + } + if (retval != tmp) { + PCHAR('>'); + width -= retval - tmp; + } + } + + if (ladjust) + while (width-- > 0) + PCHAR(' '); + + break; + default: + while (percent < fmt) + PCHAR(*percent++); + /* + * Since we ignore a formatting argument it is no + * longer safe to obey the remaining formatting + * arguments as the arguments will no longer match + * the format specs. + */ + stop = 1; + break; + } + } +#undef PCHAR +} + +/* + * Put character in log buffer with a particular priority. + */ +static void +msglogchar(int c, int pri) +{ + static int lastpri = -1; + static int dangling; + char nbuf[MAXNBUF]; + char *p; + + if (!msgbufmapped) + return; + if (c == '\0' || c == '\r') + return; + if (pri != -1 && pri != lastpri) { + if (dangling) { + msgbuf_addchar(msgbufp, '\n'); + dangling = 0; + } + msgbuf_addchar(msgbufp, '<'); + for (p = ksprintn(nbuf, (uintmax_t)pri, 10, NULL, 0); *p;) + msgbuf_addchar(msgbufp, *p--); + msgbuf_addchar(msgbufp, '>'); + lastpri = pri; + } + msgbuf_addchar(msgbufp, c); + if (c == '\n') { + dangling = 0; + lastpri = -1; + } else { + dangling = 1; + } +} + +static void +msglogstr(char *str, int pri, int filter_cr) +{ + if (!msgbufmapped) + return; + + msgbuf_addstr(msgbufp, pri, str, filter_cr); +} + +void +msgbufinit(void *ptr, int size) +{ + char *cp; + static struct msgbuf *oldp = NULL; + bool print_boot_tag; + + size -= sizeof(*msgbufp); + cp = (char *)ptr; + print_boot_tag = !msgbufmapped; + /* Attempt to fetch kern.boot_tag tunable on first mapping */ + if (!msgbufmapped) + TUNABLE_STR_FETCH("kern.boot_tag", current_boot_tag, + sizeof(current_boot_tag)); + msgbufp = (struct msgbuf *)(cp + size); + msgbuf_reinit(msgbufp, cp, size); + if (msgbufmapped && oldp != msgbufp) + msgbuf_copy(oldp, msgbufp); + msgbufmapped = true; + if (print_boot_tag && *current_boot_tag != '\0') + printf("%s\n", current_boot_tag); + oldp = msgbufp; +} + +/* Sysctls for accessing/clearing the msgbuf */ +static int +sysctl_kern_msgbuf(SYSCTL_HANDLER_ARGS) +{ + char buf[128]; + u_int seq; + int error, len; + + error = priv_check(req->td, PRIV_MSGBUF); + if (error) + return (error); + + /* Read the whole buffer, one chunk at a time. */ + mtx_lock(&msgbuf_lock); + msgbuf_peekbytes(msgbufp, NULL, 0, &seq); + for (;;) { + len = msgbuf_peekbytes(msgbufp, buf, sizeof(buf), &seq); + mtx_unlock(&msgbuf_lock); + if (len == 0) + return (SYSCTL_OUT(req, "", 1)); /* add nulterm */ + + error = sysctl_handle_opaque(oidp, buf, len, req); + if (error) + return (error); + + mtx_lock(&msgbuf_lock); + } +} + +SYSCTL_PROC(_kern, OID_AUTO, msgbuf, + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + NULL, 0, sysctl_kern_msgbuf, "A", "Contents of kernel message buffer"); + +static int msgbuf_clearflag; + +static int +sysctl_kern_msgbuf_clear(SYSCTL_HANDLER_ARGS) +{ + int error; + error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); + if (!error && req->newptr) { + mtx_lock(&msgbuf_lock); + msgbuf_clear(msgbufp); + mtx_unlock(&msgbuf_lock); + msgbuf_clearflag = 0; + } + return (error); +} + +SYSCTL_PROC(_kern, OID_AUTO, msgbuf_clear, + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE | CTLFLAG_MPSAFE, + &msgbuf_clearflag, 0, sysctl_kern_msgbuf_clear, "I", + "Clear kernel message buffer"); + +#ifdef DDB + +DB_SHOW_COMMAND(msgbuf, db_show_msgbuf) +{ + int i, j; + + if (!msgbufmapped) { + db_printf("msgbuf not mapped yet\n"); + return; + } + db_printf("msgbufp = %p\n", msgbufp); + db_printf("magic = %x, size = %d, r= %u, w = %u, ptr = %p, cksum= %u\n", + msgbufp->msg_magic, msgbufp->msg_size, msgbufp->msg_rseq, + msgbufp->msg_wseq, msgbufp->msg_ptr, msgbufp->msg_cksum); + for (i = 0; i < msgbufp->msg_size && !db_pager_quit; i++) { + j = MSGBUF_SEQ_TO_POS(msgbufp, i + msgbufp->msg_rseq); + db_printf("%c", msgbufp->msg_ptr[j]); + } + db_printf("\n"); +} + +#endif /* DDB */ + +void +hexdump(const void *ptr, int length, const char *hdr, int flags) +{ + int i, j, k; + int cols; + const unsigned char *cp; + char delim; + + if ((flags & HD_DELIM_MASK) != 0) + delim = (flags & HD_DELIM_MASK) >> 8; + else + delim = ' '; + + if ((flags & HD_COLUMN_MASK) != 0) + cols = flags & HD_COLUMN_MASK; + else + cols = 16; + + cp = ptr; + for (i = 0; i < length; i+= cols) { + if (hdr != NULL) + printf("%s", hdr); + + if ((flags & HD_OMIT_COUNT) == 0) + printf("%04x ", i); + + if ((flags & HD_OMIT_HEX) == 0) { + for (j = 0; j < cols; j++) { + k = i + j; + if (k < length) + printf("%c%02x", delim, cp[k]); + else + printf(" "); + } + } + + if ((flags & HD_OMIT_CHARS) == 0) { + printf(" |"); + for (j = 0; j < cols; j++) { + k = i + j; + if (k >= length) + printf(" "); + else if (cp[k] >= ' ' && cp[k] <= '~') + printf("%c", cp[k]); + else + printf("."); + } + printf("|"); + } + printf("\n"); + } +} +#endif /* _KERNEL */ + +void +sbuf_hexdump(struct sbuf *sb, const void *ptr, int length, const char *hdr, + int flags) +{ + int i, j, k; + int cols; + const unsigned char *cp; + char delim; + + if ((flags & HD_DELIM_MASK) != 0) + delim = (flags & HD_DELIM_MASK) >> 8; + else + delim = ' '; + + if ((flags & HD_COLUMN_MASK) != 0) + cols = flags & HD_COLUMN_MASK; + else + cols = 16; + + cp = ptr; + for (i = 0; i < length; i+= cols) { + if (hdr != NULL) + sbuf_printf(sb, "%s", hdr); + + if ((flags & HD_OMIT_COUNT) == 0) + sbuf_printf(sb, "%04x ", i); + + if ((flags & HD_OMIT_HEX) == 0) { + for (j = 0; j < cols; j++) { + k = i + j; + if (k < length) + sbuf_printf(sb, "%c%02x", delim, cp[k]); + else + sbuf_printf(sb, " "); + } + } + + if ((flags & HD_OMIT_CHARS) == 0) { + sbuf_printf(sb, " |"); + for (j = 0; j < cols; j++) { + k = i + j; + if (k >= length) + sbuf_printf(sb, " "); + else if (cp[k] >= ' ' && cp[k] <= '~') + sbuf_printf(sb, "%c", cp[k]); + else + sbuf_printf(sb, "."); + } + sbuf_printf(sb, "|"); + } + sbuf_printf(sb, "\n"); + } +} + +#ifdef _KERNEL +void +counted_warning(unsigned *counter, const char *msg) +{ + struct thread *td; + unsigned c; + + for (;;) { + c = *counter; + if (c == 0) + break; + if (atomic_cmpset_int(counter, c, c - 1)) { + td = curthread; + log(LOG_INFO, "pid %d (%s) %s%s\n", + td->td_proc->p_pid, td->td_name, msg, + c > 1 ? "" : " - not logging anymore"); + break; + } + } +} +#endif + +#ifdef _KERNEL +void +sbuf_putbuf(struct sbuf *sb) +{ + + prf_putbuf(sbuf_data(sb), TOLOG | TOCONS, -1); +} +#else +void +sbuf_putbuf(struct sbuf *sb) +{ + + printf("%s", sbuf_data(sb)); +} +#endif + +int +sbuf_printf_drain(void *arg, const char *data, int len) +{ + size_t *retvalptr; + int r; +#ifdef _KERNEL + char *dataptr; + char oldchr; + + /* + * This is allowed as an extra byte is always resvered for + * terminating NUL byte. Save and restore the byte because + * we might be flushing a record, and there may be valid + * data after the buffer. + */ + oldchr = data[len]; + dataptr = __DECONST(char *, data); + dataptr[len] = '\0'; + + prf_putbuf(dataptr, TOLOG | TOCONS, -1); + r = len; + + dataptr[len] = oldchr; + +#else /* !_KERNEL */ + + r = printf("%.*s", len, data); + if (r < 0) + return (-errno); + +#endif + + retvalptr = arg; + if (retvalptr != NULL) + *retvalptr += r; + + return (r); +} diff --git a/pw/sbuf/subr_sbuf.c b/pw/sbuf/subr_sbuf.c new file mode 100644 index 0000000..5588bae --- /dev/null +++ b/pw/sbuf/subr_sbuf.c @@ -0,0 +1,950 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2000-2008 Poul-Henning Kamp + * Copyright (c) 2000-2008 Dag-Erling Coïdan Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> + +#ifdef _KERNEL +#include <sys/ctype.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/limits.h> +#include <sys/malloc.h> +#include <sys/systm.h> +#include <sys/uio.h> +#include <machine/stdarg.h> +#else /* _KERNEL */ +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#endif /* _KERNEL */ + +#include <sys/sbuf.h> + +#ifdef _KERNEL +static MALLOC_DEFINE(M_SBUF, "sbuf", "string buffers"); +#define SBMALLOC(size, flags) malloc(size, M_SBUF, (flags) | M_ZERO) +#define SBFREE(buf) free(buf, M_SBUF) +#else /* _KERNEL */ +#define KASSERT(e, m) +#define SBMALLOC(size, flags) calloc(1, size) +#define SBFREE(buf) free(buf) +#endif /* _KERNEL */ + +/* + * Predicates + */ +#define SBUF_ISDYNAMIC(s) ((s)->s_flags & SBUF_DYNAMIC) +#define SBUF_ISDYNSTRUCT(s) ((s)->s_flags & SBUF_DYNSTRUCT) +#define SBUF_ISFINISHED(s) ((s)->s_flags & SBUF_FINISHED) +#define SBUF_ISDRAINATEOL(s) ((s)->s_flags & SBUF_DRAINATEOL) +#define SBUF_HASROOM(s) ((s)->s_len < (s)->s_size - 1) +#define SBUF_FREESPACE(s) ((s)->s_size - ((s)->s_len + 1)) +#define SBUF_CANEXTEND(s) ((s)->s_flags & SBUF_AUTOEXTEND) +#define SBUF_ISSECTION(s) ((s)->s_flags & SBUF_INSECTION) +#define SBUF_NULINCLUDED(s) ((s)->s_flags & SBUF_INCLUDENUL) +#define SBUF_ISDRAINTOEOR(s) ((s)->s_flags & SBUF_DRAINTOEOR) +#define SBUF_DODRAINTOEOR(s) (SBUF_ISSECTION(s) && SBUF_ISDRAINTOEOR(s)) +#define SBUF_MALLOCFLAG(s) \ + (((s)->s_flags & SBUF_NOWAIT) ? M_NOWAIT : M_WAITOK) + +/* + * Set / clear flags + */ +#define SBUF_SETFLAG(s, f) do { (s)->s_flags |= (f); } while (0) +#define SBUF_CLEARFLAG(s, f) do { (s)->s_flags &= ~(f); } while (0) + +#define SBUF_MINSIZE 2 /* Min is 1 byte + nulterm. */ +#define SBUF_MINEXTENDSIZE 16 /* Should be power of 2. */ + +#ifdef PAGE_SIZE +#define SBUF_MAXEXTENDSIZE PAGE_SIZE +#define SBUF_MAXEXTENDINCR PAGE_SIZE +#else +#define SBUF_MAXEXTENDSIZE 4096 +#define SBUF_MAXEXTENDINCR 4096 +#endif + +/* + * Debugging support + */ +#if defined(_KERNEL) && defined(INVARIANTS) + +static void +_assert_sbuf_integrity(const char *fun, struct sbuf *s) +{ + + KASSERT(s != NULL, + ("%s called with a NULL sbuf pointer", fun)); + KASSERT(s->s_buf != NULL, + ("%s called with uninitialized or corrupt sbuf", fun)); + if (SBUF_ISFINISHED(s) && SBUF_NULINCLUDED(s)) { + KASSERT(s->s_len <= s->s_size, + ("wrote past end of sbuf (%jd >= %jd)", + (intmax_t)s->s_len, (intmax_t)s->s_size)); + } else { + KASSERT(s->s_len < s->s_size, + ("wrote past end of sbuf (%jd >= %jd)", + (intmax_t)s->s_len, (intmax_t)s->s_size)); + } +} + +static void +_assert_sbuf_state(const char *fun, struct sbuf *s, int state) +{ + + KASSERT((s->s_flags & SBUF_FINISHED) == state, + ("%s called with %sfinished or corrupt sbuf", fun, + (state ? "un" : ""))); +} + +#define assert_sbuf_integrity(s) _assert_sbuf_integrity(__func__, (s)) +#define assert_sbuf_state(s, i) _assert_sbuf_state(__func__, (s), (i)) + +#else /* _KERNEL && INVARIANTS */ + +#define assert_sbuf_integrity(s) do { } while (0) +#define assert_sbuf_state(s, i) do { } while (0) + +#endif /* _KERNEL && INVARIANTS */ + +#ifdef CTASSERT +CTASSERT(powerof2(SBUF_MAXEXTENDSIZE)); +CTASSERT(powerof2(SBUF_MAXEXTENDINCR)); +#endif + +static int +sbuf_extendsize(int size) +{ + int newsize; + + if (size < (int)SBUF_MAXEXTENDSIZE) { + newsize = SBUF_MINEXTENDSIZE; + while (newsize < size) + newsize *= 2; + } else { + newsize = roundup(size, SBUF_MAXEXTENDINCR); + } + KASSERT(newsize >= size, ("%s: %d < %d\n", __func__, newsize, size)); + return (newsize); +} + +/* + * Extend an sbuf. + */ +static int +sbuf_extend(struct sbuf *s, int addlen) +{ + char *newbuf; + int newsize; + + if (!SBUF_CANEXTEND(s)) + return (-1); + newsize = sbuf_extendsize(s->s_size + addlen); + newbuf = SBMALLOC(newsize, SBUF_MALLOCFLAG(s)); + if (newbuf == NULL) + return (-1); + memcpy(newbuf, s->s_buf, s->s_size); + if (SBUF_ISDYNAMIC(s)) + SBFREE(s->s_buf); + else + SBUF_SETFLAG(s, SBUF_DYNAMIC); + s->s_buf = newbuf; + s->s_size = newsize; + return (0); +} + +/* + * Initialize an sbuf. + * If buf is non-NULL, it points to a static or already-allocated string + * big enough to hold at least length characters. + */ +struct sbuf * +sbuf_new(struct sbuf *s, char *buf, int length, int flags) +{ + + KASSERT(length >= 0, + ("attempt to create an sbuf of negative length (%d)", length)); + KASSERT((flags & ~SBUF_USRFLAGMSK) == 0, + ("%s called with invalid flags", __func__)); + KASSERT((flags & SBUF_AUTOEXTEND) || length >= SBUF_MINSIZE, + ("sbuf buffer %d smaller than minimum %d bytes", length, + SBUF_MINSIZE)); + + flags &= SBUF_USRFLAGMSK; + + /* + * Allocate 'DYNSTRUCT' sbuf from the heap, if NULL 's' was provided. + */ + if (s == NULL) { + s = SBMALLOC(sizeof(*s), + (flags & SBUF_NOWAIT) ? M_NOWAIT : M_WAITOK); + if (s == NULL) + goto out; + SBUF_SETFLAG(s, SBUF_DYNSTRUCT); + } else { + /* + * DYNSTRUCT SBMALLOC sbufs are allocated with M_ZERO, but + * user-provided sbuf objects must be initialized. + */ + memset(s, 0, sizeof(*s)); + } + + s->s_flags |= flags; + s->s_size = length; + s->s_buf = buf; + /* + * Never-written sbufs do not need \n termination. + */ + SBUF_SETFLAG(s, SBUF_DRAINATEOL); + + /* + * Allocate DYNAMIC, i.e., heap data buffer backing the sbuf, if no + * buffer was provided. + */ + if (s->s_buf == NULL) { + if (SBUF_CANEXTEND(s)) + s->s_size = sbuf_extendsize(s->s_size); + s->s_buf = SBMALLOC(s->s_size, SBUF_MALLOCFLAG(s)); + if (s->s_buf == NULL) + goto out; + SBUF_SETFLAG(s, SBUF_DYNAMIC); + } + +out: + if (s != NULL && s->s_buf == NULL) { + if (SBUF_ISDYNSTRUCT(s)) + SBFREE(s); + s = NULL; + } + return (s); +} + +#ifdef _KERNEL +/* + * Create an sbuf with uio data + */ +struct sbuf * +sbuf_uionew(struct sbuf *s, struct uio *uio, int *error) +{ + + KASSERT(uio != NULL, + ("%s called with NULL uio pointer", __func__)); + KASSERT(error != NULL, + ("%s called with NULL error pointer", __func__)); + + s = sbuf_new(s, NULL, uio->uio_resid + 1, 0); + if (s == NULL) { + *error = ENOMEM; + return (NULL); + } + *error = uiomove(s->s_buf, uio->uio_resid, uio); + if (*error != 0) { + sbuf_delete(s); + return (NULL); + } + s->s_len = s->s_size - 1; + if (SBUF_ISSECTION(s)) + s->s_sect_len = s->s_size - 1; + *error = 0; + return (s); +} +#endif + +int +sbuf_get_flags(struct sbuf *s) +{ + + return (s->s_flags & SBUF_USRFLAGMSK); +} + +void +sbuf_clear_flags(struct sbuf *s, int flags) +{ + + s->s_flags &= ~(flags & SBUF_USRFLAGMSK); +} + +void +sbuf_set_flags(struct sbuf *s, int flags) +{ + + s->s_flags |= (flags & SBUF_USRFLAGMSK); +} + +/* + * Clear an sbuf and reset its position. + */ +void +sbuf_clear(struct sbuf *s) +{ + + assert_sbuf_integrity(s); + /* don't care if it's finished or not */ + KASSERT(s->s_drain_func == NULL, + ("%s makes no sense on sbuf %p with drain", __func__, s)); + + SBUF_CLEARFLAG(s, SBUF_FINISHED); + s->s_error = 0; + s->s_len = 0; + s->s_rec_off = 0; + s->s_sect_len = 0; +} + +/* + * Set the sbuf's end position to an arbitrary value. + * Effectively truncates the sbuf at the new position. + */ +int +sbuf_setpos(struct sbuf *s, ssize_t pos) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + KASSERT(pos >= 0, + ("attempt to seek to a negative position (%jd)", (intmax_t)pos)); + KASSERT(pos < s->s_size, + ("attempt to seek past end of sbuf (%jd >= %jd)", + (intmax_t)pos, (intmax_t)s->s_size)); + KASSERT(!SBUF_ISSECTION(s), + ("attempt to seek when in a section")); + + if (pos < 0 || pos > s->s_len) + return (-1); + s->s_len = pos; + return (0); +} + +/* + * Drain into a counter. Counts amount of data without producing output. + * Useful for cases like sysctl, where user may first request only size. + * This allows to avoid pointless allocation/freeing of large buffers. + */ +int +sbuf_count_drain(void *arg, const char *data __unused, int len) +{ + size_t *sizep; + + sizep = (size_t *)arg; + *sizep += len; + return (len); +} + +/* + * Set up a drain function and argument on an sbuf to flush data to + * when the sbuf buffer overflows. + */ +void +sbuf_set_drain(struct sbuf *s, sbuf_drain_func *func, void *ctx) +{ + + assert_sbuf_state(s, 0); + assert_sbuf_integrity(s); + KASSERT(func == s->s_drain_func || s->s_len == 0, + ("Cannot change drain to %p on non-empty sbuf %p", func, s)); + s->s_drain_func = func; + s->s_drain_arg = ctx; +} + +/* + * Call the drain and process the return. + */ +static int +sbuf_drain(struct sbuf *s) +{ + int len; + + KASSERT(s->s_len > 0, ("Shouldn't drain empty sbuf %p", s)); + KASSERT(s->s_error == 0, ("Called %s with error on %p", __func__, s)); + + if (SBUF_DODRAINTOEOR(s) && s->s_rec_off == 0) + return (s->s_error = EDEADLK); + len = s->s_drain_func(s->s_drain_arg, s->s_buf, + SBUF_DODRAINTOEOR(s) ? s->s_rec_off : s->s_len); + if (len <= 0) { + s->s_error = len ? -len : EDEADLK; + return (s->s_error); + } + KASSERT(len > 0 && len <= s->s_len, + ("Bad drain amount %d for sbuf %p", len, s)); + s->s_len -= len; + s->s_rec_off -= len; + /* + * Fast path for the expected case where all the data was + * drained. + */ + if (s->s_len == 0) { + /* + * When the s_buf is entirely drained, we need to remember if + * the last character was a '\n' or not for + * sbuf_nl_terminate(). + */ + if (s->s_buf[len - 1] == '\n') + SBUF_SETFLAG(s, SBUF_DRAINATEOL); + else + SBUF_CLEARFLAG(s, SBUF_DRAINATEOL); + return (0); + } + /* + * Move the remaining characters to the beginning of the + * string. + */ + memmove(s->s_buf, s->s_buf + len, s->s_len); + return (0); +} + +/* + * Append bytes to an sbuf. This is the core function for appending + * to an sbuf and is the main place that deals with extending the + * buffer and marking overflow. + */ +static void +sbuf_put_bytes(struct sbuf *s, const char *buf, size_t len) +{ + size_t n; + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + if (s->s_error != 0) + return; + while (len > 0) { + if (SBUF_FREESPACE(s) <= 0) { + /* + * If there is a drain, use it, otherwise extend the + * buffer. + */ + if (s->s_drain_func != NULL) + (void)sbuf_drain(s); + else if (sbuf_extend(s, len > INT_MAX ? INT_MAX : len) + < 0) + s->s_error = ENOMEM; + if (s->s_error != 0) + return; + } + n = SBUF_FREESPACE(s); + if (len < n) + n = len; + memcpy(&s->s_buf[s->s_len], buf, n); + s->s_len += n; + if (SBUF_ISSECTION(s)) + s->s_sect_len += n; + len -= n; + buf += n; + } +} + +static void +sbuf_put_byte(struct sbuf *s, char c) +{ + + sbuf_put_bytes(s, &c, 1); +} + +/* + * Append a byte string to an sbuf. + */ +int +sbuf_bcat(struct sbuf *s, const void *buf, size_t len) +{ + + sbuf_put_bytes(s, buf, len); + if (s->s_error != 0) + return (-1); + return (0); +} + +#ifdef _KERNEL +/* + * Copy a byte string from userland into an sbuf. + */ +int +sbuf_bcopyin(struct sbuf *s, const void *uaddr, size_t len) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + KASSERT(s->s_drain_func == NULL, + ("Nonsensical copyin to sbuf %p with a drain", s)); + + if (s->s_error != 0) + return (-1); + if (len == 0) + return (0); + if (len > SBUF_FREESPACE(s)) { + sbuf_extend(s, len - SBUF_FREESPACE(s)); + if (SBUF_FREESPACE(s) < len) + len = SBUF_FREESPACE(s); + } + if (copyin(uaddr, s->s_buf + s->s_len, len) != 0) + return (-1); + s->s_len += len; + + return (0); +} +#endif + +/* + * Copy a byte string into an sbuf. + */ +int +sbuf_bcpy(struct sbuf *s, const void *buf, size_t len) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + sbuf_clear(s); + return (sbuf_bcat(s, buf, len)); +} + +/* + * Append a string to an sbuf. + */ +int +sbuf_cat(struct sbuf *s, const char *str) +{ + size_t n; + + n = strlen(str); + sbuf_put_bytes(s, str, n); + if (s->s_error != 0) + return (-1); + return (0); +} + +#ifdef _KERNEL +/* + * Append a string from userland to an sbuf. + */ +int +sbuf_copyin(struct sbuf *s, const void *uaddr, size_t len) +{ + size_t done; + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + KASSERT(s->s_drain_func == NULL, + ("Nonsensical copyin to sbuf %p with a drain", s)); + + if (s->s_error != 0) + return (-1); + + if (len == 0) + len = SBUF_FREESPACE(s); /* XXX return 0? */ + if (len > SBUF_FREESPACE(s)) { + sbuf_extend(s, len); + if (SBUF_FREESPACE(s) < len) + len = SBUF_FREESPACE(s); + } + switch (copyinstr(uaddr, s->s_buf + s->s_len, len + 1, &done)) { + case ENAMETOOLONG: + s->s_error = ENOMEM; + /* fall through */ + case 0: + s->s_len += done - 1; + if (SBUF_ISSECTION(s)) + s->s_sect_len += done - 1; + break; + default: + return (-1); /* XXX */ + } + + return (done); +} +#endif + +/* + * Copy a string into an sbuf. + */ +int +sbuf_cpy(struct sbuf *s, const char *str) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + sbuf_clear(s); + return (sbuf_cat(s, str)); +} + +/* + * Format the given argument list and append the resulting string to an sbuf. + */ +#ifdef _KERNEL + +/* + * Append a non-NUL character to an sbuf. This prototype signature is + * suitable for use with kvprintf(9). + */ +static void +sbuf_putc_func(int c, void *arg) +{ + + if (c != '\0') + sbuf_put_byte(arg, c); +} + +int +sbuf_vprintf(struct sbuf *s, const char *fmt, va_list ap) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + KASSERT(fmt != NULL, + ("%s called with a NULL format string", __func__)); + + (void)kvprintf(fmt, sbuf_putc_func, s, 10, ap); + if (s->s_error != 0) + return (-1); + return (0); +} +#else /* !_KERNEL */ +int +sbuf_vprintf(struct sbuf *s, const char *fmt, va_list ap) +{ + va_list ap_copy; + int error, len; + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + KASSERT(fmt != NULL, + ("%s called with a NULL format string", __func__)); + + if (s->s_error != 0) + return (-1); + + /* + * For the moment, there is no way to get vsnprintf(3) to hand + * back a character at a time, to push everything into + * sbuf_putc_func() as was done for the kernel. + * + * In userspace, while drains are useful, there's generally + * not a problem attempting to malloc(3) on out of space. So + * expand a userland sbuf if there is not enough room for the + * data produced by sbuf_[v]printf(3). + */ + + error = 0; + do { + va_copy(ap_copy, ap); + len = vsnprintf(&s->s_buf[s->s_len], SBUF_FREESPACE(s) + 1, + fmt, ap_copy); + if (len < 0) { + s->s_error = errno; + return (-1); + } + va_end(ap_copy); + + if (SBUF_FREESPACE(s) >= len) + break; + /* Cannot print with the current available space. */ + if (s->s_drain_func != NULL && s->s_len > 0) + error = sbuf_drain(s); /* sbuf_drain() sets s_error. */ + else if (sbuf_extend(s, len - SBUF_FREESPACE(s)) != 0) + s->s_error = error = ENOMEM; + } while (error == 0); + + /* + * s->s_len is the length of the string, without the terminating nul. + * When updating s->s_len, we must subtract 1 from the length that + * we passed into vsnprintf() because that length includes the + * terminating nul. + * + * vsnprintf() returns the amount that would have been copied, + * given sufficient space, so don't over-increment s_len. + */ + if (SBUF_FREESPACE(s) < len) + len = SBUF_FREESPACE(s); + s->s_len += len; + if (SBUF_ISSECTION(s)) + s->s_sect_len += len; + + KASSERT(s->s_len < s->s_size, + ("wrote past end of sbuf (%d >= %d)", s->s_len, s->s_size)); + + if (s->s_error != 0) + return (-1); + return (0); +} +#endif /* _KERNEL */ + +/* + * Format the given arguments and append the resulting string to an sbuf. + */ +int +sbuf_printf(struct sbuf *s, const char *fmt, ...) +{ + va_list ap; + int result; + + va_start(ap, fmt); + result = sbuf_vprintf(s, fmt, ap); + va_end(ap); + return (result); +} + +/* + * Append a character to an sbuf. + */ +int +sbuf_putc(struct sbuf *s, int c) +{ + + sbuf_put_byte(s, c); + if (s->s_error != 0) + return (-1); + return (0); +} + +/* + * Append a trailing newline to a non-empty sbuf, if one is not already + * present. Handles sbufs with drain functions correctly. + */ +int +sbuf_nl_terminate(struct sbuf *s) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + /* + * If the s_buf isn't empty, the last byte is simply s_buf[s_len - 1]. + * + * If the s_buf is empty because a drain function drained it, we + * remember if the last byte was a \n with the SBUF_DRAINATEOL flag in + * sbuf_drain(). + * + * In either case, we only append a \n if the previous character was + * something else. + */ + if (s->s_len == 0) { + if (!SBUF_ISDRAINATEOL(s)) + sbuf_put_byte(s, '\n'); + } else if (s->s_buf[s->s_len - 1] != '\n') + sbuf_put_byte(s, '\n'); + + if (s->s_error != 0) + return (-1); + return (0); +} + +/* + * Trim whitespace characters from end of an sbuf. + */ +int +sbuf_trim(struct sbuf *s) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + KASSERT(s->s_drain_func == NULL, + ("%s makes no sense on sbuf %p with drain", __func__, s)); + + if (s->s_error != 0) + return (-1); + + while (s->s_len > 0 && isspace(s->s_buf[s->s_len-1])) { + --s->s_len; + if (SBUF_ISSECTION(s)) + s->s_sect_len--; + } + + return (0); +} + +/* + * Check if an sbuf has an error. + */ +int +sbuf_error(const struct sbuf *s) +{ + + return (s->s_error); +} + +/* + * Finish off an sbuf. + */ +int +sbuf_finish(struct sbuf *s) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + s->s_buf[s->s_len] = '\0'; + if (SBUF_NULINCLUDED(s)) + s->s_len++; + if (s->s_drain_func != NULL) { + while (s->s_len > 0 && s->s_error == 0) + s->s_error = sbuf_drain(s); + } + SBUF_SETFLAG(s, SBUF_FINISHED); +#ifdef _KERNEL + return (s->s_error); +#else + if (s->s_error != 0) { + errno = s->s_error; + return (-1); + } + return (0); +#endif +} + +/* + * Return a pointer to the sbuf data. + */ +char * +sbuf_data(struct sbuf *s) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, SBUF_FINISHED); + KASSERT(s->s_drain_func == NULL, + ("%s makes no sense on sbuf %p with drain", __func__, s)); + + return (s->s_buf); +} + +/* + * Return the length of the sbuf data. + */ +ssize_t +sbuf_len(struct sbuf *s) +{ + + assert_sbuf_integrity(s); + /* don't care if it's finished or not */ + KASSERT(s->s_drain_func == NULL, + ("%s makes no sense on sbuf %p with drain", __func__, s)); + + if (s->s_error != 0) + return (-1); + + /* If finished, nulterm is already in len, else add one. */ + if (SBUF_NULINCLUDED(s) && !SBUF_ISFINISHED(s)) + return (s->s_len + 1); + return (s->s_len); +} + +/* + * Clear an sbuf, free its buffer if necessary. + */ +void +sbuf_delete(struct sbuf *s) +{ + int isdyn; + + assert_sbuf_integrity(s); + /* don't care if it's finished or not */ + + if (SBUF_ISDYNAMIC(s)) + SBFREE(s->s_buf); + isdyn = SBUF_ISDYNSTRUCT(s); + memset(s, 0, sizeof(*s)); + if (isdyn) + SBFREE(s); +} + +/* + * Check if an sbuf has been finished. + */ +int +sbuf_done(const struct sbuf *s) +{ + + return (SBUF_ISFINISHED(s)); +} + +/* + * Start a section. + */ +void +sbuf_start_section(struct sbuf *s, ssize_t *old_lenp) +{ + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + + if (!SBUF_ISSECTION(s)) { + KASSERT(s->s_sect_len == 0, + ("s_sect_len != 0 when starting a section")); + if (old_lenp != NULL) + *old_lenp = -1; + s->s_rec_off = s->s_len; + SBUF_SETFLAG(s, SBUF_INSECTION); + } else { + KASSERT(old_lenp != NULL, + ("s_sect_len should be saved when starting a subsection")); + *old_lenp = s->s_sect_len; + s->s_sect_len = 0; + } +} + +/* + * End the section padding to the specified length with the specified + * character. + */ +ssize_t +sbuf_end_section(struct sbuf *s, ssize_t old_len, size_t pad, int c) +{ + ssize_t len; + + assert_sbuf_integrity(s); + assert_sbuf_state(s, 0); + KASSERT(SBUF_ISSECTION(s), + ("attempt to end a section when not in a section")); + + if (pad > 1) { + len = roundup(s->s_sect_len, pad) - s->s_sect_len; + for (; s->s_error == 0 && len > 0; len--) + sbuf_put_byte(s, c); + } + len = s->s_sect_len; + if (old_len == -1) { + s->s_rec_off = s->s_sect_len = 0; + SBUF_CLEARFLAG(s, SBUF_INSECTION); + } else { + s->s_sect_len += old_len; + } + if (s->s_error != 0) + return (-1); + return (len); +} diff --git a/pw/sbuf/sys/sbuf.h b/pw/sbuf/sys/sbuf.h new file mode 100644 index 0000000..f2cd679 --- /dev/null +++ b/pw/sbuf/sys/sbuf.h @@ -0,0 +1,123 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2000-2008 Poul-Henning Kamp + * Copyright (c) 2000-2008 Dag-Erling Coïdan Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _SYS_SBUF_H_ +#define _SYS_SBUF_H_ + +#include <sys/_types.h> + +typedef __builtin_va_list __va_list; + +struct sbuf; +typedef int (sbuf_drain_func)(void *, const char *, int); + +/* + * Structure definition + */ +struct sbuf { + char *s_buf; /* storage buffer */ + sbuf_drain_func *s_drain_func; /* drain function */ + void *s_drain_arg; /* user-supplied drain argument */ + int s_error; /* current error code */ + ssize_t s_size; /* size of storage buffer */ + ssize_t s_len; /* current length of string */ +#define SBUF_FIXEDLEN 0x00000000 /* fixed length buffer (default) */ +#define SBUF_AUTOEXTEND 0x00000001 /* automatically extend buffer */ +#define SBUF_INCLUDENUL 0x00000002 /* nulterm byte is counted in len */ +#define SBUF_DRAINTOEOR 0x00000004 /* use section 0 as drain EOR marker */ +#define SBUF_NOWAIT 0x00000008 /* Extend with non-blocking malloc */ +#define SBUF_USRFLAGMSK 0x0000ffff /* mask of flags the user may specify */ +#define SBUF_DYNAMIC 0x00010000 /* s_buf must be freed */ +#define SBUF_FINISHED 0x00020000 /* set by sbuf_finish() */ +#define SBUF_DYNSTRUCT 0x00080000 /* sbuf must be freed */ +#define SBUF_INSECTION 0x00100000 /* set by sbuf_start_section() */ +#define SBUF_DRAINATEOL 0x00200000 /* drained contents ended in \n */ + int s_flags; /* flags */ + ssize_t s_sect_len; /* current length of section */ + ssize_t s_rec_off; /* current record start offset */ +}; + +#ifndef HD_COLUMN_MASK +#define HD_COLUMN_MASK 0xff +#define HD_DELIM_MASK 0xff00 +#define HD_OMIT_COUNT (1 << 16) +#define HD_OMIT_HEX (1 << 17) +#define HD_OMIT_CHARS (1 << 18) +#endif /* HD_COLUMN_MASK */ + +__BEGIN_DECLS +/* + * API functions + */ +struct sbuf *sbuf_new(struct sbuf *, char *, int, int); +#define sbuf_new_auto() \ + sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND) +int sbuf_get_flags(struct sbuf *); +void sbuf_clear_flags(struct sbuf *, int); +void sbuf_set_flags(struct sbuf *, int); +void sbuf_clear(struct sbuf *); +int sbuf_setpos(struct sbuf *, ssize_t); +int sbuf_bcat(struct sbuf *, const void *, size_t); +int sbuf_bcpy(struct sbuf *, const void *, size_t); +int sbuf_cat(struct sbuf *, const char *); +int sbuf_cpy(struct sbuf *, const char *); +int sbuf_printf(struct sbuf *, const char *, ...) + __printflike(2, 3); +int sbuf_vprintf(struct sbuf *, const char *, __va_list) + __printflike(2, 0); +int sbuf_nl_terminate(struct sbuf *); +int sbuf_putc(struct sbuf *, int); +void sbuf_set_drain(struct sbuf *, sbuf_drain_func *, void *); +int sbuf_trim(struct sbuf *); +int sbuf_error(const struct sbuf *); +int sbuf_finish(struct sbuf *); +char *sbuf_data(struct sbuf *); +ssize_t sbuf_len(struct sbuf *); +int sbuf_done(const struct sbuf *); +void sbuf_delete(struct sbuf *); +void sbuf_start_section(struct sbuf *, ssize_t *); +ssize_t sbuf_end_section(struct sbuf *, ssize_t, size_t, int); +void sbuf_hexdump(struct sbuf *, const void *, int, const char *, + int); +int sbuf_count_drain(void *arg, const char *data, int len); +int sbuf_printf_drain(void *arg, const char *data, int len); +void sbuf_putbuf(struct sbuf *); + +#ifdef _KERNEL +struct uio; +struct sbuf *sbuf_uionew(struct sbuf *, struct uio *, int *); +int sbuf_bcopyin(struct sbuf *, const void *, size_t); +int sbuf_copyin(struct sbuf *, const void *, size_t); +#endif +__END_DECLS + +#endif |