home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #30 / NN_1992_30.iso / spool / comp / unix / wizards / 5213 / rvi.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-12-14  |  9.5 KB  |  276 lines

  1. /* File: rvi.c
  2.  *     
  3.  *     Name:
  4.  *         rvi - restricted fullscreen text editor
  5.  *     Syntax:
  6.  *         rvi [filename]
  7.  *     
  8.  *     Description:
  9.  *     'rvi' remedies various security holes in the 'vi' fullscreen
  10.  *     text editor.  In 'vi', for example, it's impossible to prevent a
  11.  *     user from invoking an interactive shell if the user chooses to
  12.  *     do so, even if the user's running 'rsh' and the variable "SHELL"
  13.  *     is set to "/bin/rsh" and is read-only, because 'vi' maintains
  14.  *     its own internal "shell" variable and executes it via the ":sh"
  15.  *     command.  'rvi' encapsulates the edit within a defined
  16.  *     sub-universe within the filesystem so that the facilities
  17.  *     accessible to the user are entirely within the control of the
  18.  *     system administrator.
  19.  *     
  20.  *     Mechanics/Implementation:
  21.  *     'rvi' is invoked from a normal current-working-directory the
  22.  *     same way that 'vi' would be, except that only one filename per
  23.  *     invocation is acceptable.  If a filename is specified, and if
  24.  *     the specified filename exists and is writable by the user, or if
  25.  *     the specified filename doesn't exist but the current working
  26.  *     directory is writable by the user, 'rvi' links that filename to
  27.  *     a temporary file within an "rvi-directory" (see below) and forks
  28.  *     a child to edit the temporary file.  The child chroot()'s to the
  29.  *     "rvi-directory", so that nothing on the system outside that
  30.  *     directory is further accessible and overlays itself with 'vi' to
  31.  *     perform the edit.  Programs available to the user are only those
  32.  *     selected by the system administrator and linked (or copied) into
  33.  *     the "rvi-directory" sub-hierarchy; in particular, interactive
  34.  *     shells are inaccessible to the user unless /bin/sh (or some
  35.  *     other shell) is/are linked (or copied) into the sub-hierarchy.
  36.  *     
  37.  *     If no filename is specified, 'rvi' creates a file called
  38.  *     "btxtNNNNN" in the current directory, where "NNNNN" is the
  39.  *     process ID number of the user's process.  If that file has
  40.  *     nonzero length on exit, the user is prompted for a new name for
  41.  *     the file.  The ":f" ex-escape command is available to the user
  42.  *     from within 'vi', but is ineffectual if the "rvi-directory" is
  43.  *     secured as the accompanying script 'rvi_install' secures it.
  44.  *     Essentially the user is restricted to writing his specified edit
  45.  *     file and no other file.
  46.  *     
  47.  *     The "rvi-directory" is an existing directory previously
  48.  *     established by the system administrator, a directory that will
  49.  *     mimic the root directory "/" during the edit.  This directory
  50.  *     must contain certain essential subdirectories, 'bin', 'tmp',
  51.  *     'etc', within which certain system files must be linked or
  52.  *     copied.  The accompanying script 'rvi_install' creates the
  53.  *     "rvi-directory" hierarchy.
  54.  *     
  55.  *     'rvi' looks first for an environment variable RVIDIR containing
  56.  *     the pathname of the "rvi-directory"; if no such variable exists,
  57.  *     'rvi' looks for a pathname in the file "/etc/default/rvi" for
  58.  *     the "rvi-directory"; if no such pathname exists in that file, or
  59.  *     if that file doesn't exist, 'rvi' assumes that the
  60.  *     "rvi-directory" is "/rvitmp".
  61.  *     
  62.  *     Copyright:
  63.  *     
  64.  *     This program was written by Fred Buck, who hereby releases it
  65.  *     into the public domain and expressly relinquishes all copy- or
  66.  *     other-rights regarding it.  This code is public property.  However,
  67.  *     the author disclaims liability for any use of this code.
  68.  *     
  69.  *     NOTES/BUGS
  70.  *     
  71.  *     The filename displayed on the 'vi' status line will always be
  72.  *     the name of the temporary file within the "rvi-directory",
  73.  *     regardless of whether 'rvi' is invoked with or without a
  74.  *     filename.  Users should be warned about this.  If the filename
  75.  *     is changed with the ":f" ex-escape command, the user will be
  76.  *     unable to write the results of his or her edit unless the
  77.  *     filename is changed back to the name of the temporary file;
  78.  *     users should be alerted to the ":f #" command to recover from
  79.  *     such a circumstance, or else the system administrator may
  80.  *     consider mapping an unused command key to the sequence ":f #^M"
  81.  *     (see vi(1) or vi(C) for details).
  82.  *     
  83.  *     The shell variable TERM must be properly set before 'rvi' is
  84.  *     called, or else the behavior of 'vi' will be erratic.  'rvi' is
  85.  *     effective as well with 'ex' as with 'vi'; to call 'ex'
  86.  *     specifically, change "vi" to "ex" in the code below, and link
  87.  *     "/bin/ex" into the "rvi-directory".
  88.  *     
  89.  *     As noted above, the "rvi-directory" MUST reside on the same
  90.  *     device as the user's current working directory.
  91.  *     
  92.  *     On SCO Xenix-286, DO NOT USE THE OPTIMIZING COMPILER SWITCH
  93.  *     "-O" when compiling 'rvi'.  The optimizer on SCO Xenix-286 is
  94.  *     less than perfect, and chokes on this program.
  95.  */
  96.  
  97. #include <stdio.h>
  98. #include <sys/types.h>
  99. #include <sys/stat.h>
  100. #include <signal.h>
  101.  
  102. #define BOZODIR        "/rvitmp"        /* default rvi-directory */
  103. #define READ        04
  104. #define WRITE        02
  105. #define EXECUTE        01
  106. #define OWNER        0100
  107. #define GROUP        010
  108. #define OTHER        01
  109. #define HIBYTE(x)    (((x) >> 8) & 0xff)
  110. #define LOBYTE(x)    ((x) & 0xff)
  111. #define WORD(x)        ((x) & 0xffffL)
  112.  
  113. static struct stat buf;
  114. static char *progname;
  115.  
  116. /* void Abort(): terminates execution with an error message.
  117.  * 
  118.  * returns: nothing
  119.  */
  120. Abort(string1, string2)
  121. char *string1, *string2;
  122. {
  123.     fprintf(stderr,"%s: ",progname);
  124.     fprintf(stderr,string1,string2);
  125.     exit(1);
  126. }
  127.  
  128. /* int perms_ok(): checks read, write or execute permission for real
  129.  *   user or group ID
  130.  *
  131.  * returns: 1 if specified permission exists, 0 otherwise
  132.  */
  133. perms_ok(statbuf, mode)
  134. struct stat *statbuf;    /* stat() buffer for file or directory */
  135. int mode;        /* READ, WRITE, or EXECUTE */
  136. {
  137.     if (mode != READ && mode != WRITE && mode != EXECUTE)
  138.         return(0);
  139.     else if (getuid() == 0)
  140.         return(1);
  141.     else if ( statbuf->st_uid == getuid()
  142.          &&  (statbuf->st_mode & (mode * OWNER)) )
  143.         return(1);
  144.     else if ( statbuf->st_gid == getgid()
  145.          &&  (statbuf->st_mode & (mode * GROUP)) )
  146.         return(1);
  147.     else if ( statbuf->st_mode & (mode * OTHER) )
  148.         eturn(1);
  149.     else return(0);
  150. }
  151.  
  152. /* main()
  153.  */
  154. main(argc, argv)
  155. int argc; char **argv;
  156. {
  157.     FILE *defalt;
  158.     char c, *bozodir;
  159.     int unnamed, newfile;
  160.     int kidstatus, kidpid;
  161.     static char tempname[80], filename[15];
  162.     static char lastchance[80] = "/rvitmp";
  163.     extern char *getenv(), *strchr(), *strrchr(), *strncpy();
  164.     extern FILE *fopen();
  165.  
  166.     newfile = unnamed = 0;
  167.     progname = argv[0];
  168.  
  169.     /* establish which directory to use */
  170.     if ((bozodir=getenv("RVIDIR"))!=0)
  171.         ;
  172.     else if ((defalt=fopen("/etc/default/rvi","r")) != (char *)0) {
  173.         fgets(lastchance,80,defalt);
  174.         *(strchr(lastchance,'\n')) = '\0';
  175.         bozodir = lastchance;
  176.         if (*bozodir == '\0') bozodir = BOZODIR;
  177.         fclose(defalt);
  178.     }
  179.     else bozodir = BOZODIR;
  180.  
  181.     /* check permissions and initialize stuff */
  182.     if (argc == 1) {    /* no edit file specified */
  183.         sprintf(filename,"btxt%d",getpid());
  184.         newfile = unnamed = 1;
  185.     }
  186.     else {            /* file specified */
  187.         if (argc > 2)
  188.             Abort("only one filename allowed\n","");
  189.         if (strchr(argv[1],'/'))
  190.             Abort("bad filename '%s' (not a basename)\n",argv[1]);
  191.         if (strlen(argv[1]) > 14)
  192.             Abort("bad filename '%s' (too long)\n",argv[1]);
  193.         strncpy(filename,argv[1],14);
  194.         if (stat(filename,&buf)==0) {    /* file exists */
  195.             if (!perms_ok(&buf,WRITE))
  196.                 Abort("can't write file '%s'\n",filename);
  197.         }
  198.         else newfile = 1;        /* file doesn't exist */
  199.     }
  200.  
  201.     if (newfile) {
  202.         if (stat(".",&buf) < 0)
  203.             Abort("can't stat working directory\n","");
  204.         if (!perms_ok(&buf,WRITE))
  205.             Abort("can't write in working directory\n","");
  206.         /* let a kid create the file with the user's real uid/gid  */
  207.         /* (we could create it ourself as 'root', and chown(), but */
  208.         /* this ensures that the creat() succeeds or fails by the  */
  209.         /* user's own real permissions).                           */
  210.         if ((kidpid=fork())==0) {
  211.             if (setgid(getgid())<0 || setuid(getuid())<0)
  212.                 exit(1);
  213.             else
  214.                 exit(creat(filename,0644)<0);
  215.         }
  216.         wait(&kidstatus);
  217.         if ( kidpid < 0
  218.         ||   LOBYTE(kidstatus) != 0
  219.         ||   HIBYTE(kidstatus) != 0 )
  220.             Abort("internal error (filename create)\n","");
  221.     }
  222.  
  223.     /* link the file-to-edit to a made-up name in the temp directory */
  224.     sprintf(tempname,"%s/btxt%d",bozodir,getpid());
  225.     if (link(filename,tempname)<0) {
  226.         fprintf(stderr,"%s: ",argv[0]);
  227.         perror("system error");
  228.         Abort("internal error (link failure)\n","");
  229.     }
  230.  
  231.     /* fork a kid to do the edit */
  232.     if ((kidpid=fork())==0) {        /* this is the kid */
  233.         if (chdir(bozodir)<0)
  234.             Abort("internal error (chdir failure)\n","");
  235.         if (chroot(".")<0)
  236.             Abort("internal error (chroot failure)\n","");
  237.         if (setgid(getgid())<0 || setuid(getuid())<0)
  238.             Abort("internal error (setgid/setuid)\n","");
  239.         execl("vi","vi",strrchr(tempname,'/')+1,0);
  240.         exit(1);                /* exec() failed */
  241.     }
  242.     else {                /* this is the parent */
  243.         signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN);
  244.         wait();
  245.     }
  246.  
  247.     /* the edit's over now, take care of supplementary housekeeping */
  248.     link(tempname,filename);        /* just in case */
  249.     unlink(tempname);            /* don't need it anymore */
  250.     if (stat(filename,&buf)<0)        /* weird case: file gone */
  251.         exit(1);
  252.     if (buf.st_size==0) {            /* if filesize zero,     */
  253.         if (newfile)            /*  kill it if new file, */
  254.             unlink(filename);
  255.         exit(0);            /*  else just bail out   */
  256.     }
  257.         /* if the file's unnamed (i.e., "btxtNNNNN"), name it */
  258.     if (kidpid > 0 && unnamed) {
  259.         printf("\nThe current filename is '%s'.\n",filename);
  260.         printf("Do you want to rename it? ");
  261.         c = getchar();
  262.         while (getchar()!='\n');
  263.         if (c!='y' && c!='Y')
  264.             exit(0);
  265.         else {
  266.             printf("New filename: ");
  267.             fgets(tempname,15,stdin);
  268.             *(strchr(tempname,'\n')) = '\0';
  269.             if (strlen(tempname)==0) exit(0);
  270.             link(filename,tempname);
  271.             unlink(filename);
  272.         }
  273.     }
  274.     exit(0);
  275. }
  276.