Path: blob/main/lib/libcasper/services/cap_pwd/cap_pwd.c
48261 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2013 The FreeBSD Foundation4*5* This software was developed by Pawel Jakub Dawidek under sponsorship from6* the FreeBSD Foundation.7*8* Redistribution and use in source and binary forms, with or without9* modification, are permitted provided that the following conditions10* are met:11* 1. Redistributions of source code must retain the above copyright12* notice, this list of conditions and the following disclaimer.13* 2. Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in the15* documentation and/or other materials provided with the distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND18* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE21* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL22* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS23* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)24* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT25* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY26* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF27* SUCH DAMAGE.28*/2930#include <sys/types.h>31#include <sys/nv.h>3233#include <assert.h>34#include <errno.h>35#include <pwd.h>36#include <stdlib.h>37#include <string.h>38#include <unistd.h>3940#include <libcasper.h>41#include <libcasper_service.h>4243#include "cap_pwd.h"4445static struct passwd gpwd;46static char *gbuffer;47static size_t gbufsize;4849static int50passwd_resize(void)51{52char *buf;5354if (gbufsize == 0)55gbufsize = 1024;56else57gbufsize *= 2;5859buf = gbuffer;60gbuffer = realloc(buf, gbufsize);61if (gbuffer == NULL) {62free(buf);63gbufsize = 0;64return (ENOMEM);65}66memset(gbuffer, 0, gbufsize);6768return (0);69}7071static int72passwd_unpack_string(const nvlist_t *nvl, const char *fieldname, char **fieldp,73char **bufferp, size_t *bufsizep)74{75const char *str;76size_t len;7778str = nvlist_get_string(nvl, fieldname);79len = strlcpy(*bufferp, str, *bufsizep);80if (len >= *bufsizep)81return (ERANGE);82*fieldp = *bufferp;83*bufferp += len + 1;84*bufsizep -= len + 1;8586return (0);87}8889static int90passwd_unpack(const nvlist_t *nvl, struct passwd *pwd, char *buffer,91size_t bufsize)92{93int error;9495if (!nvlist_exists_string(nvl, "pw_name"))96return (EINVAL);9798explicit_bzero(pwd, sizeof(*pwd));99100error = passwd_unpack_string(nvl, "pw_name", &pwd->pw_name, &buffer,101&bufsize);102if (error != 0)103return (error);104pwd->pw_uid = (uid_t)nvlist_get_number(nvl, "pw_uid");105pwd->pw_gid = (gid_t)nvlist_get_number(nvl, "pw_gid");106pwd->pw_change = (time_t)nvlist_get_number(nvl, "pw_change");107error = passwd_unpack_string(nvl, "pw_passwd", &pwd->pw_passwd, &buffer,108&bufsize);109if (error != 0)110return (error);111error = passwd_unpack_string(nvl, "pw_class", &pwd->pw_class, &buffer,112&bufsize);113if (error != 0)114return (error);115error = passwd_unpack_string(nvl, "pw_gecos", &pwd->pw_gecos, &buffer,116&bufsize);117if (error != 0)118return (error);119error = passwd_unpack_string(nvl, "pw_dir", &pwd->pw_dir, &buffer,120&bufsize);121if (error != 0)122return (error);123error = passwd_unpack_string(nvl, "pw_shell", &pwd->pw_shell, &buffer,124&bufsize);125if (error != 0)126return (error);127pwd->pw_expire = (time_t)nvlist_get_number(nvl, "pw_expire");128pwd->pw_fields = (int)nvlist_get_number(nvl, "pw_fields");129130return (0);131}132133static int134cap_getpwcommon_r(cap_channel_t *chan, const char *cmd, const char *login,135uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize,136struct passwd **result)137{138nvlist_t *nvl;139bool getpw_r;140int error;141142nvl = nvlist_create(0);143nvlist_add_string(nvl, "cmd", cmd);144if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0) {145/* Add nothing. */146} else if (strcmp(cmd, "getpwnam") == 0 ||147strcmp(cmd, "getpwnam_r") == 0) {148nvlist_add_string(nvl, "name", login);149} else if (strcmp(cmd, "getpwuid") == 0 ||150strcmp(cmd, "getpwuid_r") == 0) {151nvlist_add_number(nvl, "uid", (uint64_t)uid);152} else {153abort();154}155nvl = cap_xfer_nvlist(chan, nvl);156if (nvl == NULL) {157assert(errno != 0);158*result = NULL;159return (errno);160}161error = (int)nvlist_get_number(nvl, "error");162if (error != 0) {163nvlist_destroy(nvl);164*result = NULL;165return (error);166}167168if (!nvlist_exists_string(nvl, "pw_name")) {169/* Not found. */170nvlist_destroy(nvl);171*result = NULL;172return (0);173}174175getpw_r = (strcmp(cmd, "getpwent_r") == 0 ||176strcmp(cmd, "getpwnam_r") == 0 || strcmp(cmd, "getpwuid_r") == 0);177178for (;;) {179error = passwd_unpack(nvl, pwd, buffer, bufsize);180if (getpw_r || error != ERANGE)181break;182assert(buffer == gbuffer);183assert(bufsize == gbufsize);184error = passwd_resize();185if (error != 0)186break;187/* Update pointers after resize. */188buffer = gbuffer;189bufsize = gbufsize;190}191192nvlist_destroy(nvl);193194if (error == 0)195*result = pwd;196else197*result = NULL;198199return (error);200}201202static struct passwd *203cap_getpwcommon(cap_channel_t *chan, const char *cmd, const char *login,204uid_t uid)205{206struct passwd *result;207int error, serrno;208209serrno = errno;210211error = cap_getpwcommon_r(chan, cmd, login, uid, &gpwd, gbuffer,212gbufsize, &result);213if (error != 0) {214errno = error;215return (NULL);216}217218errno = serrno;219220return (result);221}222223struct passwd *224cap_getpwent(cap_channel_t *chan)225{226227return (cap_getpwcommon(chan, "getpwent", NULL, 0));228}229230struct passwd *231cap_getpwnam(cap_channel_t *chan, const char *login)232{233234return (cap_getpwcommon(chan, "getpwnam", login, 0));235}236237struct passwd *238cap_getpwuid(cap_channel_t *chan, uid_t uid)239{240241return (cap_getpwcommon(chan, "getpwuid", NULL, uid));242}243244int245cap_getpwent_r(cap_channel_t *chan, struct passwd *pwd, char *buffer,246size_t bufsize, struct passwd **result)247{248249return (cap_getpwcommon_r(chan, "getpwent_r", NULL, 0, pwd, buffer,250bufsize, result));251}252253int254cap_getpwnam_r(cap_channel_t *chan, const char *name, struct passwd *pwd,255char *buffer, size_t bufsize, struct passwd **result)256{257258return (cap_getpwcommon_r(chan, "getpwnam_r", name, 0, pwd, buffer,259bufsize, result));260}261262int263cap_getpwuid_r(cap_channel_t *chan, uid_t uid, struct passwd *pwd, char *buffer,264size_t bufsize, struct passwd **result)265{266267return (cap_getpwcommon_r(chan, "getpwuid_r", NULL, uid, pwd, buffer,268bufsize, result));269}270271int272cap_setpassent(cap_channel_t *chan, int stayopen)273{274nvlist_t *nvl;275276nvl = nvlist_create(0);277nvlist_add_string(nvl, "cmd", "setpassent");278nvlist_add_bool(nvl, "stayopen", stayopen != 0);279nvl = cap_xfer_nvlist(chan, nvl);280if (nvl == NULL)281return (0);282if (nvlist_get_number(nvl, "error") != 0) {283errno = nvlist_get_number(nvl, "error");284nvlist_destroy(nvl);285return (0);286}287nvlist_destroy(nvl);288289return (1);290}291292static void293cap_set_end_pwent(cap_channel_t *chan, const char *cmd)294{295nvlist_t *nvl;296297nvl = nvlist_create(0);298nvlist_add_string(nvl, "cmd", cmd);299/* Ignore any errors, we have no way to report them. */300nvlist_destroy(cap_xfer_nvlist(chan, nvl));301}302303void304cap_setpwent(cap_channel_t *chan)305{306307cap_set_end_pwent(chan, "setpwent");308}309310void311cap_endpwent(cap_channel_t *chan)312{313314cap_set_end_pwent(chan, "endpwent");315}316317int318cap_pwd_limit_cmds(cap_channel_t *chan, const char * const *cmds, size_t ncmds)319{320nvlist_t *limits, *nvl;321unsigned int i;322323if (cap_limit_get(chan, &limits) < 0)324return (-1);325if (limits == NULL) {326limits = nvlist_create(0);327} else {328if (nvlist_exists_nvlist(limits, "cmds"))329nvlist_free_nvlist(limits, "cmds");330}331nvl = nvlist_create(0);332for (i = 0; i < ncmds; i++)333nvlist_add_null(nvl, cmds[i]);334nvlist_move_nvlist(limits, "cmds", nvl);335return (cap_limit_set(chan, limits));336}337338int339cap_pwd_limit_fields(cap_channel_t *chan, const char * const *fields,340size_t nfields)341{342nvlist_t *limits, *nvl;343unsigned int i;344345if (cap_limit_get(chan, &limits) < 0)346return (-1);347if (limits == NULL) {348limits = nvlist_create(0);349} else {350if (nvlist_exists_nvlist(limits, "fields"))351nvlist_free_nvlist(limits, "fields");352}353nvl = nvlist_create(0);354for (i = 0; i < nfields; i++)355nvlist_add_null(nvl, fields[i]);356nvlist_move_nvlist(limits, "fields", nvl);357return (cap_limit_set(chan, limits));358}359360int361cap_pwd_limit_users(cap_channel_t *chan, const char * const *names,362size_t nnames, uid_t *uids, size_t nuids)363{364nvlist_t *limits, *users;365char nvlname[64];366unsigned int i;367int n;368369if (cap_limit_get(chan, &limits) < 0)370return (-1);371if (limits == NULL) {372limits = nvlist_create(0);373} else {374if (nvlist_exists_nvlist(limits, "users"))375nvlist_free_nvlist(limits, "users");376}377users = nvlist_create(0);378for (i = 0; i < nuids; i++) {379n = snprintf(nvlname, sizeof(nvlname), "uid%u", i);380assert(n > 0 && n < (int)sizeof(nvlname));381nvlist_add_number(users, nvlname, (uint64_t)uids[i]);382}383for (i = 0; i < nnames; i++) {384n = snprintf(nvlname, sizeof(nvlname), "name%u", i);385assert(n > 0 && n < (int)sizeof(nvlname));386nvlist_add_string(users, nvlname, names[i]);387}388nvlist_move_nvlist(limits, "users", users);389return (cap_limit_set(chan, limits));390}391392393/*394* Service functions.395*/396static bool397pwd_allowed_cmd(const nvlist_t *limits, const char *cmd)398{399400if (limits == NULL)401return (true);402403/*404* If no limit was set on allowed commands, then all commands405* are allowed.406*/407if (!nvlist_exists_nvlist(limits, "cmds"))408return (true);409410limits = nvlist_get_nvlist(limits, "cmds");411return (nvlist_exists_null(limits, cmd));412}413414static int415pwd_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits)416{417const char *name;418void *cookie;419int type;420421cookie = NULL;422while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {423if (type != NV_TYPE_NULL)424return (EINVAL);425if (!pwd_allowed_cmd(oldlimits, name))426return (ENOTCAPABLE);427}428429return (0);430}431432static bool433pwd_allowed_user(const nvlist_t *limits, const char *uname, uid_t uid)434{435const char *name;436void *cookie;437int type;438439if (limits == NULL)440return (true);441442/*443* If no limit was set on allowed users, then all users are allowed.444*/445if (!nvlist_exists_nvlist(limits, "users"))446return (true);447448limits = nvlist_get_nvlist(limits, "users");449cookie = NULL;450while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {451switch (type) {452case NV_TYPE_NUMBER:453if (uid != (uid_t)-1 &&454nvlist_get_number(limits, name) == (uint64_t)uid) {455return (true);456}457break;458case NV_TYPE_STRING:459if (uname != NULL &&460strcmp(nvlist_get_string(limits, name),461uname) == 0) {462return (true);463}464break;465default:466abort();467}468}469470return (false);471}472473static int474pwd_allowed_users(const nvlist_t *oldlimits, const nvlist_t *newlimits)475{476const char *name, *uname;477void *cookie;478uid_t uid;479int type;480481cookie = NULL;482while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {483switch (type) {484case NV_TYPE_NUMBER:485uid = (uid_t)nvlist_get_number(newlimits, name);486uname = NULL;487break;488case NV_TYPE_STRING:489uid = (uid_t)-1;490uname = nvlist_get_string(newlimits, name);491break;492default:493return (EINVAL);494}495if (!pwd_allowed_user(oldlimits, uname, uid))496return (ENOTCAPABLE);497}498499return (0);500}501502static bool503pwd_allowed_field(const nvlist_t *limits, const char *field)504{505506if (limits == NULL)507return (true);508509/*510* If no limit was set on allowed fields, then all fields are allowed.511*/512if (!nvlist_exists_nvlist(limits, "fields"))513return (true);514515limits = nvlist_get_nvlist(limits, "fields");516return (nvlist_exists_null(limits, field));517}518519static int520pwd_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits)521{522const char *name;523void *cookie;524int type;525526cookie = NULL;527while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {528if (type != NV_TYPE_NULL)529return (EINVAL);530if (!pwd_allowed_field(oldlimits, name))531return (ENOTCAPABLE);532}533534return (0);535}536537static bool538pwd_pack(const nvlist_t *limits, const struct passwd *pwd, nvlist_t *nvl)539{540int fields;541542if (pwd == NULL)543return (true);544545/*546* If either name or UID is allowed, we allow it.547*/548if (!pwd_allowed_user(limits, pwd->pw_name, pwd->pw_uid))549return (false);550551fields = pwd->pw_fields;552553if (pwd_allowed_field(limits, "pw_name")) {554nvlist_add_string(nvl, "pw_name", pwd->pw_name);555} else {556nvlist_add_string(nvl, "pw_name", "");557fields &= ~_PWF_NAME;558}559if (pwd_allowed_field(limits, "pw_uid")) {560nvlist_add_number(nvl, "pw_uid", (uint64_t)pwd->pw_uid);561} else {562nvlist_add_number(nvl, "pw_uid", (uint64_t)-1);563fields &= ~_PWF_UID;564}565if (pwd_allowed_field(limits, "pw_gid")) {566nvlist_add_number(nvl, "pw_gid", (uint64_t)pwd->pw_gid);567} else {568nvlist_add_number(nvl, "pw_gid", (uint64_t)-1);569fields &= ~_PWF_GID;570}571if (pwd_allowed_field(limits, "pw_change")) {572nvlist_add_number(nvl, "pw_change", (uint64_t)pwd->pw_change);573} else {574nvlist_add_number(nvl, "pw_change", (uint64_t)0);575fields &= ~_PWF_CHANGE;576}577if (pwd_allowed_field(limits, "pw_passwd")) {578nvlist_add_string(nvl, "pw_passwd", pwd->pw_passwd);579} else {580nvlist_add_string(nvl, "pw_passwd", "");581fields &= ~_PWF_PASSWD;582}583if (pwd_allowed_field(limits, "pw_class")) {584nvlist_add_string(nvl, "pw_class", pwd->pw_class);585} else {586nvlist_add_string(nvl, "pw_class", "");587fields &= ~_PWF_CLASS;588}589if (pwd_allowed_field(limits, "pw_gecos")) {590nvlist_add_string(nvl, "pw_gecos", pwd->pw_gecos);591} else {592nvlist_add_string(nvl, "pw_gecos", "");593fields &= ~_PWF_GECOS;594}595if (pwd_allowed_field(limits, "pw_dir")) {596nvlist_add_string(nvl, "pw_dir", pwd->pw_dir);597} else {598nvlist_add_string(nvl, "pw_dir", "");599fields &= ~_PWF_DIR;600}601if (pwd_allowed_field(limits, "pw_shell")) {602nvlist_add_string(nvl, "pw_shell", pwd->pw_shell);603} else {604nvlist_add_string(nvl, "pw_shell", "");605fields &= ~_PWF_SHELL;606}607if (pwd_allowed_field(limits, "pw_expire")) {608nvlist_add_number(nvl, "pw_expire", (uint64_t)pwd->pw_expire);609} else {610nvlist_add_number(nvl, "pw_expire", (uint64_t)0);611fields &= ~_PWF_EXPIRE;612}613nvlist_add_number(nvl, "pw_fields", (uint64_t)fields);614615return (true);616}617618static int619pwd_getpwent(const nvlist_t *limits, const nvlist_t *nvlin __unused,620nvlist_t *nvlout)621{622struct passwd *pwd;623624for (;;) {625errno = 0;626pwd = getpwent();627if (errno != 0)628return (errno);629if (pwd_pack(limits, pwd, nvlout))630return (0);631}632633/* NOTREACHED */634}635636static int637pwd_getpwnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)638{639struct passwd *pwd;640const char *name;641642if (!nvlist_exists_string(nvlin, "name"))643return (EINVAL);644name = nvlist_get_string(nvlin, "name");645assert(name != NULL);646647errno = 0;648pwd = getpwnam(name);649if (errno != 0)650return (errno);651652(void)pwd_pack(limits, pwd, nvlout);653654return (0);655}656657static int658pwd_getpwuid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)659{660struct passwd *pwd;661uid_t uid;662663if (!nvlist_exists_number(nvlin, "uid"))664return (EINVAL);665666uid = (uid_t)nvlist_get_number(nvlin, "uid");667668errno = 0;669pwd = getpwuid(uid);670if (errno != 0)671return (errno);672673(void)pwd_pack(limits, pwd, nvlout);674675return (0);676}677678static int679pwd_setpassent(const nvlist_t *limits __unused, const nvlist_t *nvlin,680nvlist_t *nvlout __unused)681{682int stayopen;683684if (!nvlist_exists_bool(nvlin, "stayopen"))685return (EINVAL);686687stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0;688689return (setpassent(stayopen) == 0 ? EFAULT : 0);690}691692static int693pwd_setpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,694nvlist_t *nvlout __unused)695{696697setpwent();698699return (0);700}701702static int703pwd_endpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,704nvlist_t *nvlout __unused)705{706707endpwent();708709return (0);710}711712static int713pwd_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)714{715const nvlist_t *limits;716const char *name;717void *cookie;718int error, type;719720if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") &&721!nvlist_exists_nvlist(newlimits, "cmds")) {722return (ENOTCAPABLE);723}724if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") &&725!nvlist_exists_nvlist(newlimits, "fields")) {726return (ENOTCAPABLE);727}728if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "users") &&729!nvlist_exists_nvlist(newlimits, "users")) {730return (ENOTCAPABLE);731}732733cookie = NULL;734while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {735if (type != NV_TYPE_NVLIST)736return (EINVAL);737limits = nvlist_get_nvlist(newlimits, name);738if (strcmp(name, "cmds") == 0)739error = pwd_allowed_cmds(oldlimits, limits);740else if (strcmp(name, "fields") == 0)741error = pwd_allowed_fields(oldlimits, limits);742else if (strcmp(name, "users") == 0)743error = pwd_allowed_users(oldlimits, limits);744else745error = EINVAL;746if (error != 0)747return (error);748}749750return (0);751}752753static int754pwd_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,755nvlist_t *nvlout)756{757int error;758759if (!pwd_allowed_cmd(limits, cmd))760return (ENOTCAPABLE);761762if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0)763error = pwd_getpwent(limits, nvlin, nvlout);764else if (strcmp(cmd, "getpwnam") == 0 || strcmp(cmd, "getpwnam_r") == 0)765error = pwd_getpwnam(limits, nvlin, nvlout);766else if (strcmp(cmd, "getpwuid") == 0 || strcmp(cmd, "getpwuid_r") == 0)767error = pwd_getpwuid(limits, nvlin, nvlout);768else if (strcmp(cmd, "setpassent") == 0)769error = pwd_setpassent(limits, nvlin, nvlout);770else if (strcmp(cmd, "setpwent") == 0)771error = pwd_setpwent(limits, nvlin, nvlout);772else if (strcmp(cmd, "endpwent") == 0)773error = pwd_endpwent(limits, nvlin, nvlout);774else775error = EINVAL;776777return (error);778}779780CREATE_SERVICE("system.pwd", pwd_limit, pwd_command, 0);781782783