tripwire-open-source/src/core/msystem.cpp

1044 lines
25 KiB
C++

//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc. All rights
// reserved.
//
// This program is free software. The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version. You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
//
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE. Please see the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
//
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
/* LINTLIBRARY */
/*
* This is the file with all the library routines in it
*
* Author information:
* Matt Bishop
* Department of Computer Science
* University of California at Davis
* Davis, CA 95616-8562
* phone (916) 752-8060
* email bishop@cs.ucdavis.edu
*
* This code is placed in the public domain. I do ask that
* you keep my name associated with it, that you not represent
* it as written by you, and that you preserve these comments.
* This software is provided "as is" and without any guarantees
* of any sort.
*
* Version information:
* 1.0 May 25, 1994 Matt Bishop
* 1.1 July 5, 1994 Matt Bishop
* added TZ to the list of environment variables to be
* passed on by default; you get what the environment
* gives you (as required by System V based systems)
* 1.2 October 4, 1994 Matt Bishop
* added mxfpopen, mxfpclose; also cleaned up le_set(),
* in that before if you added a predefined environment
* variable as the first variable, it would process it,
* initialize the environment list (first call), and
* then append the name; now if le_set() is called, it
* initializes the environment and then does the checking
* 1.3 October 31, 1994 Matt Bishop
* made the globals static for better modularity
*/
#include "stdcore.h"
#if IS_UNIX
/*
* set, reset environment to be passed to mpopem
*/
#include "config.h"
#include <stdio.h>
#include <sys/types.h>
#if !IS_SORTIX
# include <sys/file.h>
#endif
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#if defined(HAVE_MALLOC_H)
#include <malloc.h>
#endif
#include <string.h>
//#include <signal.h>
#include "tw_signal.h"
#ifdef __STDC__
# include <unistd.h>
# include <stdlib.h>
#endif
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#include <assert.h>
#include "msystem.h"
/*
* signal type
*/
#ifndef SIG_TYPE
# define SIG_TYPE void
#endif
/*
* define error message printer
*/
#define ERMSG(x) if (le_verbose){ \
(void) fprintf(stderr, "SE internal error: ");\
(void) fprintf(stderr, "%s(%d): %s\n", \
__FILE__, __LINE__-4, x); \
}
/*
* define limits
*
* for the popen/pclose clones, we need to store PIDs in an array;
* how big should it be? answer: since each popen call requires 1
* pipe, it can only be as big as the maximim number of pipes allowed
* that number is MAX_MPOPEN
*/
#ifndef MAX_MPOPEN
# define MAX_MPOPEN 20
#endif
/*
* all environment variable arrays are dynamically allocated; if they are
* too small, they grow by PTR_INC to accommodate the new variable
* changing this just causes more (or less) allocations; it's an efficiency
* consideration, not a security or system one
*/
#define PTR_INC 1024 /* how much to increment pointer arrays */
/*
* this is the maximum number of signals; we use NSIG if that's
* defined, otherwise 32 (which seems to be right for most systems)
*/
#ifdef NSIG
# define MAX_SIGNAL NSIG
#else
# define MAX_SIGNAL 32
#endif
/*
* this is the maximum number of file descriptors (NFILES if
* defined, otherwise 256 (which seems to be right for most systems)
*/
#ifdef NFILES
# define MAX_DESC NFILES
#else
# define MAX_DESC 256
#endif
#ifdef NOFILE
# define TW_NO_DESC NOFILE
#else
# define TW_NO_DESC MAX_DESC+1
#endif
/*
* in case the subprocess fails to exec the command properly
*/
#define EXIT_BAD -1 /* oops! */
/*
* now, the environment
*
* the default environment; you get the bare bones here.
* to add to it, just stick the new environment variables
* at the end of the array; the program does the rest automatically
*/
const char* nvfix[] = { /* these MUST be set or reset */
DEF_PATH, /* a safe path */
DEF_SHELL, /* a safe shell */
DEF_IFS, /* a safe IFS */
DEF_TZ, /* the current time zone */
NULL /* add new ones here */
};
#define SZ_NVFIX (sizeof(nvfix)/sizeof(char *)) /* size of nvfix */
static int octmask = DEF_UMASK; /* default umask */
static int mresetgid = UID_RESET; /* reset EGID to RGID by default */
static int mresetuid = GID_RESET; /* reset EUID to RUID by default */
static int fdleave[MAX_DESC]; /* 1 to keep file descriptor open */
static char **envp = NULL; /* environment passed to child */
static int sz_envp = 0;
static int nend = 0; /* # entries in envp */
static int le_verbose = 1; /* 1 to print error messages */
/*
* structure for malloc
*/
union xyzzy {
char **cpp; /* doubly-indirect pointer */
#ifdef __STDC__
void *vp; /* generic pointer */
#else
char *vp; /* generic pointer */
#endif
}; /* used to cast malloc properly */
/*
* library functions
*/
#ifndef __STDC__
char *getenv(); /* get variable from environment */
#endif
/************* U T I L I T Y F U N C T I O N S *******************/
/*
* string duplication into private memory
* on some systems, this is a library function, so define STRDUP
* if it is on yours
*/
#ifdef STRDUP
# ifndef __STDC__
char *strdup();
# endif
#else
# ifdef __STDC__
static char *strdup(char* str)
# else
static char *strdup(str)
char *str;
# endif
{
register char *p; /* temp pointer */
/*
* allocate space for the string, and copy if successful
*/
if ((p = (char*)malloc((unsigned)((strlen(str)+1)*sizeof(char))))
!= NULL)
(void) strcpy(p, str);
return(p);
}
#endif
/*
* allocate space for an array of pointers, OR
* (if space already allocated) increase the allocation by PTR_INC
*/
#ifdef __STDC__
static char **c2alloc(const char**old, int *sz_alloc)
#else
static char **c2alloc(old, sz_alloc)
char **old;
int *sz_alloc;
#endif
{
register int i; /* counter in a for loop */
union xyzzy x; /* used to cast malloc properly */
/*
* allocate space for the new (expanded) array
*/
x.vp = malloc((unsigned) ((*sz_alloc + PTR_INC) * sizeof(char *)));
if (x.vp != NULL){
/* success! copy the old and free it, if appropriate */
if (old != NULL){
for(i = 0; i < *sz_alloc; i++)
x.cpp[i] = (char*)old[i];
x.cpp = (char**)old;
(void) free(x.vp);
}
/* now have PTR_INC more room */
*sz_alloc += PTR_INC;
}
/*
* return pointer to new space
*/
return(x.cpp);
}
#ifdef __STDC__
static int initenv(void)
#else
static int initenv()
#endif
{
register int i;
register int rval;
if (envp != NULL)
le_clobber();
for(i = 0; nvfix[i] != NULL; i++)
if ((rval = le_set(nvfix[i])) != SE_NONE)
return(rval);
return(SE_NONE);
}
/************* E N V I R O N M E N T C O N T R O L *******************/
/*
* clobber the internal environment
*/
#ifdef __STDC__
void le_clobber(void)
#else
void le_clobber()
#endif
{
register int i; /* counter in a for loop */
union {
char **ep;
char *p;
} x;
/*
* if the environment is defined and not fixed, clobber it
*/
if (envp != NULL){
/* it's defined -- is it fixed? */
if (envp != (char**)nvfix){
/* no -- usual walk the list crud */
for(i = 0; envp[i] != NULL; i++)
(void) free(envp[i]);
x.ep = envp;
(void) free(x.p);
}
/* say there's not anything there any more */
envp = NULL;
}
/*
* now clobber the sizes
*/
nend = sz_envp = 0;
}
/*
* get a pointer to the environment element
*/
#ifdef __STDC__
static int le_getenv(const char* var)
#else
static int le_getenv(var)
char *var;
#endif
{
register int i; /* counter in a for loop */
register char *p, *q; /* used to compare two strings */
/*
* check for no environment
*/
if (envp == NULL)
return(-1);
/*
* there is one -- now walk the environment list
*/
for(i = 0; envp[i] != NULL; i++){
/* compare */
p = envp[i];
q = (char*)var;
while(*p && *q && *p == *q)
p++, q++;
/* have we a match? */
if ((*p == '=' || *p == '\0') && (*q == '=' || *q == '\0')){
/* YES -- return its index */
return(i);
}
}
/*
* no match
*/
return(-1);
}
/*
* set an environment variable
*/
#ifdef __STDC__
int le_set(const char* env)
#else
int le_set(env)
char *env;
#endif
{
register char *p, *q; /* what is to be put into env */
register int n; /* where a previous definition is */
/*
* seeif youneed to create the environment list
*/
if (sz_envp == 0){
if ((envp = c2alloc((const char**)envp, &sz_envp)) == NULL){
ERMSG("ran out of memory");
return(SE_NOMEM);
}
for(nend = 0; nvfix[nend] != NULL; nend++)
if ((envp[nend] = strdup(nvfix[nend])) == NULL){
ERMSG("ran out of memory");
return(SE_NOMEM);
}
envp[nend] = NULL;
}
/*
* if there is an = sign,
* it's a redefinition; if not,
* just include it from the current environment
* (if not defined there, don't define it here)
*/
if (strchr(env, '=') == NULL){
/* is it defined locally? */
if ((q = getenv(env)) == NULL){
/* no -- don't define it here */
return(SE_NONE);
}
else if ((p = (char*)malloc((unsigned) (strlen(env)+strlen(q)+2)))
== NULL){
ERMSG("ran out of memory");
return(SE_NOMEM);
}
else{
(void) strcpy(p, env);
(void) strcat(p, "=");
(void) strcat(p, q);
}
}
else if ((p = strdup(env)) == NULL){
ERMSG("ran out of memory");
return(SE_NOMEM);
}
/*
* if it isn't defined, see if you need to create the environment list
*/
if (nend == sz_envp && (envp = c2alloc((const char**)envp, &sz_envp)) == NULL){
ERMSG("ran out of memory");
return(SE_NOMEM);
}
/*
* add it to the environment
* if it is already defined, delete the old definition
* and replace it with the new definition
*/
if ((n = le_getenv(env)) > -1){
(void) free(envp[n]);
envp[n] = p;
return(SE_NONE);
}
envp[nend++] = p;
envp[nend] = NULL;
/*
* all done
*/
return(SE_NONE);
}
/*
* clear a current environment variable
*/
#ifdef __STDC__
int le_unset(const char* env)
#else
int le_unset(env)
char *env;
#endif
{
register int i; /* counter in a for loop */
/*
* delete it from the environment
*/
if ((i = le_getenv(env)) > -1){
(void) free(envp[i]);
for( ; envp[i] != NULL; i++)
envp[i] = envp[i+1];
return(SE_NONE);
}
/*
* no such variable
*/
return(SE_NOVAR);
}
/*
* set the default umask
*/
#ifdef __STDC__
int le_umask(int umak)
#else
int le_umask(umak)
int umak;
#endif
{
/*
* reset the umask
*/
octmask = umak;
return(SE_NONE);
}
/*
* leave a file descriptor open
*/
#ifdef __STDC__
int le_openfd(int fd)
#else
int le_openfd(fd)
int fd;
#endif
{
/*
* check args
*/
if (0 > fd || fd >= MAX_DESC)
return(SE_BADFD);
/*
* mark the descriptor for leaving open
*/
fdleave[fd] = 1;
return(SE_NONE);
}
/*
* mark a file descriptor closed
*/
#ifdef __STDC__
int le_closefd(int fd)
#else
int le_closefd(fd)
int fd;
#endif
{
/*
* check args
*/
if (0 > fd || fd >= MAX_DESC)
return(SE_BADFD);
/*
* mark the descriptor for closing
*/
fdleave[fd] = 0;
return(SE_NONE);
}
/************* P R I V I L E G E C O N T R O L *******************/
/*
* say how to handle the effective (and real) UIDs
*/
#ifdef __STDC__
int le_euid(int uid)
#else
int le_euid( uid)
int uid;
#endif
{
mresetuid = uid;
return(SE_NONE);
}
/*
* say how to handle the effective (and real) GIDs
*/
#ifdef __STDC__
int le_egid(int gid)
#else
int le_egid(gid)
int gid;
#endif
{
mresetgid = gid;
return(SE_NONE);
}
/************* S U B C O M M A N D E X E C U T I O N *******************/
/*
* get the shell to use for the subcommand
*/
#ifdef __STDC__
static const char *shellenv(void)
#else
static const char *shellenv()
#endif
{
register int i; /* counter in a for loop */
register const char *shptr; /* points to shell name */
/*
* error check; should never happen
*/
if (envp == NULL && (i = initenv()) != SE_NONE)
return(NULL);
/*
* get the shell environment variable
*/
for(i = 0; envp[i] != NULL; i++)
if (strncmp(envp[i], "SHELL=", strlen("SHELL=")) == 0)
break;
/*
* not defined; use the default shell
*/
if (envp[i] == NULL)
shptr = NOSHELL;
else
shptr = strchr(envp[i], '=') + 1;
return(shptr);
}
/*
* like system but A LOT safer
*/
#ifdef __STDC__
int msystem(const char* cmd)
#else
int msystem(cmd)
char *cmd;
#endif
{
const char *argv[5]; /* argument list */
register const char *p; /* temoporary pointers */
register const char* shptr; /* the program to be run */
register int i; /* index number of child */
/*
* if it's NULL, initialize it
*/
if (envp == NULL && (i = initenv()) != SE_NONE)
return(i);
/*
* get the SHELL variable (if any)
*/
shptr = shellenv();
/*
* set it up, just like popen
*/
argv[0] = ((p = strrchr(shptr, '/')) == NULL) ? shptr : p+1;
argv[1] = "-c";
argv[2] = cmd;
argv[3] = NULL;
/*
* run it
*/
if ((i = schild(shptr, (const char**)argv, (const char**)envp, (FILE **) NULL, octmask)) < 0)
return(127);
return(echild(i));
}
/*
* this structure holds the information associating
* file descriptors and PIDs. It ks needed as the mpopen/mpclose interface
* uses file pointers but the wait call needs a PID
*/
static struct popenfunc { /* association of pid, file pointer */
int pid; /* the process identifier */
FILE *fp; /* the file pointer */
} pfunc[MAX_MPOPEN];
/*
* like popen but A LOT safer
*/
#ifdef __STDC__
FILE *mpopen(const char* cmd, const char* mode)
#else
FILE *mpopen(cmd, mode)
char *cmd;
char *mode;
#endif
{
const char *argv[5]; /* argument list */
register const char *p; /* temoporary pointers */
register const char *shptr; /* the program to be run */
FILE *fpa[3]; /* process communication descriptors */
register int indx; /* index number of child */
/*
* see if anything is available
*/
for(indx = 0; indx < MAX_MPOPEN; indx++)
if (pfunc[indx].pid == 0)
break;
if (indx == MAX_MPOPEN)
return(NULL);
/*
* now get the SHELL variable (if any)
*/
shptr = shellenv();
/*
* set it up, just like popen
*/
argv[0] = ((p = strrchr(shptr, '/')) == NULL) ? shptr : p+1;
argv[1] = "-c";
argv[2] = cmd;
argv[3] = NULL;
fpa[0] = (*mode == 'w') ? stdin : NULL;
fpa[1] = (*mode == 'r') ? stdout : NULL;
fpa[2] = NULL;
/*
* run it
*/
if ((pfunc[indx].pid = schild(shptr, (const char**)argv, (const char**)envp, fpa, octmask)) < 0)
return(NULL);
return(pfunc[indx].fp = ((*mode == 'w') ? fpa[0] : fpa[1]));
}
/*
* close the pipe
*/
#ifdef __STDC__
int mpclose(FILE *fp)
#else
int mpclose(fp)
FILE *fp;
#endif
{
register int indx; /* used to look for corresponding pid */
register int rstatus; /* return status of command */
/*
* loop until you find the right process
*/
for(indx = 0; indx < MAX_MPOPEN; indx++)
if (pfunc[indx].fp == fp){
/* got it ... flush and close the descriptor */
(void) fflush(fp);
(void) fclose(fp);
/* get the status code fo the child */
rstatus = echild(pfunc[indx].pid);
/* clear the entry and return the code */
pfunc[indx].pid = 0;
return(rstatus);
}
/*
* no such process - signal no child
*/
return(-1);
}
/*
* like popen but A LOT safer
* uses file descriptors for all three files
* (0, 1, 2)
*/
#ifdef __STDC__
int mfpopen(const char* cmd, FILE *fpa[])
#else
int mfpopen(cmd, fpa)
char *cmd;
FILE *fpa[];
#endif
{
const char *argv[5]; /* argument list */
register const char *p; /* temoporary pointers */
register const char *shptr; /* the program to be run */
register int indx; /* index number of child */
/*
* see if anything is available
*/
for(indx = 0; indx < MAX_MPOPEN; indx++)
if (pfunc[indx].pid == 0)
break;
if (indx == MAX_MPOPEN)
return(-1);
/*
* now get the SHELL variable (if any)
*/
shptr = shellenv();
/*
* set it up, just like popen
*/
argv[0] = ((p = strrchr(shptr, '/')) == NULL) ? shptr : p+1;
argv[1] = "-c";
argv[2] = cmd;
argv[3] = NULL;
/*
* run it
*/
if ((pfunc[indx].pid = schild(shptr, (const char**)argv, (const char**)envp, fpa, octmask)) < 0)
return(-1);
return(indx);
}
/*
* close the pipe
*/
#ifdef __STDC__
int mfpclose(int indx, FILE *fp[3])
#else
int mfpclose(indx, fp)
int indx;
FILE *fp[];
#endif
{
register int rstatus; /* return status of command */
/*
* loop until you find the right process
*/
if (pfunc[indx].pid == 0)
return(-1);
/* got it ... flush and close the descriptor */
if (fp[0] != NULL)
(void) fclose(fp[0]);
/* get the status code fo the child */
rstatus = echild(pfunc[indx].pid);
/* clear the entry and return the code */
pfunc[indx].pid = 0;
/* got it ... flush and close the descriptor */
if (fp[1] != NULL)
(void) fclose(fp[1]);
if (fp[2] != NULL)
(void) fclose(fp[2]);
return(rstatus);
}
/*
* like popen but A LOT safer
* uses arg vector, not command, and file descriptors 0, 1, 2
*/
#ifdef __STDC__
int mxfpopen(const char* argv[], FILE *fpa[])
#else
int mxfpopen(argv, fpa)
char *argv[];
FILE *fpa[];
#endif
{
register int indx; /* index number of child */
/*
* see if anything is available
*/
for(indx = 0; indx < MAX_MPOPEN; indx++)
if (pfunc[indx].pid == 0)
break;
if (indx == MAX_MPOPEN)
return(-1);
/*
* run it
*/
if ((pfunc[indx].pid = schild(argv[0], argv, (const char**)envp, fpa, octmask)) < 0)
return(-1);
return(indx);
}
/*
* close the pipe
*/
#ifdef __STDC__
int mxfpclose(int indx, FILE *fp[3])
#else
int mxfpclose(indx, fp)
int indx;
FILE *fp[];
#endif
{
return(mfpclose(indx, fp));
}
#if IS_AROS
#define fork() vfork()
#endif
/*
* signal values
*/
static tw_sighandler_t savesig[MAX_SIGNAL];
/*
* spawn a child; the child's args and environment are as indicated,
* the file descriptors 0/1/2 are redirected to the open files fp[0]/
* fp[1]/fp[2] if they are non-NULL, and the umask of the child is set
* to omask
*/
#ifdef __STDC__
int schild(const char* cmd, const char** argp, const char** envptr, FILE *fp[], int mask)
#else
int schild(cmd, argp, envptr, fp, mask)
char *cmd;
char **argp;
char **envptr;
FILE *fp[];
int mask;
#endif
{
int p[3][2]; /* pipes to/from child */
register int i; /* counter in for loop */
register int ch_pid; /* child PID */
register int euid, egid; /* in case reset[gu]id is -1 */
/*
* create 1 pipe for each of standard input, output, error
*/
if (fp != NULL){
if (pipe(p[0]) < 0 || pipe(p[1]) < 0 || pipe(p[2]) < 0){
ERMSG("pipes couldn't be made");
return(SE_NOPIPE);
}
}
/*
* remember the effective uid
*/
euid = geteuid();
egid = getegid();
/*
* spawn the child and make the pipes the subprocess stdin, stdout
*/
if ((ch_pid = fork()) == 0){
/* now reset the uid and gid if desired */
if (mresetgid < -1) (void) setgid(getgid());
else if (mresetgid == -1) (void) setgid(egid);
else if (mresetgid > -1) (void) setgid(mresetgid);
if (mresetuid < -1) (void) setuid(getuid());
else if (mresetuid == -1) (void) setuid(euid);
else if (mresetuid > -1) (void) setuid(mresetuid);
/* reset the umask */
(void) umask(mask);
/* close the unused ends of the pipe */
/* and all other files except 0, 1, 2 */
for(i = 3; i < TW_NO_DESC; i++)
if (fp == NULL || (!fdleave[i] && i != p[0][0]
&& i != p[1][1] && i != p[2][1]))
(void) close(i);
/* if the parent wants to read/write to the child, */
/* dup the descriptor; we tell this if the input fp */
/* array has a NULL in the slot (no interest) */
if (fp != NULL){
if (fp[0] != NULL){
(void) dup2(p[0][0], 0);
}
(void) close(p[0][0]);
if (fp[1] != NULL){
(void) dup2(p[1][1], 1);
}
(void) close(p[1][1]);
if (fp[2] != NULL){
(void) dup2(p[2][1], 2);
}
(void) close(p[2][1]);
}
/* exec the command and environment */
(void) execve(cmd, (char* const*)argp, (char* const*)envptr);
/* should never happen ... */
_exit(EXIT_BAD);
}
/*
* parent process: if couldn't create child, error
*/
if (ch_pid != -1)
{
/*
* ignore any signals until child dies
*/
for(i = 0; i < MAX_SIGNAL; i++)
#ifdef SIGCHLD
if (i != SIGCHLD)
#endif
savesig[i] = tw_sigign(i);
/*
* close unused end of pipes
*/
if (fp != NULL){
(void) close(p[0][0]);
(void) close(p[1][1]);
(void) close(p[2][1]);
}
/*
* use a stdio interface for uniformity
*/
if (fp != NULL)
{
if (fp[0] != NULL)
fp[0] = fdopen(p[0][1], "w");
else
(void) close(p[0][1]);
if (fp[1] != NULL)
fp[1] = fdopen(p[1][0], "r");
else
(void) close(p[1][0]);
if (fp[2] != NULL)
fp[2] = fdopen(p[2][0], "r");
else
(void) close(p[2][0]);
}
}
/*
* return child's PID
*/
return(ch_pid);
}
/*
* wait for child to die
*/
#ifdef __STDC__
int echild(int pid)
#else
int echild(pid)
int pid;
#endif
{
register int r; /* PID of process just exited */
int status; /* status of wait call */
/*
* done; wait for child to terminate
*/
while((r = wait(&status)) != pid && r != -1) ;
/*
* if child already dead, assume an exit status of -1
*/
if (r == -1) status = -1;
/*
* restore signal traps
*/
for(r = 0; r < MAX_SIGNAL; r++)
(void) tw_signal(r, savesig[r]);
/*
* return exit status
*/
return(status);
}
#endif //#if IS_UNIX