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