1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2003-2007 Tim Kientzle 5 * All rights reserved. 6 */ 7 8 9 #include "cpio_platform.h" 10 11 #ifdef HAVE_ERRNO_H 12 #include <errno.h> 13 #endif 14 #ifdef HAVE_GRP_H 15 #include <grp.h> 16 #endif 17 #ifdef HAVE_PWD_H 18 #include <pwd.h> 19 #endif 20 #include <stdio.h> 21 #ifdef HAVE_STDLIB_H 22 #include <stdlib.h> 23 #endif 24 #ifdef HAVE_STRING_H 25 #include <string.h> 26 #endif 27 28 #include "cpio.h" 29 #include "err.h" 30 31 /* 32 * Short options for cpio. Please keep this sorted. 33 */ 34 static const char *short_options = "067AaBC:cdE:F:f:H:hI:iJjLlmnO:opR:rtuVvW:yZz"; 35 36 /* 37 * Long options for cpio. Please keep this sorted. 38 */ 39 static const struct option { 40 const char *name; 41 int required; /* 1 if this option requires an argument */ 42 int equivalent; /* Equivalent short option. */ 43 } cpio_longopts[] = { 44 { "b64encode", 0, OPTION_B64ENCODE }, 45 { "binary", 0, '7' }, 46 { "create", 0, 'o' }, 47 { "dereference", 0, 'L' }, 48 { "dot", 0, 'V' }, 49 { "extract", 0, 'i' }, 50 { "file", 1, 'F' }, 51 { "format", 1, 'H' }, 52 { "grzip", 0, OPTION_GRZIP }, 53 { "help", 0, 'h' }, 54 { "insecure", 0, OPTION_INSECURE }, 55 { "link", 0, 'l' }, 56 { "list", 0, 't' }, 57 { "lrzip", 0, OPTION_LRZIP }, 58 { "lz4", 0, OPTION_LZ4 }, 59 { "lzma", 0, OPTION_LZMA }, 60 { "lzop", 0, OPTION_LZOP }, 61 { "make-directories", 0, 'd' }, 62 { "no-preserve-owner", 0, OPTION_NO_PRESERVE_OWNER }, 63 { "null", 0, '0' }, 64 { "numeric-uid-gid", 0, 'n' }, 65 { "owner", 1, 'R' }, 66 { "passphrase", 1, OPTION_PASSPHRASE }, 67 { "pass-through", 0, 'p' }, 68 { "preserve-modification-time", 0, 'm' }, 69 { "preserve-owner", 0, OPTION_PRESERVE_OWNER }, 70 { "pwb", 0, '6' }, 71 { "quiet", 0, OPTION_QUIET }, 72 { "unconditional", 0, 'u' }, 73 { "uuencode", 0, OPTION_UUENCODE }, 74 { "verbose", 0, 'v' }, 75 { "version", 0, OPTION_VERSION }, 76 { "xz", 0, 'J' }, 77 { "zstd", 0, OPTION_ZSTD }, 78 { NULL, 0, 0 } 79 }; 80 81 /* 82 * I used to try to select platform-provided getopt() or 83 * getopt_long(), but that caused a lot of headaches. In particular, 84 * I couldn't consistently use long options in the test harness 85 * because not all platforms have getopt_long(). That in turn led to 86 * overuse of the -W hack in the test harness, which made it rough to 87 * run the test harness against GNU cpio. (I periodically run the 88 * test harness here against GNU cpio as a sanity-check. Yes, 89 * I've found a couple of bugs in GNU cpio that way.) 90 */ 91 int 92 cpio_getopt(struct cpio *cpio) 93 { 94 enum { state_start = 0, state_next_word, state_short, state_long }; 95 static int state = state_start; 96 static char *opt_word; 97 98 const struct option *popt, *match, *match2; 99 const char *p, *long_prefix; 100 size_t optlength; 101 int opt; 102 int required; 103 104 again: 105 match = NULL; 106 match2 = NULL; 107 long_prefix = "--"; 108 opt = '?'; 109 required = 0; 110 cpio->argument = NULL; 111 112 /* First time through, initialize everything. */ 113 if (state == state_start) { 114 /* Skip program name. */ 115 ++cpio->argv; 116 --cpio->argc; 117 state = state_next_word; 118 } 119 120 /* 121 * We're ready to look at the next word in argv. 122 */ 123 if (state == state_next_word) { 124 /* No more arguments, so no more options. */ 125 if (cpio->argv[0] == NULL) 126 return (-1); 127 /* Doesn't start with '-', so no more options. */ 128 if (cpio->argv[0][0] != '-') 129 return (-1); 130 /* "--" marks end of options; consume it and return. */ 131 if (strcmp(cpio->argv[0], "--") == 0) { 132 ++cpio->argv; 133 --cpio->argc; 134 return (-1); 135 } 136 /* Get next word for parsing. */ 137 opt_word = *cpio->argv++; 138 --cpio->argc; 139 if (opt_word[1] == '-') { 140 /* Set up long option parser. */ 141 state = state_long; 142 opt_word += 2; /* Skip leading '--' */ 143 } else { 144 /* Set up short option parser. */ 145 state = state_short; 146 ++opt_word; /* Skip leading '-' */ 147 } 148 } 149 150 /* 151 * We're parsing a group of POSIX-style single-character options. 152 */ 153 if (state == state_short) { 154 /* Peel next option off of a group of short options. */ 155 opt = *opt_word++; 156 if (opt == '\0') { 157 /* End of this group; recurse to get next option. */ 158 state = state_next_word; 159 goto again; 160 } 161 162 /* Does this option take an argument? */ 163 p = strchr(short_options, opt); 164 if (p == NULL) 165 return ('?'); 166 if (p[1] == ':') 167 required = 1; 168 169 /* If it takes an argument, parse that. */ 170 if (required) { 171 /* If arg is run-in, opt_word already points to it. */ 172 if (opt_word[0] == '\0') { 173 /* Otherwise, pick up the next word. */ 174 opt_word = *cpio->argv; 175 if (opt_word == NULL) { 176 lafe_warnc(0, 177 "Option -%c requires an argument", 178 opt); 179 return ('?'); 180 } 181 ++cpio->argv; 182 --cpio->argc; 183 } 184 if (opt == 'W') { 185 state = state_long; 186 long_prefix = "-W "; /* For clearer errors. */ 187 } else { 188 state = state_next_word; 189 cpio->argument = opt_word; 190 } 191 } 192 } 193 194 /* We're reading a long option, including -W long=arg convention. */ 195 if (state == state_long) { 196 /* After this long option, we'll be starting a new word. */ 197 state = state_next_word; 198 199 /* Option name ends at '=' if there is one. */ 200 p = strchr(opt_word, '='); 201 if (p != NULL) { 202 optlength = (size_t)(p - opt_word); 203 cpio->argument = (char *)(uintptr_t)(p + 1); 204 } else { 205 optlength = strlen(opt_word); 206 } 207 208 /* Search the table for an unambiguous match. */ 209 for (popt = cpio_longopts; popt->name != NULL; popt++) { 210 /* Short-circuit if first chars don't match. */ 211 if (popt->name[0] != opt_word[0]) 212 continue; 213 /* If option is a prefix of name in table, record it.*/ 214 if (strncmp(opt_word, popt->name, optlength) == 0) { 215 match2 = match; /* Record up to two matches. */ 216 match = popt; 217 /* If it's an exact match, we're done. */ 218 if (strlen(popt->name) == optlength) { 219 match2 = NULL; /* Forget the others. */ 220 break; 221 } 222 } 223 } 224 225 /* Fail if there wasn't a unique match. */ 226 if (match == NULL) { 227 lafe_warnc(0, 228 "Option %s%s is not supported", 229 long_prefix, opt_word); 230 return ('?'); 231 } 232 if (match2 != NULL) { 233 lafe_warnc(0, 234 "Ambiguous option %s%s (matches --%s and --%s)", 235 long_prefix, opt_word, match->name, match2->name); 236 return ('?'); 237 } 238 239 /* We've found a unique match; does it need an argument? */ 240 if (match->required) { 241 /* Argument required: get next word if necessary. */ 242 if (cpio->argument == NULL) { 243 cpio->argument = *cpio->argv; 244 if (cpio->argument == NULL) { 245 lafe_warnc(0, 246 "Option %s%s requires an argument", 247 long_prefix, match->name); 248 return ('?'); 249 } 250 ++cpio->argv; 251 --cpio->argc; 252 } 253 } else { 254 /* Argument forbidden: fail if there is one. */ 255 if (cpio->argument != NULL) { 256 lafe_warnc(0, 257 "Option %s%s does not allow an argument", 258 long_prefix, match->name); 259 return ('?'); 260 } 261 } 262 return (match->equivalent); 263 } 264 265 return (opt); 266 } 267 268 269 /* 270 * Parse the argument to the -R or --owner flag. 271 * 272 * The format is one of the following: 273 * <username|uid> - Override user but not group 274 * <username>: - Override both, group is user's default group 275 * <uid>: - Override user but not group 276 * <username|uid>:<groupname|gid> - Override both 277 * :<groupname|gid> - Override group but not user 278 * 279 * Where uid/gid are decimal representations and groupname/username 280 * are names to be looked up in system database. Note that we try 281 * to look up an argument as a name first, then try numeric parsing. 282 * 283 * A period can be used instead of the colon. 284 * 285 * Sets uid/gid return as appropriate, -1 indicates uid/gid not specified. 286 * TODO: If the spec uses uname/gname, then return those to the caller 287 * as well. If the spec provides uid/gid, just return names as NULL. 288 * 289 * Returns NULL if no error, otherwise returns error string for display. 290 * 291 */ 292 int 293 owner_parse(const char *spec, struct cpio_owner *owner, const char **errmsg) 294 { 295 static char errbuff[128]; 296 const char *u, *ue, *g; 297 298 owner->uid = -1; 299 owner->gid = -1; 300 301 owner->uname = NULL; 302 owner->gname = NULL; 303 304 if (spec[0] == '\0') { 305 *errmsg = "Invalid empty user/group spec"; 306 return (-1); 307 } 308 309 /* 310 * Split spec into [user][:.][group] 311 * u -> first char of username, NULL if no username 312 * ue -> first char after username (colon, period, or \0) 313 * g -> first char of group name 314 */ 315 if (*spec == ':' || *spec == '.') { 316 /* If spec starts with ':' or '.', then just group. */ 317 ue = u = NULL; 318 g = spec + 1; 319 } else { 320 /* Otherwise, [user] or [user][:] or [user][:][group] */ 321 ue = u = spec; 322 while (*ue != ':' && *ue != '.' && *ue != '\0') 323 ++ue; 324 g = ue; 325 if (*g != '\0') /* Skip : or . to find first char of group. */ 326 ++g; 327 } 328 329 if (u != NULL) { 330 /* Look up user: ue is first char after end of user. */ 331 char *user; 332 struct passwd *pwent; 333 334 user = malloc(ue - u + 1); 335 if (user == NULL) 336 goto alloc_error; 337 memcpy(user, u, ue - u); 338 user[ue - u] = '\0'; 339 if ((pwent = getpwnam(user)) != NULL) { 340 owner->uid = pwent->pw_uid; 341 owner->uname = strdup(pwent->pw_name); 342 if (owner->uname == NULL) { 343 free(user); 344 goto alloc_error; 345 } 346 if (*ue != '\0') 347 owner->gid = pwent->pw_gid; 348 } else { 349 char *end; 350 errno = 0; 351 owner->uid = (int)strtoul(user, &end, 10); 352 if (errno || *end != '\0') { 353 snprintf(errbuff, sizeof(errbuff), 354 "Couldn't lookup user ``%s''", user); 355 errbuff[sizeof(errbuff) - 1] = '\0'; 356 free(user); 357 *errmsg = errbuff; 358 return (-1); 359 } 360 } 361 free(user); 362 } 363 364 if (*g != '\0') { 365 struct group *grp; 366 if ((grp = getgrnam(g)) != NULL) { 367 owner->gid = grp->gr_gid; 368 owner->gname = strdup(grp->gr_name); 369 if (owner->gname == NULL) { 370 free(owner->uname); 371 owner->uname = NULL; 372 goto alloc_error; 373 } 374 } else { 375 char *end; 376 errno = 0; 377 owner->gid = (int)strtoul(g, &end, 10); 378 if (errno || *end != '\0') { 379 snprintf(errbuff, sizeof(errbuff), 380 "Couldn't lookup group ``%s''", g); 381 errbuff[sizeof(errbuff) - 1] = '\0'; 382 *errmsg = errbuff; 383 return (-1); 384 } 385 } 386 } 387 return (0); 388 alloc_error: 389 *errmsg = "Couldn't allocate memory"; 390 return (-1); 391 } 392