1 /* $NetBSD: grutil.c,v 1.2 2008/04/28 20:24:14 martin 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.2 2008/04/28 20:24:14 martin 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, rval; 141 gid_t *groups; 142 gid_t oldgid; 143 144 oldgid = getgid(); 145 if (oldgid == newgid) /* nothing to do */ 146 return ADDGRP_NOERROR; 147 148 rval = alloc_groups(&ngroups, &groups, &ngroupsmax); 149 if (rval != 0) 150 return rval; 151 152 /* 153 * BSD based systems normally have the egid in the supplemental 154 * group list. 155 */ 156 #if (defined(BSD) && BSD >= 199306) 157 /* 158 * According to POSIX/XPG6: 159 * On system where the egid is normally in the supplemental group list 160 * (or whenever the old egid actually is in the supplemental group 161 * list): 162 * o If the new egid is in the supplemental group list, 163 * just change the egid. 164 * o If the new egid is not in the supplemental group list, 165 * add the new egid to the list if there is room. 166 */ 167 168 rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace); 169 #else 170 /* 171 * According to POSIX/XPG6: 172 * On systems where the egid is not normally in the supplemental group 173 * list (or whenever the old egid is not in the supplemental group 174 * list): 175 * o If the new egid is in the supplemental group list, delete 176 * it from the list. 177 * o If the old egid is not in the supplemental group list, 178 * add the old egid to the list if there is room. 179 */ 180 { 181 int i; 182 183 /* search for new egid in supplemental group list */ 184 for (i = 0; i < ngroups && groups[i] != newgid; i++) 185 continue; 186 187 /* remove new egid from supplemental group list */ 188 if (i != ngroups) 189 for (--ngroups; i < ngroups; i++) 190 groups[i] = groups[i + 1]; 191 192 rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace); 193 } 194 #endif 195 free_groups(groups); 196 return rval; 197 } 198 199 /* 200 * If newgrp fails, it returns (gid_t)-1 and the errno variable is 201 * set to: 202 * [EINVAL] Unknown group. 203 * [EPERM] Bad password. 204 */ 205 static gid_t 206 newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt) 207 { 208 struct group *grp; 209 char **ap; 210 char *p; 211 gid_t *groups; 212 int ngroups, ngroupsmax; 213 214 if (gname == NULL) 215 return pwd->pw_gid; 216 217 grp = getgrnam(gname); 218 219 #ifdef GRUTIL_ACCEPT_GROUP_NUMBERS 220 if (grp == NULL) { 221 gid_t gid; 222 if (*gname != '-') { 223 gid = (gid_t)strtol(gname, &p, 10); 224 if (*p == '\0') 225 grp = getgrgid(gid); 226 } 227 } 228 #endif 229 if (grp == NULL) { 230 errno = EINVAL; 231 return (gid_t)-1; 232 } 233 234 if (ruid == 0 || pwd->pw_gid == grp->gr_gid) 235 return grp->gr_gid; 236 237 if (alloc_groups(&ngroups, &groups, &ngroupsmax) == 0) { 238 int i; 239 for (i = 0; i < ngroups; i++) 240 if (groups[i] == grp->gr_gid) { 241 free_groups(groups); 242 return grp->gr_gid; 243 } 244 free_groups(groups); 245 } 246 247 /* 248 * Check the group membership list in case the groups[] array 249 * was maxed out or the user has been added to it since login. 250 */ 251 for (ap = grp->gr_mem; *ap != NULL; ap++) 252 if (strcmp(*ap, pwd->pw_name) == 0) 253 return grp->gr_gid; 254 255 if (*grp->gr_passwd != '\0') { 256 p = getpass(prompt); 257 if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) { 258 (void)memset(p, '\0', _PASSWORD_LEN); 259 return grp->gr_gid; 260 } 261 (void)memset(p, '\0', _PASSWORD_LEN); 262 } 263 264 errno = EPERM; 265 return (gid_t)-1; 266 } 267 268 #ifdef GRUTIL_SETGROUPS_MAKESPACE 269 # define ADDGRP_MAKESPACE 1 270 #else 271 # define ADDGRP_MAKESPACE 0 272 #endif 273 274 #ifdef GRUTIL_ALLOW_GROUP_ERRORS 275 # define maybe_exit(e) 276 #else 277 # define maybe_exit(e) exit(e); 278 #endif 279 280 void 281 addgroup( 282 #ifdef LOGIN_CAP 283 login_cap_t *lc, 284 #endif 285 const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt) 286 { 287 pwd->pw_gid = newgrp(gname, pwd, ruid, prompt); 288 if (pwd->pw_gid == (gid_t)-1) { 289 switch (errno) { 290 case EINVAL: 291 warnx("Unknown group `%s'", gname); 292 maybe_exit(EXIT_FAILURE); 293 break; 294 case EPERM: /* password failure */ 295 warnx("Sorry"); 296 maybe_exit(EXIT_FAILURE); 297 break; 298 default: /* XXX - should never happen */ 299 err(EXIT_FAILURE, "unknown error"); 300 break; 301 } 302 pwd->pw_gid = getgid(); 303 } 304 305 switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) { 306 case ADDGRP_NOERROR: 307 break; 308 case ADDGRP_EMALLOC: 309 err(EXIT_FAILURE, "malloc"); 310 break; 311 case ADDGRP_EGETGROUPS: 312 err(EXIT_FAILURE, "getgroups"); 313 break; 314 case ADDGRP_ESETGROUPS: 315 switch(errno) { 316 case EINVAL: 317 warnx("setgroups: ngroups > ngroupsmax"); 318 maybe_exit(EXIT_FAILURE); 319 break; 320 case EPERM: 321 case EFAULT: 322 default: 323 warn("setgroups"); 324 maybe_exit(EXIT_FAILURE); 325 break; 326 } 327 break; 328 } 329 330 #ifdef LOGIN_CAP 331 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1) 332 err(EXIT_FAILURE, "setting user context"); 333 #else 334 if (setgid(pwd->pw_gid) == -1) 335 err(EXIT_FAILURE, "setgid"); 336 #endif 337 } 338