2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A#pragma ident "%Z%%M% %I% %E% SMI"
2N/A
2N/A#include "lint.h"
2N/A#include "mtlib.h"
2N/A#include <string.h>
2N/A#include <syslog.h>
2N/A#include <sys/stat.h>
2N/A#include <fcntl.h>
2N/A#include <limits.h>
2N/A#include <unistd.h>
2N/A#include <stdlib.h>
2N/A#include <thread.h>
2N/A#include <synch.h>
2N/A#include <ctype.h>
2N/A#include <errno.h>
2N/A#include "libc.h"
2N/A#include "nlspath_checks.h"
2N/A
2N/Aextern const char **_environ;
2N/A
2N/A/*
2N/A * We want to prevent the use of NLSPATH by setugid applications but
2N/A * not completely. CDE depends on this very much.
2N/A * Yes, this is ugly.
2N/A */
2N/A
2N/Astruct trusted_systemdirs {
2N/A const char *dir;
2N/A size_t dirlen;
2N/A};
2N/A
2N/A#define _USRLIB "/usr/lib/"
2N/A#define _USRDT "/usr/dt/"
2N/A#define _USROW "/usr/openwin/"
2N/A
2N/Astatic const struct trusted_systemdirs prefix[] = {
2N/A { _USRLIB, sizeof (_USRLIB) - 1 },
2N/A { _USRDT, sizeof (_USRDT) - 1 },
2N/A { _USROW, sizeof (_USROW) - 1 },
2N/A { NULL, 0 }
2N/A};
2N/A
2N/Astatic int8_t nlspath_safe;
2N/A
2N/A/*
2N/A * Routine to check the safety of a messages file.
2N/A * When the program specifies a pathname and doesn't
2N/A * use NLSPATH, it should specify the "safe" flag as 1.
2N/A * Most checks will be disabled then.
2N/A * fstat64 is done here and the stat structure is returned
2N/A * to prevent duplication of system calls.
2N/A *
2N/A * The trust return value contains an indication of
2N/A * trustworthiness (i.e., does check_format need to be called or
2N/A * not)
2N/A */
2N/A
2N/Aint
2N/Anls_safe_open(const char *path, struct stat64 *statbuf, int *trust, int safe)
2N/A{
2N/A int fd;
2N/A int trust_path;
2N/A int systemdir = 0;
2N/A int abs_path = 0;
2N/A int trust_owner = 0;
2N/A int trust_group = 0;
2N/A const struct trusted_systemdirs *p;
2N/A
2N/A /*
2N/A * If SAFE_F has been specified or NLSPATH is safe (or not set),
2N/A * set trust_path and trust the file as an initial value.
2N/A */
2N/A trust_path = *trust = safe || nlspath_safe;
2N/A
2N/A fd = open(path, O_RDONLY);
2N/A
2N/A if (fd < 0)
2N/A return (-1);
2N/A
2N/A if (fstat64(fd, statbuf) == -1) {
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Trust only files owned by root or bin (uid 2), except
2N/A * when specified as full path or when NLSPATH is known to
2N/A * be safe.
2N/A * Don't trust files writable by other or writable
2N/A * by non-bin, non-root system group.
2N/A * Don't trust these files even if the path is correct.
2N/A * Since we don't support changing uids/gids on our files,
2N/A * we hardcode them here for now.
2N/A */
2N/A
2N/A /*
2N/A * if the path is absolute and does not contain "/../",
2N/A * set abs_path.
2N/A */
2N/A if (*path == '/' && strstr(path, "/../") == NULL) {
2N/A abs_path = 1;
2N/A /*
2N/A * if the path belongs to the trusted system directory,
2N/A * set systemdir.
2N/A */
2N/A for (p = prefix; p->dir; p++) {
2N/A if (strncmp(p->dir, path, p->dirlen) == 0) {
2N/A systemdir = 1;
2N/A break;
2N/A }
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * If the owner is root or bin, set trust_owner.
2N/A */
2N/A if (statbuf->st_uid == 0 || statbuf->st_uid == 2) {
2N/A trust_owner = 1;
2N/A }
2N/A /*
2N/A * If the file is neither other-writable nor group-writable by
2N/A * non-bin and non-root system group, set trust_group.
2N/A */
2N/A if ((statbuf->st_mode & (S_IWOTH)) == 0 &&
2N/A ((statbuf->st_mode & (S_IWGRP)) == 0 ||
2N/A (statbuf->st_gid < 4 && statbuf->st_gid != 1))) {
2N/A trust_group = 1;
2N/A }
2N/A
2N/A /*
2N/A * Even if UNSAFE_F has been specified and unsafe-NLSPATH
2N/A * has been set, trust the file as long as it belongs to
2N/A * the trusted system directory.
2N/A */
2N/A if (!*trust && systemdir) {
2N/A *trust = 1;
2N/A }
2N/A
2N/A /*
2N/A * If:
2N/A * file is not a full pathname,
2N/A * or
2N/A * neither trust_owner nor trust_path is set,
2N/A * or
2N/A * trust_group is not set,
2N/A * untrust it.
2N/A */
2N/A if (*trust &&
2N/A (!abs_path || (!trust_owner && !trust_path) || !trust_group)) {
2N/A *trust = 0;
2N/A }
2N/A
2N/A /*
2N/A * If set[ug]id process, open for the untrusted file should fail.
2N/A * Otherwise, the message extracted from the untrusted file
2N/A * will have to be checked by check_format().
2N/A */
2N/A if (issetugid()) {
2N/A if (!*trust) {
2N/A /*
2N/A * Open should fail
2N/A */
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * if the path does not belong to the trusted system directory
2N/A * or if the owner is neither root nor bin, untrust it.
2N/A */
2N/A if (!systemdir || !trust_owner) {
2N/A *trust = 0;
2N/A }
2N/A }
2N/A
2N/A return (fd);
2N/A}
2N/A
2N/A/*
2N/A * Extract a format into a normalized format string.
2N/A * Returns the number of arguments converted, -1 on error.
2N/A * The string norm should contain 2N bytes; an upperbound is the
2N/A * length of the format string.
2N/A * The canonical format consists of two chars: one is the conversion
2N/A * character (s, c, d, x, etc), the second one is the option flag.
2N/A * L, ll, l, w as defined below.
2N/A * A special conversion character, '*', indicates that the argument
2N/A * is used as a precision specifier.
2N/A */
2N/A
2N/A#define OPT_L 0x01
2N/A#define OPT_l 0x02
2N/A#define OPT_ll 0x04
2N/A#define OPT_w 0x08
2N/A#define OPT_h 0x10
2N/A#define OPT_hh 0x20
2N/A#define OPT_j 0x40
2N/A
2N/A/* Number of bytes per canonical format entry */
2N/A#define FORMAT_SIZE 2
2N/A
2N/A/*
2N/A * Check and store the argument; allow each argument to be used only as
2N/A * one type even though printf allows multiple uses. The specification only
2N/A * allows one use, but we don't want to break existing functional code,
2N/A * even if it's buggy.
2N/A */
2N/A#define STORE(buf, size, arg, val) if (arg * FORMAT_SIZE + 1 >= size ||\
2N/A (strict ? \
2N/A (buf[arg*FORMAT_SIZE] != '\0' && \
2N/A buf[arg*FORMAT_SIZE] != val) \
2N/A : \
2N/A (buf[arg*FORMAT_SIZE] == 'n'))) \
2N/A return (-1); \
2N/A else {\
2N/A if (arg >= maxarg) \
2N/A maxarg = arg + 1; \
2N/A narg++; \
2N/A buf[arg*FORMAT_SIZE] = val; \
2N/A }
2N/A
2N/A/*
2N/A * This function extracts sprintf format into a canonical
2N/A * sprintf form. It's not as easy as just removing everything
2N/A * that isn't a format specifier, because of "%n$" specifiers.
2N/A * Ideally, this should be compatible with printf and not
2N/A * fail on bad formats.
2N/A * However, that makes writing a proper check_format that
2N/A * doesn't cause crashes a lot harder.
2N/A */
2N/A
2N/Astatic int
2N/Aextract_format(const char *fmt, char *norm, size_t sz, int strict)
2N/A{
2N/A int narg = 0;
2N/A int t, arg, argp;
2N/A int dotseen;
2N/A char flag;
2N/A char conv;
2N/A int lastarg = -1;
2N/A int prevarg;
2N/A int maxarg = 0; /* Highest index seen + 1 */
2N/A int lflag;
2N/A
2N/A (void) memset(norm, '\0', sz);
2N/A
2N/A#ifdef DEBUG
2N/A printf("Format \"%s\" canonical form: ", fmt);
2N/A#endif
2N/A
2N/A for (; *fmt; fmt++) {
2N/A if (*fmt == '%') {
2N/A if (*++fmt == '%')
2N/A continue;
2N/A
2N/A if (*fmt == '\0')
2N/A break;
2N/A
2N/A prevarg = lastarg;
2N/A arg = ++lastarg;
2N/A
2N/A t = 0;
2N/A while (*fmt && isdigit(*fmt))
2N/A t = t * 10 + *fmt++ - '0';
2N/A
2N/A if (*fmt == '$') {
2N/A lastarg = arg = t - 1;
2N/A fmt++;
2N/A }
2N/A
2N/A if (*fmt == '\0')
2N/A goto end;
2N/A
2N/A dotseen = 0;
2N/A flag = 0;
2N/A lflag = 0;
2N/Aagain:
2N/A /* Skip flags */
2N/A while (*fmt) {
2N/A switch (*fmt) {
2N/A case '\'':
2N/A case '+':
2N/A case '-':
2N/A case ' ':
2N/A case '#':
2N/A case '0':
2N/A fmt++;
2N/A continue;
2N/A }
2N/A break;
2N/A }
2N/A
2N/A while (*fmt && isdigit(*fmt))
2N/A fmt++;
2N/A
2N/A if (*fmt == '*') {
2N/A if (isdigit(fmt[1])) {
2N/A fmt++;
2N/A t = 0;
2N/A while (*fmt && isdigit(*fmt))
2N/A t = t * 10 + *fmt++ - '0';
2N/A
2N/A if (*fmt == '$') {
2N/A argp = t - 1;
2N/A STORE(norm, sz, argp, '*');
2N/A }
2N/A /*
2N/A * If digits follow a '*', it is
2N/A * not loaded as an argument, the
2N/A * digits are used instead.
2N/A */
2N/A } else {
2N/A /*
2N/A * Weird as it may seem, if we
2N/A * use an numbered argument, we
2N/A * get the next one if we have
2N/A * an unnumbered '*'
2N/A */
2N/A if (fmt[1] == '$')
2N/A fmt++;
2N/A else {
2N/A argp = arg;
2N/A prevarg = arg;
2N/A lastarg = ++arg;
2N/A STORE(norm, sz, argp, '*');
2N/A }
2N/A }
2N/A fmt++;
2N/A }
2N/A
2N/A /* Fail on two or more dots if we do strict checking */
2N/A if (*fmt == '.' || *fmt == '*') {
2N/A if (dotseen && strict)
2N/A return (-1);
2N/A dotseen = 1;
2N/A fmt++;
2N/A goto again;
2N/A }
2N/A
2N/A if (*fmt == '\0')
2N/A goto end;
2N/A
2N/A while (*fmt) {
2N/A switch (*fmt) {
2N/A case 'l':
2N/A if (!(flag & OPT_ll)) {
2N/A if (lflag) {
2N/A flag &= ~OPT_l;
2N/A flag |= OPT_ll;
2N/A } else {
2N/A flag |= OPT_l;
2N/A }
2N/A }
2N/A lflag++;
2N/A break;
2N/A case 'L':
2N/A flag |= OPT_L;
2N/A break;
2N/A case 'w':
2N/A flag |= OPT_w;
2N/A break;
2N/A case 'h':
2N/A if (flag & (OPT_h|OPT_hh))
2N/A flag |= OPT_hh;
2N/A else
2N/A flag |= OPT_h;
2N/A break;
2N/A case 'j':
2N/A flag |= OPT_j;
2N/A break;
2N/A case 'z':
2N/A case 't':
2N/A if (!(flag & OPT_ll)) {
2N/A flag |= OPT_l;
2N/A }
2N/A break;
2N/A case '\'':
2N/A case '+':
2N/A case '-':
2N/A case ' ':
2N/A case '#':
2N/A case '.':
2N/A case '*':
2N/A goto again;
2N/A default:
2N/A if (isdigit(*fmt))
2N/A goto again;
2N/A else
2N/A goto done;
2N/A }
2N/A fmt++;
2N/A }
2N/Adone:
2N/A if (*fmt == '\0')
2N/A goto end;
2N/A
2N/A switch (*fmt) {
2N/A case 'C':
2N/A flag |= OPT_l;
2N/A /* FALLTHROUGH */
2N/A case 'd':
2N/A case 'i':
2N/A case 'o':
2N/A case 'u':
2N/A case 'c':
2N/A case 'x':
2N/A case 'X':
2N/A conv = 'I';
2N/A break;
2N/A case 'e':
2N/A case 'E':
2N/A case 'f':
2N/A case 'F':
2N/A case 'a':
2N/A case 'A':
2N/A case 'g':
2N/A case 'G':
2N/A conv = 'D';
2N/A break;
2N/A case 'S':
2N/A flag |= OPT_l;
2N/A /* FALLTHROUGH */
2N/A case 's':
2N/A conv = 's';
2N/A break;
2N/A case 'p':
2N/A case 'n':
2N/A conv = *fmt;
2N/A break;
2N/A default:
2N/A lastarg = prevarg;
2N/A continue;
2N/A }
2N/A
2N/A STORE(norm, sz, arg, conv);
2N/A norm[arg*FORMAT_SIZE + 1] = flag;
2N/A }
2N/A }
2N/A#ifdef DEBUG
2N/A for (t = 0; t < maxarg * FORMAT_SIZE; t += FORMAT_SIZE) {
2N/A printf("%c(%d)", norm[t], norm[t+1]);
2N/A }
2N/A putchar('\n');
2N/A#endif
2N/Aend:
2N/A if (strict)
2N/A for (arg = 0; arg < maxarg; arg++)
2N/A if (norm[arg*FORMAT_SIZE] == '\0')
2N/A return (-1);
2N/A
2N/A return (maxarg);
2N/A}
2N/A
2N/Achar *
2N/Acheck_format(const char *org, const char *new, int strict)
2N/A{
2N/A char *ofmt, *nfmt, *torg;
2N/A size_t osz, nsz;
2N/A int olen, nlen;
2N/A
2N/A if (!org) {
2N/A /*
2N/A * Default message is NULL.
2N/A * dtmail uses NULL for default message.
2N/A */
2N/A torg = "(NULL)";
2N/A } else {
2N/A torg = (char *)org;
2N/A }
2N/A
2N/A /* Short cut */
2N/A if (org == new || strcmp(torg, new) == 0 ||
2N/A strchr(new, '%') == NULL)
2N/A return ((char *)new);
2N/A
2N/A osz = strlen(torg) * FORMAT_SIZE;
2N/A ofmt = malloc(osz);
2N/A if (ofmt == NULL)
2N/A return ((char *)org);
2N/A
2N/A olen = extract_format(torg, ofmt, osz, 0);
2N/A
2N/A if (olen == -1)
2N/A syslog(LOG_AUTH|LOG_INFO,
2N/A "invalid format in gettext argument: \"%s\"", torg);
2N/A
2N/A nsz = strlen(new) * FORMAT_SIZE;
2N/A nfmt = malloc(nsz);
2N/A if (nfmt == NULL) {
2N/A free(ofmt);
2N/A return ((char *)org);
2N/A }
2N/A
2N/A nlen = extract_format(new, nfmt, nsz, strict);
2N/A
2N/A if (nlen == -1) {
2N/A free(ofmt);
2N/A free(nfmt);
2N/A syslog(LOG_AUTH|LOG_NOTICE,
2N/A "invalid format in message file \"%.100s\" -> \"%s\"",
2N/A torg, new);
2N/A errno = EBADMSG;
2N/A return ((char *)org);
2N/A }
2N/A
2N/A if (strict && (olen != nlen || olen == -1)) {
2N/A free(ofmt);
2N/A free(nfmt);
2N/A syslog(LOG_AUTH|LOG_NOTICE,
2N/A "incompatible format in message file: \"%.100s\" != \"%s\"",
2N/A torg, new);
2N/A errno = EBADMSG;
2N/A return ((char *)org);
2N/A }
2N/A
2N/A if (strict && memcmp(ofmt, nfmt, nlen * FORMAT_SIZE) == 0) {
2N/A free(ofmt);
2N/A free(nfmt);
2N/A return ((char *)new);
2N/A } else {
2N/A if (!strict) {
2N/A char *n;
2N/A
2N/A nlen *= FORMAT_SIZE;
2N/A
2N/A for (n = nfmt; n = memchr(n, 'n', nfmt + nlen - n);
2N/A n++) {
2N/A int off = (n - nfmt);
2N/A
2N/A if (off >= olen * FORMAT_SIZE ||
2N/A ofmt[off] != 'n' ||
2N/A ofmt[off+1] != nfmt[off+1]) {
2N/A free(ofmt);
2N/A free(nfmt);
2N/A syslog(LOG_AUTH|LOG_NOTICE,
2N/A "dangerous format in message file: "
2N/A "\"%.100s\" -> \"%s\"", torg, new);
2N/A errno = EBADMSG;
2N/A return ((char *)org);
2N/A }
2N/A }
2N/A free(ofmt);
2N/A free(nfmt);
2N/A return ((char *)new);
2N/A }
2N/A free(ofmt);
2N/A free(nfmt);
2N/A syslog(LOG_AUTH|LOG_NOTICE,
2N/A "incompatible format in message file \"%.100s\" != \"%s\"",
2N/A torg, new);
2N/A errno = EBADMSG;
2N/A return ((char *)org);
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * s1 is either name, or name=value
2N/A * s2 is name=value
2N/A * if names match, return value of s2, else NULL
2N/A * used for environment searching: see getenv
2N/A */
2N/Aconst char *
2N/Anvmatch(const char *s1, const char *s2)
2N/A{
2N/A while (*s1 == *s2++)
2N/A if (*s1++ == '=')
2N/A return (s2);
2N/A if (*s1 == '\0' && *(s2-1) == '=')
2N/A return (s2);
2N/A return (NULL);
2N/A}
2N/A
2N/A/*
2N/A * Handle NLSPATH environment variables in the environment.
2N/A * This routine is hooked into getenv/putenv at first call.
2N/A *
2N/A * The intention is to ignore NLSPATH in set-uid applications,
2N/A * and determine whether the NLSPATH in an application was set
2N/A * by the applications or derived from the user's environment.
2N/A */
2N/A
2N/Avoid
2N/Aclean_env(void)
2N/A{
2N/A const char **p;
2N/A
2N/A if (_environ == NULL) {
2N/A /* can happen when processing a SunOS 4.x AOUT file */
2N/A nlspath_safe = 1;
2N/A return;
2N/A }
2N/A
2N/A /* Find the first NLSPATH occurrence */
2N/A for (p = _environ; *p; p++)
2N/A if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
2N/A break;
2N/A
2N/A if (!*p) /* None found, we're safe */
2N/A nlspath_safe = 1;
2N/A else if (issetugid()) { /* Found and set-uid, clean */
2N/A int off = 1;
2N/A
2N/A for (p++; (p[-off] = p[0]) != '\0'; p++)
2N/A if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
2N/A off++;
2N/A
2N/A nlspath_safe = 1;
2N/A }
2N/A}