/*1* Copyright 2016 Chris Torek <[email protected]>2* All rights reserved3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted providing that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR14* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED15* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE16* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY17* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL18* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,21* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING22* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE23* POSSIBILITY OF SUCH DAMAGE.24*25*/2627#include <errno.h>28#include <stdlib.h>29#include <string.h>30#include <unistd.h>3132#if defined(WITH_CASPER)33#include <libcasper.h>34#include <casper/cap_pwd.h>35#include <casper/cap_grp.h>36#endif3738#include "rfuncs.h"3940/*41* This is essentially a clone of the BSD basename_r function,42* which is like POSIX basename() but puts the result in a user43* supplied buffer.44*45* In BSD basename_r, the buffer must be least MAXPATHLEN bytes46* long. In our case we take the size of the buffer as an argument.47*48* Note that it's impossible in general to do this without49* a temporary buffer since basename("foo/bar") is "bar",50* but basename("foo/bar/") is still "bar" -- no trailing51* slash is allowed.52*53* The return value is your supplied buffer <buf>, or NULL if54* the length of the basename of the supplied <path> equals or55* exceeds your indicated <bufsize>.56*57* As a special but useful case, if you supply NULL for the <buf>58* argument, we allocate the buffer dynamically to match the59* basename, i.e., the result is basically strdup()ed for you.60* In this case <bufsize> is ignored (recommended: pass 0 here).61*/62char *63r_basename(const char *path, char *buf, size_t bufsize)64{65const char *endp, *comp;66size_t len;6768/*69* NULL or empty path means ".". This is perhaps overly70* forgiving but matches libc basename_r(), and avoids71* breaking the code below.72*/73if (path == NULL || *path == '\0') {74comp = ".";75len = 1;76} else {77/*78* Back up over any trailing slashes. If we reach79* the top of the path and it's still a trailing80* slash, it's also a leading slash and the entire81* path is just "/" (or "//", or "///", etc).82*/83endp = path + strlen(path) - 1;84while (*endp == '/' && endp > path)85endp--;86/* Invariant: *endp != '/' || endp == path */87if (*endp == '/') {88/* then endp==path and hence entire path is "/" */89comp = "/";90len = 1;91} else {92/*93* We handled empty strings earlier, and94* we just proved *endp != '/'. Hence95* we have a non-empty basename, ending96* at endp.97*98* Back up one path name component. The99* part between these two is the basename.100*101* Note that we only stop backing up when102* either comp==path, or comp[-1] is '/'.103*104* Suppose path[0] is '/'. Then, since *endp105* is *not* '/', we had comp>path initially, and106* stopped backing up because we found a '/'107* (perhaps path[0], perhaps a later '/').108*109* Or, suppose path[0] is NOT '/'. Then,110* either there are no '/'s at all and111* comp==path, or comp[-1] is '/'.112*113* In all cases, we want all bytes from *comp114* to *endp, inclusive.115*/116comp = endp;117while (comp > path && comp[-1] != '/')118comp--;119len = (size_t)(endp - comp + 1);120}121}122if (buf == NULL) {123buf = malloc(len + 1);124if (buf == NULL)125return (NULL);126} else {127if (len >= bufsize) {128errno = ENAMETOOLONG;129return (NULL);130}131}132memcpy(buf, comp, len);133buf[len] = '\0';134return (buf);135}136137/*138* This is much like POSIX dirname(), but is reentrant.139*140* We examine a path, find the directory portion, and copy that141* to a user supplied buffer <buf> of the given size <bufsize>.142*143* Note that dirname("/foo/bar/") is "/foo", dirname("/foo") is "/",144* and dirname("////") is "/". However, dirname("////foo/bar") is145* "////foo" (we do not resolve these leading slashes away -- this146* matches the BSD libc behavior).147*148* The return value is your supplied buffer <buf>, or NULL if149* the length of the dirname of the supplied <path> equals or150* exceeds your indicated <bufsize>.151*152* As a special but useful case, if you supply NULL for the <buf>153* argument, we allocate the buffer dynamically to match the154* dirname, i.e., the result is basically strdup()ed for you.155* In this case <bufsize> is ignored (recommended: pass 0 here).156*/157char *158r_dirname(const char *path, char *buf, size_t bufsize)159{160const char *endp, *dirpart;161size_t len;162163/*164* NULL or empty path means ".". This is perhaps overly165* forgiving but matches libc dirname(), and avoids breaking166* the code below.167*/168if (path == NULL || *path == '\0') {169dirpart = ".";170len = 1;171} else {172/*173* Back up over any trailing slashes, then back up174* one path name, then back up over more slashes.175* In all cases, stop as soon as endp==path so176* that we do not back out of the buffer entirely.177*178* The first loop takes care of trailing slashes179* in names like "/foo/bar//" (where the dirname180* part is to be "/foo"), the second strips out181* the non-dir-name part, and the third leaves us182* pointing to the end of the directory component.183*184* If the entire name is of the form "/foo" or185* "//foo" (or "/foo/", etc, but we already186* handled trailing slashes), we end up pointing187* to the leading "/", which is what we want; but188* if it is of the form "foo" (or "foo/", etc) we189* point to a non-slash. So, if (and only if)190* endp==path AND *endp is not '/', the dirname is191* ".", but in all cases, the LENGTH of the192* dirname is (endp-path+1).193*/194endp = path + strlen(path) - 1;195while (endp > path && *endp == '/')196endp--;197while (endp > path && *endp != '/')198endp--;199while (endp > path && *endp == '/')200endp--;201202len = (size_t)(endp - path + 1);203if (endp == path && *endp != '/')204dirpart = ".";205else206dirpart = path;207}208if (buf == NULL) {209buf = malloc(len + 1);210if (buf == NULL)211return (NULL);212} else {213if (len >= bufsize) {214errno = ENAMETOOLONG;215return (NULL);216}217}218memcpy(buf, dirpart, len);219buf[len] = '\0';220return (buf);221}222223static void224r_pginit(struct r_pgdata *pg)225{226227/* Note: init to half size since the first thing we do is double it */228pg->r_pgbufsize = 1 << 9;229pg->r_pgbuf = NULL; /* note that realloc(NULL) == malloc */230}231232static int233r_pgexpand(struct r_pgdata *pg)234{235size_t nsize;236237nsize = pg->r_pgbufsize << 1;238if (nsize >= (1 << 20) ||239(pg->r_pgbuf = realloc(pg->r_pgbuf, nsize)) == NULL)240return (ENOMEM);241return (0);242}243244void245r_pgfree(struct r_pgdata *pg)246{247248free(pg->r_pgbuf);249}250251struct passwd *252r_getpwuid(uid_t uid, struct r_pgdata *pg)253{254struct passwd *result = NULL;255int error;256257r_pginit(pg);258do {259error = r_pgexpand(pg);260if (error == 0)261error = getpwuid_r(uid, &pg->r_pgun.un_pw,262pg->r_pgbuf, pg->r_pgbufsize, &result);263} while (error == ERANGE);264265return (error ? NULL : result);266}267268struct group *269r_getgrgid(gid_t gid, struct r_pgdata *pg)270{271struct group *result = NULL;272int error;273274r_pginit(pg);275do {276error = r_pgexpand(pg);277if (error == 0)278error = getgrgid_r(gid, &pg->r_pgun.un_gr,279pg->r_pgbuf, pg->r_pgbufsize, &result);280} while (error == ERANGE);281282return (error ? NULL : result);283}284285#if defined(WITH_CASPER)286struct passwd *287r_cap_getpwuid(cap_channel_t *cap, uid_t uid, struct r_pgdata *pg)288{289struct passwd *result = NULL;290int error;291292r_pginit(pg);293do {294error = r_pgexpand(pg);295if (error == 0)296error = cap_getpwuid_r(cap, uid, &pg->r_pgun.un_pw,297pg->r_pgbuf, pg->r_pgbufsize, &result);298} while (error == ERANGE);299300return (error ? NULL : result);301}302303struct group *304r_cap_getgrgid(cap_channel_t *cap, gid_t gid, struct r_pgdata *pg)305{306struct group *result = NULL;307int error;308309r_pginit(pg);310do {311error = r_pgexpand(pg);312if (error == 0)313error = cap_getgrgid_r(cap, gid, &pg->r_pgun.un_gr,314pg->r_pgbuf, pg->r_pgbufsize, &result);315} while (error == ERANGE);316317return (error ? NULL : result);318}319#endif320321322