1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 /*
  28  * This code is MKS code ported to Solaris originally with minimum
  29  * modifications so that upgrades from MKS would readily integrate.
  30  * The MKS basis for this modification was:
  31  *
  32  *      $Id: wordexp.c 1.22 1994/11/21 18:24:50 miked
  33  *
  34  * Additional modifications have been made to this code to make it
  35  * 64-bit clean.
  36  */
  37 
  38 /*
  39  * wordexp, wordfree -- POSIX.2 D11.2 word expansion routines.
  40  *
  41  * Copyright 1985, 1992 by Mortice Kern Systems Inc.  All rights reserved.
  42  * Modified by Roland Mainz <roland.mainz@nrubsig.org> to support ksh93.
  43  */
  44 
  45 #pragma weak _wordexp = wordexp
  46 #pragma weak _wordfree = wordfree
  47 
  48 #include "lint.h"
  49 #include <stdio.h>
  50 #include <unistd.h>
  51 #include <limits.h>
  52 #include <fcntl.h>
  53 #include <limits.h>
  54 #include <stdlib.h>
  55 #include <alloca.h>
  56 #include <string.h>
  57 #include <sys/wait.h>
  58 #include <pthread.h>
  59 #include <unistd.h>
  60 #include <wordexp.h>
  61 #include <stdio.h>
  62 #include <spawn.h>
  63 #include <errno.h>
  64 
  65 #define INITIAL 8               /* initial pathv allocation */
  66 #define BUFSZ   256             /* allocation unit of the line buffer */
  67 
  68 /*
  69  * Needs no locking if fetched only once.
  70  * See getenv()/putenv()/setenv().
  71  */
  72 extern  const char **_environ;
  73 
  74 /* Local prototypes */
  75 static int append(wordexp_t *, char *);
  76 
  77 /*
  78  * |mystpcpy| - like |strcpy()| but returns the end of the buffer
  79  * We'll add this later (and a matching multibyte/widechar version)
  80  * as normal libc function.
  81  *
  82  * Copy string s2 to s1.  s1 must be large enough.
  83  * return s1-1 (position of string terminator ('\0') in destination buffer).
  84  */
  85 static char *
  86 mystpcpy(char *s1, const char *s2)
  87 {
  88         while (*s1++ = *s2++)
  89                 ;
  90         return (s1-1);
  91 }
  92 
  93 /*
  94  * Do word expansion.
  95  * We build a mini-script in |buff| which takes care of all details,
  96  * including stdin/stdout/stderr redirection, WRDE_NOCMD mode and
  97  * the word expansion itself.
  98  */
  99 int
 100 wordexp(const char *word, wordexp_t *wp, int flags)
 101 {
 102         const char *path = "/usr/bin/ksh93";
 103         wordexp_t wptmp;
 104         size_t si;
 105         pid_t pid;
 106         char *line, *eob, *cp;          /* word from shell */
 107         int rv = WRDE_ERRNO;
 108         int status;
 109         int pv[2];                      /* pipe from shell stdout */
 110         FILE *fp;                       /* pipe read stream */
 111         int tmpalloc;
 112         char *wd = NULL;
 113         const char **env = NULL;
 114         const char **envp;
 115         const char *ev;
 116         int n;
 117         posix_spawnattr_t attr;
 118         posix_spawn_file_actions_t fact;
 119         int error;
 120         int cancel_state;
 121         size_t bufflen; /* Length of |buff| */
 122         char *buff;
 123         char *currbuffp; /* Current position of '\0' in |buff| */
 124         char *args[10];
 125         int i;
 126 
 127         /*
 128          * Do absolute minimum necessary for the REUSE flag. Eventually
 129          * want to be able to actually avoid excessive malloc calls.
 130          */
 131         if (flags & WRDE_REUSE)
 132                 wordfree(wp);
 133 
 134         /*
 135          * Initialize wordexp_t
 136          *
 137          * XPG requires that the struct pointed to by wp not be modified
 138          * unless wordexp() either succeeds, or fails on WRDE_NOSPACE.
 139          * So we work with wptmp, and only copy wptmp to wp if one of the
 140          * previously mentioned conditions is satisfied.
 141          */
 142         wptmp = *wp;
 143 
 144         /*
 145          * Man page says:
 146          * 2. All of the calls must set WRDE_DOOFFS, or all must not
 147          *    set it.
 148          * Therefore, if it's not set, we_offs will always be reset.
 149          */
 150         if ((flags & WRDE_DOOFFS) == 0)
 151                 wptmp.we_offs = 0;
 152 
 153         /*
 154          * If we get APPEND|REUSE, how should we do?
 155          * allocating buffer anyway to avoid segfault.
 156          */
 157         tmpalloc = 0;
 158         if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) {
 159                 wptmp.we_wordc = 0;
 160                 wptmp.we_wordn = wptmp.we_offs + INITIAL;
 161                 wptmp.we_wordv = malloc(sizeof (char *) * wptmp.we_wordn);
 162                 if (wptmp.we_wordv == NULL)
 163                         return (WRDE_NOSPACE);
 164                 wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs;
 165                 for (si = 0; si < wptmp.we_offs; si++)
 166                         wptmp.we_wordv[si] = NULL;
 167                 tmpalloc = 1;
 168         }
 169 
 170         /*
 171          * The UNIX98 Posix conformance test suite requires
 172          * |wordexp()| to not be a cancellation point.
 173          */
 174         (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
 175 
 176         /*
 177          * Make sure PWD is in the environment.
 178          */
 179         if ((envp = _environ) == NULL) {
 180                 /* can happen when processing a SunOS 4.x AOUT file */
 181                 ev = NULL;
 182                 n = 0;
 183         } else {
 184                 for (n = 0; (ev = envp[n]) != NULL; n++) {
 185                         if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0)
 186                                 break;
 187                 }
 188         }
 189         if (ev == NULL) {       /* PWD missing from the environment */
 190                 /* allocate a new environment */
 191                 if ((env = malloc((n + 2) * sizeof (char *))) == NULL ||
 192                     (wd = malloc(PATH_MAX + 4)) == NULL)
 193                         goto cleanup;
 194                 for (i = 0; i < n; i++)
 195                         env[i] = envp[i];
 196                 (void) strcpy(wd, "PWD=");
 197                 if (getcwd(&wd[4], PATH_MAX) == NULL)
 198                         (void) strcpy(&wd[4], "/");
 199                 env[i] = wd;
 200                 env[i + 1] = NULL;
 201                 envp = env;
 202         }
 203 
 204         /*
 205          * Calculate size of required buffer (which is size of the
 206          * input string (|word|) plus all string literals below;
 207          * this value MUST be adjusted each time the literals are
 208          * changed!!).
 209          */
 210         bufflen = 165 + strlen(word);
 211         buff = alloca(bufflen);
 212         i = 0;
 213 
 214         /* Start filling the buffer */
 215         buff[0] = '\0';
 216         currbuffp = buff;
 217 
 218         if (flags & WRDE_UNDEF)
 219                 currbuffp = mystpcpy(currbuffp, "set -o nounset\n");
 220         if ((flags & WRDE_SHOWERR) == 0) {
 221                 /*
 222                  * The newline ('\n') is neccesary to make sure that
 223                  * the redirection to /dev/null is already active in
 224                  * the case the printf below contains a syntax
 225                  * error...
 226                  */
 227                 currbuffp = mystpcpy(currbuffp, "exec 2>/dev/null\n");
 228         }
 229         /* Squish stdin */
 230         currbuffp = mystpcpy(currbuffp, "exec 0</dev/null\n");
 231 
 232         if (flags & WRDE_NOCMD) {
 233                 /*
 234                  * Switch to restricted shell (rksh) mode here to
 235                  * put the word expansion into a "cage" which
 236                  * prevents users from executing external commands
 237                  * (outside those listed by ${PATH} (which we set
 238                  * explicitly to /usr/no/such/path/element/)).
 239                  */
 240                 currbuffp = mystpcpy(currbuffp,
 241                     "export PATH=/usr/no/such/path/element/ ; "
 242                     "set -o restricted\n");
 243         }
 244 
 245         (void) snprintf(currbuffp, bufflen,
 246             "print -f '%%s\\000' -- %s", word);
 247 
 248         args[i++] = strrchr(path, '/') + 1;
 249         args[i++] = "-c";
 250         args[i++] = buff;
 251         args[i++] = NULL;
 252 
 253         if ((error = posix_spawnattr_init(&attr)) != 0) {
 254                 errno = error;
 255                 goto cleanup;
 256         }
 257         if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
 258                 (void) posix_spawnattr_destroy(&attr);
 259                 errno = error;
 260                 goto cleanup;
 261         }
 262 
 263         /*
 264          * Set up pipe from shell stdout to "fp" for us
 265          */
 266         if (pipe(pv) < 0) {
 267                 error = errno;
 268                 (void) posix_spawnattr_destroy(&attr);
 269                 (void) posix_spawn_file_actions_destroy(&fact);
 270                 errno = error;
 271                 goto cleanup;
 272         }
 273 
 274         /*
 275          * Spawn shell
 276          */
 277         error = posix_spawnattr_setflags(&attr,
 278             POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
 279         if (error == 0)
 280                 error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1);
 281         if (error == 0 && pv[0] != 1)
 282                 error = posix_spawn_file_actions_addclose(&fact, pv[0]);
 283         if (error == 0 && pv[1] != 1)
 284                 error = posix_spawn_file_actions_addclose(&fact, pv[1]);
 285         if (error == 0 && !(flags & WRDE_SHOWERR))
 286                 error = posix_spawn_file_actions_addopen(&fact, 2,
 287                     "/dev/null", O_WRONLY, 0);
 288 
 289         if (error == 0)
 290                 error = posix_spawn(&pid, path, &fact, &attr,
 291                     (char *const *)args, (char *const *)envp);
 292         (void) posix_spawnattr_destroy(&attr);
 293         (void) posix_spawn_file_actions_destroy(&fact);
 294         (void) close(pv[1]);
 295         if (error) {
 296                 (void) close(pv[0]);
 297                 errno = error;
 298                 goto cleanup;
 299         }
 300 
 301         if ((fp = fdopen(pv[0], "rF")) == NULL) {
 302                 error = errno;
 303                 (void) close(pv[0]);
 304                 errno = error;
 305                 goto wait_cleanup;
 306         }
 307 
 308         /*
 309          * Read words from shell, separated with '\0'.
 310          * Since there is no way to disable IFS splitting,
 311          * it would be possible to separate the output with '\n'.
 312          */
 313         cp = line = malloc(BUFSZ);
 314         if (line == NULL) {
 315                 error = errno;
 316                 (void) fclose(fp);
 317                 errno = error;
 318                 goto wait_cleanup;
 319         }
 320         eob = line + BUFSZ;
 321 
 322         rv = 0;
 323         flockfile(fp);
 324         while ((i = getc_unlocked(fp)) != EOF) {
 325                 *cp++ = (char)i;
 326                 if (i == '\0') {
 327                         cp = line;
 328                         if ((rv = append(&wptmp, cp)) != 0) {
 329                                 break;
 330                         }
 331                 }
 332                 if (cp == eob) {
 333                         size_t bs = (eob - line);
 334                         char *nl;
 335 
 336                         if ((nl = realloc(line, bs + BUFSZ)) == NULL) {
 337                                 rv = WRDE_NOSPACE;
 338                                 break;
 339                         }
 340                         line = nl;
 341                         cp = line + bs;
 342                         eob = cp + BUFSZ;
 343                 }
 344         }
 345         funlockfile(fp);
 346 
 347         wptmp.we_wordp[wptmp.we_wordc] = NULL;
 348 
 349         free(line);
 350         (void) fclose(fp);      /* kill shell if still writing */
 351 
 352 wait_cleanup:
 353         while (waitpid(pid, &status, 0) == -1) {
 354                 if (errno != EINTR) {
 355                         if (rv == 0)
 356                                 rv = WRDE_ERRNO;
 357                         break;
 358                 }
 359         }
 360         if (rv == 0)
 361                 rv = WEXITSTATUS(status); /* shell WRDE_* status */
 362 
 363 cleanup:
 364         if (rv == 0)
 365                 *wp = wptmp;
 366         else if (tmpalloc)
 367                 wordfree(&wptmp);
 368 
 369         if (env)
 370                 free(env);
 371         if (wd)
 372                 free(wd);
 373         /*
 374          * Map ksh93 errors to |wordexp()| errors
 375          */
 376         switch (rv) {
 377                 case 1:
 378                         rv = WRDE_BADVAL;
 379                         break;
 380                 case 127:
 381                         rv = WRDE_BADCHAR;
 382                         break;
 383         }
 384 
 385         (void) pthread_setcancelstate(cancel_state, NULL);
 386         return (rv);
 387 }
 388 
 389 /*
 390  * Append a word to the wordexp_t structure, growing it as necessary.
 391  */
 392 static int
 393 append(wordexp_t *wp, char *str)
 394 {
 395         char *cp;
 396         char **nwp;
 397 
 398         /*
 399          * We will be adding one entry and later adding
 400          * one more NULL. So we need 2 more free slots.
 401          */
 402         if ((wp->we_wordp + wp->we_wordc) ==
 403             (wp->we_wordv + wp->we_wordn - 1)) {
 404                 nwp = realloc(wp->we_wordv,
 405                     (wp->we_wordn + INITIAL) * sizeof (char *));
 406                 if (nwp == NULL)
 407                         return (WRDE_NOSPACE);
 408                 wp->we_wordn += INITIAL;
 409                 wp->we_wordv = nwp;
 410                 wp->we_wordp = wp->we_wordv + wp->we_offs;
 411         }
 412         if ((cp = strdup(str)) == NULL)
 413                 return (WRDE_NOSPACE);
 414         wp->we_wordp[wp->we_wordc++] = cp;
 415         return (0);
 416 }
 417 
 418 /*
 419  * Free all space owned by wordexp_t.
 420  */
 421 void
 422 wordfree(wordexp_t *wp)
 423 {
 424         size_t i;
 425 
 426         if (wp->we_wordv == NULL)
 427                 return;
 428         for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++)
 429                 free(wp->we_wordv[i]);
 430         free((void *)wp->we_wordv);
 431         wp->we_wordc = 0;
 432         wp->we_wordv = NULL;
 433 }