1 /* $NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $ */ 2 3 /*- 4 * Copyright (c) 2007 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Brian Ginsbach. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 #include <sys/cdefs.h> 32 __RCSID("$NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $"); 33 34 #include <sys/param.h> 35 #include <err.h> 36 #include <errno.h> 37 #include <grp.h> 38 #include <pwd.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 #include <util.h> 44 45 #ifdef LOGIN_CAP 46 #include <login_cap.h> 47 #endif 48 49 #include "grutil.h" 50 51 typedef enum { 52 ADDGRP_NOERROR = 0, /* must be zero */ 53 ADDGRP_EMALLOC = 1, 54 ADDGRP_EGETGROUPS = 2, 55 ADDGRP_ESETGROUPS = 3 56 } addgrp_ret_t; 57 58 static void 59 free_groups(void *groups) 60 { 61 int oerrno; 62 63 oerrno = errno; 64 free(groups); 65 errno = oerrno; 66 } 67 68 static addgrp_ret_t 69 alloc_groups(int *ngroups, gid_t **groups, int *ngroupsmax) 70 { 71 *ngroupsmax = (int)sysconf(_SC_NGROUPS_MAX); 72 if (*ngroupsmax < 0) 73 *ngroupsmax = NGROUPS_MAX; 74 75 *groups = malloc(*ngroupsmax * sizeof(**groups)); 76 if (*groups == NULL) 77 return ADDGRP_EMALLOC; 78 79 *ngroups = getgroups(*ngroupsmax, *groups); 80 if (*ngroups == -1) { 81 free_groups(*groups); 82 return ADDGRP_ESETGROUPS; 83 } 84 return ADDGRP_NOERROR; 85 } 86 87 static addgrp_ret_t 88 addgid(gid_t *groups, int ngroups, int ngroupsmax, gid_t gid, int makespace) 89 { 90 int i; 91 92 /* search for gid in supplemental group list */ 93 for (i = 0; i < ngroups && groups[i] != gid; i++) 94 continue; 95 96 /* add the gid to the supplemental group list */ 97 if (i == ngroups) { 98 if (ngroups < ngroupsmax) 99 groups[ngroups++] = gid; 100 else { /* 101 * setgroups(2) will fail with errno = EINVAL 102 * if ngroups > nmaxgroups. If makespace is 103 * set, replace the last group with the new 104 * one. Otherwise, fail the way setgroups(2) 105 * would if we passed the larger groups array. 106 */ 107 if (makespace) { 108 /* 109 * Find a slot that doesn't contain 110 * the primary group. 111 */ 112 struct passwd *pwd; 113 gid_t pgid; 114 pwd = getpwuid(getuid()); 115 if (pwd == NULL) 116 goto error; 117 pgid = pwd->pw_gid; 118 for (i = ngroupsmax - 1; i >= 0; i--) 119 if (groups[i] != pgid) 120 break; 121 if (i < 0) 122 goto error; 123 groups[i] = gid; 124 } 125 else { 126 error: 127 errno = EINVAL; 128 return ADDGRP_ESETGROUPS; 129 } 130 } 131 if (setgroups(ngroups, groups) < 0) 132 return ADDGRP_ESETGROUPS; 133 } 134 return ADDGRP_NOERROR; 135 } 136 137 static addgrp_ret_t 138 addgrp(gid_t newgid, int makespace) 139 { 140 int ngroups, ngroupsmax; 141 addgrp_ret_t rval; 142 gid_t *groups; 143 gid_t oldgid; 144 145 oldgid = getgid(); 146 if (oldgid == newgid) /* nothing to do */ 147 return ADDGRP_NOERROR; 148 149 rval = alloc_groups(&ngroups, &groups, &ngroupsmax); 150 if (rval != ADDGRP_NOERROR) 151 return rval; 152 153 /* 154 * BSD based systems normally have the egid in the supplemental 155 * group list. 156 */ 157 #if (defined(BSD) && BSD >= 199306) 158 /* 159 * According to POSIX/XPG6: 160 * On system where the egid is normally in the supplemental group list 161 * (or whenever the old egid actually is in the supplemental group 162 * list): 163 * o If the new egid is in the supplemental group list, 164 * just change the egid. 165 * o If the new egid is not in the supplemental group list, 166 * add the new egid to the list if there is room. 167 */ 168 169 rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace); 170 #else 171 /* 172 * According to POSIX/XPG6: 173 * On systems where the egid is not normally in the supplemental group 174 * list (or whenever the old egid is not in the supplemental group 175 * list): 176 * o If the new egid is in the supplemental group list, delete 177 * it from the list. 178 * o If the old egid is not in the supplemental group list, 179 * add the old egid to the list if there is room. 180 */ 181 { 182 int i; 183 184 /* search for new egid in supplemental group list */ 185 for (i = 0; i < ngroups && groups[i] != newgid; i++) 186 continue; 187 188 /* remove new egid from supplemental group list */ 189 if (i != ngroups) 190 for (--ngroups; i < ngroups; i++) 191 groups[i] = groups[i + 1]; 192 193 rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace); 194 } 195 #endif 196 free_groups(groups); 197 return rval; 198 } 199 200 /* 201 * If newgrp fails, it returns (gid_t)-1 and the errno variable is 202 * set to: 203 * [EINVAL] Unknown group. 204 * [EPERM] Bad password. 205 */ 206 static gid_t 207 newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt) 208 { 209 struct group *grp; 210 char **ap; 211 char *p; 212 gid_t *groups; 213 int ngroups, ngroupsmax; 214 215 if (gname == NULL) 216 return pwd->pw_gid; 217 218 grp = getgrnam(gname); 219 220 #ifdef GRUTIL_ACCEPT_GROUP_NUMBERS 221 if (grp == NULL) { 222 gid_t gid; 223 if (*gname != '-') { 224 gid = (gid_t)strtol(gname, &p, 10); 225 if (*p == '\0') 226 grp = getgrgid(gid); 227 } 228 } 229 #endif 230 if (grp == NULL) { 231 errno = EINVAL; 232 return (gid_t)-1; 233 } 234 235 if (ruid == 0 || pwd->pw_gid == grp->gr_gid) 236 return grp->gr_gid; 237 238 if (alloc_groups(&ngroups, &groups, &ngroupsmax) == ADDGRP_NOERROR) { 239 int i; 240 for (i = 0; i < ngroups; i++) 241 if (groups[i] == grp->gr_gid) { 242 free_groups(groups); 243 return grp->gr_gid; 244 } 245 free_groups(groups); 246 } 247 248 /* 249 * Check the group membership list in case the groups[] array 250 * was maxed out or the user has been added to it since login. 251 */ 252 for (ap = grp->gr_mem; *ap != NULL; ap++) 253 if (strcmp(*ap, pwd->pw_name) == 0) 254 return grp->gr_gid; 255 256 if (*grp->gr_passwd != '\0') { 257 p = getpass(prompt); 258 if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) { 259 (void)memset(p, '\0', _PASSWORD_LEN); 260 return grp->gr_gid; 261 } 262 (void)memset(p, '\0', _PASSWORD_LEN); 263 } 264 265 errno = EPERM; 266 return (gid_t)-1; 267 } 268 269 #ifdef GRUTIL_SETGROUPS_MAKESPACE 270 # define ADDGRP_MAKESPACE 1 271 #else 272 # define ADDGRP_MAKESPACE 0 273 #endif 274 275 #ifdef GRUTIL_ALLOW_GROUP_ERRORS 276 # define maybe_exit(e) 277 #else 278 # define maybe_exit(e) exit(e); 279 #endif 280 281 void 282 addgroup( 283 #ifdef LOGIN_CAP 284 login_cap_t *lc, 285 #endif 286 const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt) 287 { 288 pwd->pw_gid = newgrp(gname, pwd, ruid, prompt); 289 if (pwd->pw_gid == (gid_t)-1) { 290 switch (errno) { 291 case EINVAL: 292 warnx("Unknown group `%s'", gname); 293 maybe_exit(EXIT_FAILURE); 294 break; 295 case EPERM: /* password failure */ 296 warnx("Sorry"); 297 maybe_exit(EXIT_FAILURE); 298 break; 299 default: /* XXX - should never happen */ 300 err(EXIT_FAILURE, "unknown error"); 301 break; 302 } 303 pwd->pw_gid = getgid(); 304 } 305 306 switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) { 307 case ADDGRP_NOERROR: 308 break; 309 case ADDGRP_EMALLOC: 310 err(EXIT_FAILURE, "malloc"); 311 break; 312 case ADDGRP_EGETGROUPS: 313 err(EXIT_FAILURE, "getgroups"); 314 break; 315 case ADDGRP_ESETGROUPS: 316 switch(errno) { 317 case EINVAL: 318 warnx("setgroups: ngroups > ngroupsmax"); 319 maybe_exit(EXIT_FAILURE); 320 break; 321 case EPERM: 322 case EFAULT: 323 default: 324 warn("setgroups"); 325 maybe_exit(EXIT_FAILURE); 326 break; 327 } 328 break; 329 } 330 331 #ifdef LOGIN_CAP 332 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1) 333 err(EXIT_FAILURE, "setting user context"); 334 #else 335 if (setgid(pwd->pw_gid) == -1) 336 err(EXIT_FAILURE, "setgid"); 337 #endif 338 } 339