SUMMARY: Anonymous FTP policies/setups

From: Lenny Turetsky (lturetsk@aida.econ.yale.edu)
Date: Thu Nov 03 1994 - 09:47:35 CST


First off, thanks to:

"Bill Kastner" <kastnerb@njc.org>
Joe Fedock <jfedock@motown.ge.com>
Ray Brownrigg <Ray.Brownrigg@isor.vuw.ac.nz>
Ric Anderson <ric@Artisoft.COM>
Rich Holland <holland@engg.ksu.edu>
Richard Pieri <ratinox@unilab.dfci.harvard.edu>
barmar@nic.near.net
bern@penthesilea.uni-trier.de (Jochen Bern)
bidwell@andrews.edu (Daniel R. Bidwell)
cammaa@mcsunx.gs.com (Aaron Cammarata)
epl@Kodak.COM (Gene Loriot (epl@kodak.com))
perryh@pluto.rain.com (Perry Hutchison)
tim@ben.dciem.dnd.ca

The original question:

--------------------------------------------------------------
We have an anonymous FTP service here thru which faculty and students can
put their work (papers, data, resumes, etc.) out for public consumption
on the 'net.
 
Thus, I have a need for a system that lets them put files into their own
area within the anon-ftp partition. (e.g., there is a ~ftp/lturetsk
directory that I need to put my files into).
 
I'm tempted to tell them to just create a subdirectory ~<userid>/pub (or
some similar name) that will be linked to ~ftp/<userid>, but I heard that
it's not a good idea for the anon-ftp setup to have any access to the
rest of the system. Why shouldn't it? Is this just an old-wive's tale, or
is there wisdom in it?
 
The system that was implemented by the previous sysadmin was a bunch of
suid executables that copied files or directories over to ~ftp, but I've
discovered that that can be used to copy another user's non-public files
(since the copying is done as root -- on the bright side, the files are
left with their original ownerships/permissions, so they're still
private, but ... ).
 
Does anyone have a good system for this kind of thing, or any ideas on
what a good system would be?
--------------------------------------------------------------

The answers:

Links to w/in ~ftp won't work b/c the ftp daemon runs in a chroot(2)
environment that won't let it see anything outside of ~ftp. I should have
known this.

Some people suggested creating ~ftp/pub/ directories for all users, but
that would be inefficient in my case b/c:
        1) New users are created all the time, and I don't want to have
                to create their ftp dir along with their home dir
        2) Most users never use the ftp system, so it would just be a
                wast of inodes and space (each dir entry takes up .5k)

Another suggestion was to create a secondary account (e.g., lturetskftp)
which a user would use to ftp in and put files into his/her ftp area.
Kinda ugly if you ask me. The person who suggested this mentioned that he
was also interested in hearing alternate suggestions -- I can see why (no
offense meant -- this is just an ungainly solution).

One person sent me a script that is included in the SunOS 5.3 (Solaris
2.3) ftpd man page that does this. I don't like the idea of having suid
scripts (I know that Sun has fixed the problem (mentioned in the Unix FAQ),
but I still don't feel safe). Besides, I'm not sure if I'm licensed to
use this script on SunOS 4.1.x. The script seems good, though.

The most often recommended solution was to create an suid executable that
would create a ~ftp/pub/`whoami` directory that would be owned by the
user, and let the user deal with that directory directly. This is what
I've done (source code included below).

What the code does is:

        IF ~ftp/pub/`whoami` does NOT exist
        THEN
                ask user if s/he wants to create it
                IF yes
                THEN
                        IF created (and chmod'ed and chown'ed)
                        THEN continue
                        ELSE exit(EXIT_FAILURE)
                ELSE exit
        ELSE continue

        get rid of setuid priveledges (no longer needed -- why risk it?)

        ask user if s/h wants to link ~ftp/pub/`whoami` do ~/pub
        IF yes
        THEN
                IF ~/pub doesn't exist
                THEN make the link
                ELSE ask for a different name and make that link
        ELSE exit

Issues left:

There should be no links w/in ~ftp to stuff outside of ~ftp -- how can I
prevent users from creating such links? The only thing I can think of
offhand is:
        Run a cron job daily that searches for links in ~ftp and mails
                their owners about the uselessness of such links
Anyone have anything better? Is there any (easy) way to modify the
filesystem to make it refuse to take links?

Since these directories are now directly owned by users, they can be used
to circumvent the disc quota system. Any way to prevent this would be
much appreciated.

~ftp is currently only available on the server. Is is safe to NFS mount
it on the client machines, or is that inadvisable? What would be the
downside to this?

Thanks a lot,
LT

PS The source code below is released as-is. The author still retains full
copyright, but you are authorized to distribute it to anyone you want so
long as *NO CHANGES* are made. The only changes that you may make in
*your own* copy are changes to constants necessary for the peculiarities
of your system (e.g., if your ftp setup has user directories in
~ftp/home/ rather than ~ftp/pub/, you may change the HOME_PREFIX
#definition). If there are any changes you think should be made to the
code, please suggest them to the author (you're authorized to make a
changed version of the code to show the author) and they will be taken
under consideration.

------ cut here ------------ cut here ------------ cut here ------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
# Makefile
# create-ftp.c
# create-ftp.h
# lib-len.c
# lib-len.h
# x.shar
# This archive created: Thu Nov 3 10:46:16 1994
export PATH; PATH=/bin:$PATH
if test -f 'Makefile'
then
        echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
MY_LIB_CODE = lib-len.c
MY_LIB_HEAD = lib-len.h
MY_LIB_FILES = $(MY_LIB_CODE) $(MY_LIB_HEAD)
MY_LIB_OBJ = lib-len.o

FTP_CODE = create-ftp.c
FTP_HEAD = create-ftp.h
FTP_FILES = $(FTP_CODE) $(FTP_HEAD)
FTP_OBJ = create-ftp.o

$(MY_LIB_OBJ): $(MY_LIB_FILES)
                $(CC) $(CFLAGS) -c $(MY_LIB_CODE) -o $@

$(FTP_OBJ): $(FTP_FILES)
                $(CC) $(CFLAGS) -c $(FTP_CODE) -o $@

create-ftp: $(FTP_OBJ) $(MY_LIB_OBJ)
                $(CC) $(CFLAGS) $(FTP_OBJ) $(MY_LIB_OBJ) -o $@
SHAR_EOF
fi # end of overwriting check
if test -f 'create-ftp.c'
then
        echo shar: will not over-write existing file "'create-ftp.c'"
else
cat << \SHAR_EOF > 'create-ftp.c'
/* create a ~ftp/pub/<user> directory and (optionally) create a ~<user>/FTP
 * which is a symbolic link to the former directory.
 *
 * must be setuid root (or whoever owns ~ftp/pub -- which *should* be root)
 *
 * Lenny Turetsky
 * lturetsk@econ.yale.edu
 * October 1994
 */
 
#include "create-ftp.h"

int main( int argc, char *argv[] )
{
  uid_t my_uid = getuid(), my_euid = geteuid();
  gid_t my_gid = getgid(), my_egid = getegid();
  pwd *ftp_pwent, *my_pwent;
  char *my_ftp_dir = NULL, link_name[1024], tmp_str[1024];
  struct stat dir_stat;

  unsigned int i;
  char C;
  char *temp_str = NULL;

  /* initialization stuff */
  umask(20 | 2);

  if ((ftp_pwent = pwd_copy(getpwnam(FTP_LN)))
      &&
      (my_pwent = pwd_copy(getpwuid(my_uid)))
      &&
      /* what should my ftp directory be? */
      ((my_ftp_dir = (char *)malloc(sizeof(char) *
                                   (strlen(ftp_pwent->pw_dir) +
                                    strlen(HOME_PREFIX) +
                                    strlen(my_pwent->pw_name) + 2)))
       &&
       (my_ftp_dir = strcpy(my_ftp_dir, ftp_pwent->pw_dir))
       &&
       (my_ftp_dir = strcat(my_ftp_dir, HOME_PREFIX))
       &&
       (my_ftp_dir = strcat(my_ftp_dir, my_pwent->pw_name))))
    {
      /* does ~ftp even exist??? */
      if (stat(ftp_pwent->pw_dir, &dir_stat) != 0)
        {
          fprintf(stderr,
                  "Cannot access ~ftp (%s).\nAre you on the right machine?\n",
                  ftp_pwent->pw_dir);
          exit(EXIT_FAILURE);
        }
    }
  else
    {
      fprintf(stderr, "\aCouldn\'t access password entries -- bombing out!\n");
      exit(EXIT_FAILURE);
    }

  if (lstat(my_ftp_dir, &dir_stat) == 0)
    {
      printf("%s already exists.\n", my_ftp_dir);
      if ((dir_stat.st_uid != my_uid)
          ||
          (dir_stat.st_gid != my_gid))
        {
          printf("%s is not owned by you. Would you like to own it? [Y/n] ",
                 my_ftp_dir);
          switch(prompt('Y', "YN"))
            {
            case EOF:
              fprintf(stderr, "Unexpected end of input.\n");
              exit(EXIT_FAILURE);
              break;
            case 'N':
              printf("OK.\n");
              exit(EXIT_SUCCESS);
              break;
            case 'Y':
            default:
              chown(my_ftp_dir, my_uid, my_gid);
              break;
            }
        }
      else
        printf("%s is owned by you.\n", my_ftp_dir);
    }
  else
    {
      if (errno == ENOENT)
        {
          printf("You don\'t current have an ftp directory; shall I create one for you? [Y/n] ");

          switch (prompt('Y', "YN"))
            {
            case EOF:
              fprintf(stderr, "Unexpected end of input.\n");
              exit(EXIT_FAILURE);
              break;
            case 'N':
              printf("OK.\n");
              exit(EXIT_SUCCESS);
              break;
            case 'Y':
            default:
              if ((mkdir(my_ftp_dir, MODE) == 0)
                  &&
                  (chmod(my_ftp_dir, MODE) == 0)
                  &&
                  (chown(my_ftp_dir, my_uid, my_gid) == 0))
                {/* don't do anything -- it worked */}
              else
                {
                  fprintf(stderr, "Couldn\'t create %s:\texiting\n",
                          my_ftp_dir);
                  exit(EXIT_FAILURE);
                }
              break;
            }
        }
      else
        {
          fprintf(stderr, "Unexpected error:\t%d encountered:\texiting\n",
                  errno);
          exit(EXIT_FAILURE);
        }
    }

  /* ~ftp/pub/`whoami` should exist by now, so let's go on */
  setuid(my_uid); /* we no longer need setuid priveleges */

  printf("Shall I link your ftp directory within your home directory (~/pub)? [Y/n] ",
         my_ftp_dir, my_pwent->pw_dir);
  switch (prompt('Y', "YN"))
    {
    case EOF:
      fprintf(stderr, "Unexpected end of input.\n");
      exit(EXIT_FAILURE);
      break;
    case 'N':
      printf("OK.\n");
      printf("If you would like to create such a link later, here\'s how:\n");
      printf("\tln -s ~ftp/pub/`whoami` ~%s\n", LINK_NAME);
      break;
    case 'Y':
    default:
      strcpy(link_name, my_pwent->pw_dir);
      strcat(link_name, LINK_NAME);

      while (stat(link_name, &dir_stat) == 0)
        {
          printf("A file/directory already has the name: %s\n", link_name);
          printf("What would you like to call the link? ");
          
          if ((temp_str = getLine(&i)) != NULL)
            {
              strcpy(link_name, my_pwent->pw_dir);
              strcat(link_name, "/");
              strcat(link_name, temp_str);

              free(temp_str);
            }
          else
            {
              fprintf(stderr, "Unexpected end of input!\n");
              exit(EXIT_FAILURE);
            }
        }

      if (symlink(my_ftp_dir, link_name) != 0)
        {
          fprintf(stderr, "Couldn\'t link %s to %s:\texiting!\n",
                  my_ftp_dir, link_name);
          exit(EXIT_FAILURE);
        }
    }
  putchar('\n');
}

pwd *pwd_copy(const pwd *src)
{
  pwd *copy = NULL;

  if (src && (copy = (pwd *)malloc(sizeof(pwd))))
    {
      copy->pw_name = strdup(src->pw_name);
      copy->pw_passwd = strdup(src->pw_passwd);
      copy->pw_uid = src->pw_uid;
      copy->pw_gid = src->pw_gid;
/* copy->pw_quota = src->pw_quota;
        copy->pw_comment = strdup(src->pw_comment); */
      copy->pw_gecos = strdup(src->pw_gecos);
      copy->pw_dir = strdup(src->pw_dir);
      copy->pw_shell = strdup(src->pw_shell);
    }
  return(copy);
}

SHAR_EOF
fi # end of overwriting check
if test -f 'create-ftp.h'
then
        echo shar: will not over-write existing file "'create-ftp.h'"
else
cat << \SHAR_EOF > 'create-ftp.h'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/stat.h>
#include <errno.h>
#include <ctype.h>

#include "lib-len.h"

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

#define STR_SIZE 256

#define MODE S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH

#define LINK_NAME "/pub"
#define HOME_PREFIX "/pub/"

#define FTP_LN "ftp"
typedef struct passwd pwd;

pwd *pwd_copy(const pwd *src);

SHAR_EOF
fi # end of overwriting check
if test -f 'lib-len.c'
then
        echo shar: will not over-write existing file "'lib-len.c'"
else
cat << \SHAR_EOF > 'lib-len.c'
#include "lib-len.h"

short int prompt(char def, char *answers)
{
  register char C, X;

  while ((C = toupper(getchar())) != EOF)
    {
      if (strchr(answers, C))
        break;
      else if (C == '\n')
        return(def);
    }
  /* get rid of everything else on the line */
  while (((X = getchar()) != EOF) && (X != '\n'));

  return(C);
}

char *getLine( unsigned int *length )
{
  int i = 0, size = DEF_SIZE, C;
  char *Line = (char *)malloc( sizeof(char) * size );

  if (feof(stdin))
    {
      free(Line);
      return(NULL);
    }

  while (( (C = getchar()) != EOF ) && ( C != '\n' ))
    {
      if (i == size)
        {
          Line = realloc( Line, sizeof(char) * (size += DEF_SIZE) );
        }
      Line[i++] = C;
    }

  if (length)
    *length = i;
  Line[i] = '\0';

  return( Line );
}

SHAR_EOF
fi # end of overwriting check
if test -f 'lib-len.h'
then
        echo shar: will not over-write existing file "'lib-len.h'"
else
cat << \SHAR_EOF > 'lib-len.h'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEF_SIZE 8

short int prompt(char def, char *answers);
char *getLine( unsigned int *length );
SHAR_EOF
fi # end of overwriting check
if test -f 'x.shar'
then
        echo shar: will not over-write existing file "'x.shar'"
else
cat << \SHAR_EOF > 'x.shar'

SHAR_EOF
fi # end of overwriting check
# End of shell archive
exit 0



This archive was generated by hypermail 2.1.2 : Fri Sep 28 2001 - 23:09:13 CDT