home *** CD-ROM | disk | FTP | other *** search
- /* File: rvi.c
- *
- * Name:
- * rvi - restricted fullscreen text editor
- * Syntax:
- * rvi [filename]
- *
- * Description:
- * 'rvi' remedies various security holes in the 'vi' fullscreen
- * text editor. In 'vi', for example, it's impossible to prevent a
- * user from invoking an interactive shell if the user chooses to
- * do so, even if the user's running 'rsh' and the variable "SHELL"
- * is set to "/bin/rsh" and is read-only, because 'vi' maintains
- * its own internal "shell" variable and executes it via the ":sh"
- * command. 'rvi' encapsulates the edit within a defined
- * sub-universe within the filesystem so that the facilities
- * accessible to the user are entirely within the control of the
- * system administrator.
- *
- * Mechanics/Implementation:
- * 'rvi' is invoked from a normal current-working-directory the
- * same way that 'vi' would be, except that only one filename per
- * invocation is acceptable. If a filename is specified, and if
- * the specified filename exists and is writable by the user, or if
- * the specified filename doesn't exist but the current working
- * directory is writable by the user, 'rvi' links that filename to
- * a temporary file within an "rvi-directory" (see below) and forks
- * a child to edit the temporary file. The child chroot()'s to the
- * "rvi-directory", so that nothing on the system outside that
- * directory is further accessible and overlays itself with 'vi' to
- * perform the edit. Programs available to the user are only those
- * selected by the system administrator and linked (or copied) into
- * the "rvi-directory" sub-hierarchy; in particular, interactive
- * shells are inaccessible to the user unless /bin/sh (or some
- * other shell) is/are linked (or copied) into the sub-hierarchy.
- *
- * If no filename is specified, 'rvi' creates a file called
- * "btxtNNNNN" in the current directory, where "NNNNN" is the
- * process ID number of the user's process. If that file has
- * nonzero length on exit, the user is prompted for a new name for
- * the file. The ":f" ex-escape command is available to the user
- * from within 'vi', but is ineffectual if the "rvi-directory" is
- * secured as the accompanying script 'rvi_install' secures it.
- * Essentially the user is restricted to writing his specified edit
- * file and no other file.
- *
- * The "rvi-directory" is an existing directory previously
- * established by the system administrator, a directory that will
- * mimic the root directory "/" during the edit. This directory
- * must contain certain essential subdirectories, 'bin', 'tmp',
- * 'etc', within which certain system files must be linked or
- * copied. The accompanying script 'rvi_install' creates the
- * "rvi-directory" hierarchy.
- *
- * 'rvi' looks first for an environment variable RVIDIR containing
- * the pathname of the "rvi-directory"; if no such variable exists,
- * 'rvi' looks for a pathname in the file "/etc/default/rvi" for
- * the "rvi-directory"; if no such pathname exists in that file, or
- * if that file doesn't exist, 'rvi' assumes that the
- * "rvi-directory" is "/rvitmp".
- *
- * Copyright:
- *
- * This program was written by Fred Buck, who hereby releases it
- * into the public domain and expressly relinquishes all copy- or
- * other-rights regarding it. This code is public property. However,
- * the author disclaims liability for any use of this code.
- *
- * NOTES/BUGS
- *
- * The filename displayed on the 'vi' status line will always be
- * the name of the temporary file within the "rvi-directory",
- * regardless of whether 'rvi' is invoked with or without a
- * filename. Users should be warned about this. If the filename
- * is changed with the ":f" ex-escape command, the user will be
- * unable to write the results of his or her edit unless the
- * filename is changed back to the name of the temporary file;
- * users should be alerted to the ":f #" command to recover from
- * such a circumstance, or else the system administrator may
- * consider mapping an unused command key to the sequence ":f #^M"
- * (see vi(1) or vi(C) for details).
- *
- * The shell variable TERM must be properly set before 'rvi' is
- * called, or else the behavior of 'vi' will be erratic. 'rvi' is
- * effective as well with 'ex' as with 'vi'; to call 'ex'
- * specifically, change "vi" to "ex" in the code below, and link
- * "/bin/ex" into the "rvi-directory".
- *
- * As noted above, the "rvi-directory" MUST reside on the same
- * device as the user's current working directory.
- *
- * On SCO Xenix-286, DO NOT USE THE OPTIMIZING COMPILER SWITCH
- * "-O" when compiling 'rvi'. The optimizer on SCO Xenix-286 is
- * less than perfect, and chokes on this program.
- */
-
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <signal.h>
-
- #define BOZODIR "/rvitmp" /* default rvi-directory */
- #define READ 04
- #define WRITE 02
- #define EXECUTE 01
- #define OWNER 0100
- #define GROUP 010
- #define OTHER 01
- #define HIBYTE(x) (((x) >> 8) & 0xff)
- #define LOBYTE(x) ((x) & 0xff)
- #define WORD(x) ((x) & 0xffffL)
-
- static struct stat buf;
- static char *progname;
-
- /* void Abort(): terminates execution with an error message.
- *
- * returns: nothing
- */
- Abort(string1, string2)
- char *string1, *string2;
- {
- fprintf(stderr,"%s: ",progname);
- fprintf(stderr,string1,string2);
- exit(1);
- }
-
- /* int perms_ok(): checks read, write or execute permission for real
- * user or group ID
- *
- * returns: 1 if specified permission exists, 0 otherwise
- */
- perms_ok(statbuf, mode)
- struct stat *statbuf; /* stat() buffer for file or directory */
- int mode; /* READ, WRITE, or EXECUTE */
- {
- if (mode != READ && mode != WRITE && mode != EXECUTE)
- return(0);
- else if (getuid() == 0)
- return(1);
- else if ( statbuf->st_uid == getuid()
- && (statbuf->st_mode & (mode * OWNER)) )
- return(1);
- else if ( statbuf->st_gid == getgid()
- && (statbuf->st_mode & (mode * GROUP)) )
- return(1);
- else if ( statbuf->st_mode & (mode * OTHER) )
- eturn(1);
- else return(0);
- }
-
- /* main()
- */
- main(argc, argv)
- int argc; char **argv;
- {
- FILE *defalt;
- char c, *bozodir;
- int unnamed, newfile;
- int kidstatus, kidpid;
- static char tempname[80], filename[15];
- static char lastchance[80] = "/rvitmp";
- extern char *getenv(), *strchr(), *strrchr(), *strncpy();
- extern FILE *fopen();
-
- newfile = unnamed = 0;
- progname = argv[0];
-
- /* establish which directory to use */
- if ((bozodir=getenv("RVIDIR"))!=0)
- ;
- else if ((defalt=fopen("/etc/default/rvi","r")) != (char *)0) {
- fgets(lastchance,80,defalt);
- *(strchr(lastchance,'\n')) = '\0';
- bozodir = lastchance;
- if (*bozodir == '\0') bozodir = BOZODIR;
- fclose(defalt);
- }
- else bozodir = BOZODIR;
-
- /* check permissions and initialize stuff */
- if (argc == 1) { /* no edit file specified */
- sprintf(filename,"btxt%d",getpid());
- newfile = unnamed = 1;
- }
- else { /* file specified */
- if (argc > 2)
- Abort("only one filename allowed\n","");
- if (strchr(argv[1],'/'))
- Abort("bad filename '%s' (not a basename)\n",argv[1]);
- if (strlen(argv[1]) > 14)
- Abort("bad filename '%s' (too long)\n",argv[1]);
- strncpy(filename,argv[1],14);
- if (stat(filename,&buf)==0) { /* file exists */
- if (!perms_ok(&buf,WRITE))
- Abort("can't write file '%s'\n",filename);
- }
- else newfile = 1; /* file doesn't exist */
- }
-
- if (newfile) {
- if (stat(".",&buf) < 0)
- Abort("can't stat working directory\n","");
- if (!perms_ok(&buf,WRITE))
- Abort("can't write in working directory\n","");
- /* let a kid create the file with the user's real uid/gid */
- /* (we could create it ourself as 'root', and chown(), but */
- /* this ensures that the creat() succeeds or fails by the */
- /* user's own real permissions). */
- if ((kidpid=fork())==0) {
- if (setgid(getgid())<0 || setuid(getuid())<0)
- exit(1);
- else
- exit(creat(filename,0644)<0);
- }
- wait(&kidstatus);
- if ( kidpid < 0
- || LOBYTE(kidstatus) != 0
- || HIBYTE(kidstatus) != 0 )
- Abort("internal error (filename create)\n","");
- }
-
- /* link the file-to-edit to a made-up name in the temp directory */
- sprintf(tempname,"%s/btxt%d",bozodir,getpid());
- if (link(filename,tempname)<0) {
- fprintf(stderr,"%s: ",argv[0]);
- perror("system error");
- Abort("internal error (link failure)\n","");
- }
-
- /* fork a kid to do the edit */
- if ((kidpid=fork())==0) { /* this is the kid */
- if (chdir(bozodir)<0)
- Abort("internal error (chdir failure)\n","");
- if (chroot(".")<0)
- Abort("internal error (chroot failure)\n","");
- if (setgid(getgid())<0 || setuid(getuid())<0)
- Abort("internal error (setgid/setuid)\n","");
- execl("vi","vi",strrchr(tempname,'/')+1,0);
- exit(1); /* exec() failed */
- }
- else { /* this is the parent */
- signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN);
- wait();
- }
-
- /* the edit's over now, take care of supplementary housekeeping */
- link(tempname,filename); /* just in case */
- unlink(tempname); /* don't need it anymore */
- if (stat(filename,&buf)<0) /* weird case: file gone */
- exit(1);
- if (buf.st_size==0) { /* if filesize zero, */
- if (newfile) /* kill it if new file, */
- unlink(filename);
- exit(0); /* else just bail out */
- }
- /* if the file's unnamed (i.e., "btxtNNNNN"), name it */
- if (kidpid > 0 && unnamed) {
- printf("\nThe current filename is '%s'.\n",filename);
- printf("Do you want to rename it? ");
- c = getchar();
- while (getchar()!='\n');
- if (c!='y' && c!='Y')
- exit(0);
- else {
- printf("New filename: ");
- fgets(tempname,15,stdin);
- *(strchr(tempname,'\n')) = '\0';
- if (strlen(tempname)==0) exit(0);
- link(filename,tempname);
- unlink(filename);
- }
- }
- exit(0);
- }
-