1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2005 - Garance Alistair Drosehn <gad@FreeBSD.org>. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * The views and conclusions contained in the software and documentation 29 * are those of the authors and should not be interpreted as representing 30 * official policies, either expressed or implied, of the FreeBSD Project. 31 */ 32 33 #include <sys/cdefs.h> 34 #include <sys/stat.h> 35 #include <sys/param.h> 36 #include <err.h> 37 #include <errno.h> 38 #include <ctype.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 44 #include "envopts.h" 45 46 static const char * 47 expand_vars(int in_thisarg, char **thisarg_p, char **dest_p, 48 const char **src_p); 49 static int is_there(char *candidate); 50 51 /* 52 * The is*() routines take a parameter of 'int', but expect values in the range 53 * of unsigned char. Define some wrappers which take a value of type 'char', 54 * whether signed or unsigned, and ensure the value ends up in the right range. 55 */ 56 #define isalnumch(Anychar) isalnum((u_char)(Anychar)) 57 #define isalphach(Anychar) isalpha((u_char)(Anychar)) 58 #define isspacech(Anychar) isspace((u_char)(Anychar)) 59 60 /* 61 * Routine to determine if a given fully-qualified filename is executable. 62 * This is copied almost verbatim from FreeBSD's usr.bin/which/which.c. 63 */ 64 static int 65 is_there(char *candidate) 66 { 67 struct stat fin; 68 69 /* XXX work around access(2) false positives for superuser */ 70 if (access(candidate, X_OK) == 0 && 71 stat(candidate, &fin) == 0 && 72 S_ISREG(fin.st_mode) && 73 (getuid() != 0 || 74 (fin.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)) { 75 if (env_verbosity > 1) 76 fprintf(stderr, "#env matched:\t'%s'\n", candidate); 77 return (1); 78 } 79 return (0); 80 } 81 82 /** 83 * Routine to search through an alternate path-list, looking for a given 84 * filename to execute. If the file is found, replace the original 85 * unqualified name with a fully-qualified path. This allows `env' to 86 * execute programs from a specific strict list of possible paths, without 87 * changing the value of PATH seen by the program which will be executed. 88 * E.G.: 89 * #!/usr/bin/env -S-P/usr/local/bin:/usr/bin perl 90 * will execute /usr/local/bin/perl or /usr/bin/perl (whichever is found 91 * first), no matter what the current value of PATH is, and without 92 * changing the value of PATH that the script will see when it runs. 93 * 94 * This is similar to the print_matches() routine in usr.bin/which/which.c. 95 */ 96 void 97 search_paths(char *path, char **argv) 98 { 99 char candidate[PATH_MAX]; 100 const char *d; 101 char *filename, *fqname; 102 103 /* If the file has a `/' in it, then no search is done */ 104 filename = *argv; 105 if (strchr(filename, '/') != NULL) 106 return; 107 108 if (env_verbosity > 1) { 109 fprintf(stderr, "#env Searching:\t'%s'\n", path); 110 fprintf(stderr, "#env for file:\t'%s'\n", filename); 111 } 112 113 fqname = NULL; 114 while ((d = strsep(&path, ":")) != NULL) { 115 if (*d == '\0') 116 d = "."; 117 if (snprintf(candidate, sizeof(candidate), "%s/%s", d, 118 filename) >= (int)sizeof(candidate)) 119 continue; 120 if (is_there(candidate)) { 121 fqname = candidate; 122 break; 123 } 124 } 125 126 if (fqname == NULL) { 127 errno = ENOENT; 128 err(127, "%s", filename); 129 } 130 *argv = strdup(candidate); 131 } 132 133 /** 134 * Routine to split a string into multiple parameters, while recognizing a 135 * few special characters. It recognizes both single and double-quoted 136 * strings. This processing is designed entirely for the benefit of the 137 * parsing of "#!"-lines (aka "shebang" lines == the first line of an 138 * executable script). Different operating systems parse that line in very 139 * different ways, and this split-on-spaces processing is meant to provide 140 * ways to specify arbitrary arguments on that line, no matter how the OS 141 * parses it. 142 * 143 * Within a single-quoted string, the two characters "\'" are treated as 144 * a literal "'" character to add to the string, and "\\" are treated as 145 * a literal "\" character to add. Other than that, all characters are 146 * copied until the processing gets to a terminating "'". 147 * 148 * Within a double-quoted string, many more "\"-style escape sequences 149 * are recognized, mostly copied from what is recognized in the `printf' 150 * command. Some OS's will not allow a literal blank character to be 151 * included in the one argument that they recognize on a shebang-line, 152 * so a few additional escape-sequences are defined to provide ways to 153 * specify blanks. 154 * 155 * Within a double-quoted string "\_" is turned into a literal blank. 156 * (Inside of a single-quoted string, the two characters are just copied) 157 * Outside of a quoted string, "\_" is treated as both a blank, and the 158 * end of the current argument. So with a shelbang-line of: 159 * #!/usr/bin/env -SA=avalue\_perl 160 * the -S value would be broken up into arguments "A=avalue" and "perl". 161 */ 162 void 163 split_spaces(const char *str, int *origind, int *origc, char ***origv) 164 { 165 static const char *nullarg = ""; 166 const char *bq_src, *copystr, *src; 167 char *dest, **newargv, *newstr, **nextarg, **oldarg; 168 int addcount, bq_destlen, copychar, found_sep, in_arg, in_dq, in_sq; 169 170 /* 171 * Ignore leading space on the string, and then malloc enough room 172 * to build a copy of it. The copy might end up shorter than the 173 * original, due to quoted strings and '\'-processing. 174 */ 175 while (isspacech(*str)) 176 str++; 177 if (*str == '\0') 178 return; 179 newstr = malloc(strlen(str) + 1); 180 181 /* 182 * Allocate plenty of space for the new array of arg-pointers, 183 * and start that array off with the first element of the old 184 * array. 185 */ 186 newargv = malloc((*origc + (strlen(str) / 2) + 2) * sizeof(char *)); 187 nextarg = newargv; 188 *nextarg++ = **origv; 189 190 /* Come up with the new args by splitting up the given string. */ 191 addcount = 0; 192 bq_destlen = in_arg = in_dq = in_sq = 0; 193 bq_src = NULL; 194 for (src = str, dest = newstr; *src != '\0'; src++) { 195 /* 196 * This switch will look at a character in *src, and decide 197 * what should be copied to *dest. It only decides what 198 * character(s) to copy, it should not modify *dest. In some 199 * cases, it will look at multiple characters from *src. 200 */ 201 copychar = found_sep = 0; 202 copystr = NULL; 203 switch (*src) { 204 case '"': 205 if (in_sq) 206 copychar = *src; 207 else if (in_dq) 208 in_dq = 0; 209 else { 210 /* 211 * Referencing nullarg ensures that a new 212 * argument is created, even if this quoted 213 * string ends up with zero characters. 214 */ 215 copystr = nullarg; 216 in_dq = 1; 217 bq_destlen = dest - *(nextarg - 1); 218 bq_src = src; 219 } 220 break; 221 case '$': 222 if (in_sq) 223 copychar = *src; 224 else { 225 copystr = expand_vars(in_arg, (nextarg - 1), 226 &dest, &src); 227 } 228 break; 229 case '\'': 230 if (in_dq) 231 copychar = *src; 232 else if (in_sq) 233 in_sq = 0; 234 else { 235 /* 236 * Referencing nullarg ensures that a new 237 * argument is created, even if this quoted 238 * string ends up with zero characters. 239 */ 240 copystr = nullarg; 241 in_sq = 1; 242 bq_destlen = dest - *(nextarg - 1); 243 bq_src = src; 244 } 245 break; 246 case '\\': 247 if (in_sq) { 248 /* 249 * Inside single-quoted strings, only the 250 * "\'" and "\\" are recognized as special 251 * strings. 252 */ 253 copychar = *(src + 1); 254 if (copychar == '\'' || copychar == '\\') 255 src++; 256 else 257 copychar = *src; 258 break; 259 } 260 src++; 261 switch (*src) { 262 case '"': 263 case '#': 264 case '$': 265 case '\'': 266 case '\\': 267 copychar = *src; 268 break; 269 case '_': 270 /* 271 * Alternate way to get a blank, which allows 272 * that blank be used to separate arguments 273 * when it is not inside a quoted string. 274 */ 275 if (in_dq) 276 copychar = ' '; 277 else { 278 found_sep = 1; 279 src++; 280 } 281 break; 282 case 'c': 283 /* 284 * Ignore remaining characters in the -S string. 285 * This would not make sense if found in the 286 * middle of a quoted string. 287 */ 288 if (in_dq) 289 errx(1, "Sequence '\\%c' is not allowed" 290 " in quoted strings", *src); 291 goto str_done; 292 case 'f': 293 copychar = '\f'; 294 break; 295 case 'n': 296 copychar = '\n'; 297 break; 298 case 'r': 299 copychar = '\r'; 300 break; 301 case 't': 302 copychar = '\t'; 303 break; 304 case 'v': 305 copychar = '\v'; 306 break; 307 default: 308 if (isspacech(*src)) 309 copychar = *src; 310 else 311 errx(1, "Invalid sequence '\\%c' in -S", 312 *src); 313 } 314 break; 315 default: 316 if ((in_dq || in_sq) && in_arg) 317 copychar = *src; 318 else if (isspacech(*src)) 319 found_sep = 1; 320 else { 321 /* 322 * If the first character of a new argument 323 * is `#', then ignore the remaining chars. 324 */ 325 if (!in_arg && *src == '#') 326 goto str_done; 327 copychar = *src; 328 } 329 } 330 /* 331 * Now that the switch has determined what (if anything) 332 * needs to be copied, copy whatever that is to *dest. 333 */ 334 if (copychar || copystr != NULL) { 335 if (!in_arg) { 336 /* This is the first byte of a new argument */ 337 *nextarg++ = dest; 338 addcount++; 339 in_arg = 1; 340 } 341 if (copychar) 342 *dest++ = (char)copychar; 343 else if (copystr != NULL) 344 while (*copystr != '\0') 345 *dest++ = *copystr++; 346 } else if (found_sep) { 347 *dest++ = '\0'; 348 while (isspacech(*src)) 349 src++; 350 --src; 351 in_arg = 0; 352 } 353 } 354 str_done: 355 *dest = '\0'; 356 *nextarg = NULL; 357 if (in_dq || in_sq) { 358 errx(1, "No terminating quote for string: %.*s%s", 359 bq_destlen, *(nextarg - 1), bq_src); 360 } 361 if (env_verbosity > 1) { 362 fprintf(stderr, "#env split -S:\t'%s'\n", str); 363 oldarg = newargv + 1; 364 fprintf(stderr, "#env into:\t'%s'\n", *oldarg); 365 for (oldarg++; *oldarg; oldarg++) 366 fprintf(stderr, "#env &\t'%s'\n", *oldarg); 367 } 368 369 /* Copy the unprocessed arg-pointers from the original array */ 370 for (oldarg = *origv + *origind; *oldarg; oldarg++) 371 *nextarg++ = *oldarg; 372 *nextarg = NULL; 373 374 /* Update optind/argc/argv in the calling routine */ 375 *origc += addcount - *origind + 1; 376 *origv = newargv; 377 *origind = 1; 378 } 379 380 /** 381 * Routine to split expand any environment variables referenced in the string 382 * that -S is processing. For now it only supports the form ${VARNAME}. It 383 * explicitly does not support $VARNAME, and obviously can not handle special 384 * shell-variables such as $?, $*, $1, etc. It is called with *src_p pointing 385 * at the initial '$', and if successful it will update *src_p, *dest_p, and 386 * possibly *thisarg_p in the calling routine. 387 */ 388 static const char * 389 expand_vars(int in_thisarg, char **thisarg_p, char **dest_p, const char **src_p) 390 { 391 const char *vbegin, *vend, *vvalue; 392 char *newstr, *vname; 393 int bad_reference; 394 size_t namelen, newlen; 395 396 bad_reference = 1; 397 vbegin = vend = (*src_p) + 1; 398 if (*vbegin++ == '{') 399 if (*vbegin == '_' || isalphach(*vbegin)) { 400 vend = vbegin + 1; 401 while (*vend == '_' || isalnumch(*vend)) 402 vend++; 403 if (*vend == '}') 404 bad_reference = 0; 405 } 406 if (bad_reference) 407 errx(1, "Only ${VARNAME} expansion is supported, error at: %s", 408 *src_p); 409 410 /* 411 * We now know we have a valid environment variable name, so update 412 * the caller's source-pointer to the last character in that reference, 413 * and then pick up the matching value. If the variable is not found, 414 * or if it has a null value, then our work here is done. 415 */ 416 *src_p = vend; 417 namelen = vend - vbegin + 1; 418 vname = malloc(namelen); 419 strlcpy(vname, vbegin, namelen); 420 vvalue = getenv(vname); 421 if (vvalue == NULL || *vvalue == '\0') { 422 if (env_verbosity > 2) 423 fprintf(stderr, 424 "#env replacing ${%s} with null string\n", 425 vname); 426 free(vname); 427 return (NULL); 428 } 429 430 if (env_verbosity > 2) 431 fprintf(stderr, "#env expanding ${%s} into '%s'\n", vname, 432 vvalue); 433 434 /* 435 * There is some value to copy to the destination. If the value is 436 * shorter than the ${VARNAME} reference that it replaces, then our 437 * caller can just copy the value to the existing destination. 438 */ 439 if (strlen(vname) + 3 >= strlen(vvalue)) { 440 free(vname); 441 return (vvalue); 442 } 443 444 /* 445 * The value is longer than the string it replaces, which means the 446 * present destination area is too small to hold it. Create a new 447 * destination area, and update the caller's 'dest' variable to match. 448 * If the caller has already started copying some info for 'thisarg' 449 * into the present destination, then the new destination area must 450 * include a copy of that data, and the pointer to 'thisarg' must also 451 * be updated. Note that it is still the caller which copies this 452 * vvalue to the new *dest. 453 */ 454 newlen = strlen(vvalue) + strlen(*src_p) + 1; 455 if (in_thisarg) { 456 **dest_p = '\0'; /* Provide terminator for 'thisarg' */ 457 newlen += strlen(*thisarg_p); 458 newstr = malloc(newlen); 459 strcpy(newstr, *thisarg_p); 460 *thisarg_p = newstr; 461 } else { 462 newstr = malloc(newlen); 463 *newstr = '\0'; 464 } 465 *dest_p = strchr(newstr, '\0'); 466 free(vname); 467 return (vvalue); 468 } 469