/*
 * Copyright (c) 1990,1993 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <sys/time.h>
#include <netatalk/endian.h>
#include <dirent.h>
#include <limits.h>
#include <atalk/afp.h>
#include <string.h>
#include <fcntl.h>
#include "auth.h"
#include "directory.h"
#include "volume.h"
#include "unix.h"

#ifdef NEED_QUOTACTL_WRAPPER
int quotactl(int cmd, const char *special, int id, caddr_t addr)
{
  return syscall(__NR_quotactl, cmd, special, id, addr);
}
#endif

/*
 * Get the free space on a partition.
 */
ustatfs_getvolspace( vol, bfree, btotal, bsize )
    struct vol	*vol;
    VolSpace    *bfree, *btotal;
    u_int32_t   *bsize;
{
#ifdef ultrix
    struct fs_data	sfs;
#else ultrix
    struct statfs	sfs;
#endif ultrix

    if ( statfs( vol->v_path, &sfs ) < 0 ) {
	return( AFPERR_PARAM );
    }

#ifdef ultrix
    *bfree = (VolSpace) sfs.fd_req.bfreen * 1024;
    *bsize = 1024;
#else
    *bfree = (VolSpace) sfs.f_bavail * sfs.f_frsize;
    *bsize = sfs.f_frsize;
#endif ultrix

#ifdef ultrix
    *btotal = (VolSpace) 
      ( sfs.fd_req.btot - ( sfs.fd_req.bfree - sfs.fd_req.bfreen )) * 1024;
#else ultrix
    *btotal = (VolSpace) 
      ( sfs.f_blocks - ( sfs.f_bfree - sfs.f_bavail )) * sfs.f_frsize;
#endif ultrix

    return( AFP_OK );
}

#ifdef __svr4__
/*
 * Return the mount point associated with the filesystem
 * on which "file" resides.  Returns NULL on failure.
 */
static char *
mountp( file, nfs)
    char	*file;
    int         *nfs;
{
    struct stat			sb;
    FILE 			*mtab;
    dev_t			devno;
    static struct mnttab	mnt;

    if ( stat( file, &sb ) < 0 ) {
	return( NULL );
    }
    devno = sb.st_dev;

    if (( mtab = fopen( "/etc/mnttab", "r" )) == NULL ) {
	return( NULL );
    }

    while ( getmntent( mtab, &mnt ) == 0 ) {
      /* local fs */
      if ( (stat( mnt.mnt_special, &sb ) == 0) && (devno == sb.st_rdev)) {
	fclose( mtab );
	return mnt.mnt_mountp;
      }
	  
      /* check for nfs. we probably should use 
       * strcmp(mnt.mnt_fstype, MNTTYPE_NFS), but that's not as fast. */
      if ((stat(mnt.mnt_mountp, &sb) == 0) && (devno == sb.st_dev) &&
	  strchr(mnt.mnt_special, ':')) {
	*nfs = 1;
	fclose( mtab );
	return mnt.mnt_special;
      }
    }

    fclose( mtab );
    return( NULL );
}

#else __svr4__
#ifdef ultrix
/*
 * Return the block-special device name associated with the filesystem
 * on which "file" resides.  Returns NULL on failure.
 */

static char *
special( file, nfs )
    char *file;
    int  *nfs;
{
    static struct fs_data	fsd;

    if ( getmnt(0, &fsd, 0, STAT_ONE, file ) < 0 ) {
	syslog(LOG_INFO, "special: getmnt %s: %m", file );
	return( NULL );
    }

    /* XXX: does this really detect an nfs mounted fs? */
    if (strchr(fsd.fd_req.devname, ':'))
      *nfs = 1;
    return( fsd.fd_req.devname );
}

#else ultrix
#ifdef BSD4_4

static char *
special( file, nfs )
    char	*file;
    int         *nfs;
{
    static struct statfs	sfs;

    if ( statfs( file, &sfs ) < 0 ) {
	return( NULL );
    }

    /* XXX: make sure this really detects an nfs mounted fs */
    if (strchr(sfs.f_mntfromname, ':'))
      *nfs = 1;
    return( sfs.f_mntfromname );
}

#else /* BSD4_4 */

static char *
special( file, nfs )
    char *file;
    int *nfs;
{
    struct stat		sb;
    FILE 		*mtab;
    dev_t		devno;
    struct mntent	*mnt;

    if ( stat( file, &sb ) < 0 ) {
	return( NULL );
    }
    devno = sb.st_dev;

    if (( mtab = setmntent( "/etc/mtab", "r" )) == NULL ) {
	return( NULL );
    }

    while (( mnt = getmntent( mtab )) != NULL ) {
      /* check for local fs */
      if ( (stat( mnt->mnt_fsname, &sb ) == 0) && devno == sb.st_rdev) {
	endmntent( mtab );
	return( mnt->mnt_fsname );
      }
      
      /* check for an nfs mount entry. the alternative is to use
       * strcmp(mnt->mnt_type, MNTTYPE_NFS) instead of the strchr. */
      if ((stat(mnt->mnt_dir, &sb) == 0) && (devno == sb.st_dev) &&
	  strchr(mnt->mnt_fsname, ':')) {
	*nfs = 1;
	endmntent( mtab );
	return( mnt->mnt_fsname );
      }
    }

    endmntent( mtab );
    return( NULL );
}

#endif BSD4_4
#endif ultrix
#endif __svr4__

static int getfsquota(vol, uid, dq)
    struct vol          *vol;
    const int           uid;
    struct dqblk        *dq;
{
#ifdef __svr4__
    struct quotctl	qc;

    qc.op = Q_GETQUOTA;
    qc.uid = uid;
    qc.addr = (caddr_t)dq;
    if ( ioctl( vol->v_qfd, Q_QUOTACTL, &qc ) < 0 ) {
	return( AFPERR_PARAM );
    }

#else __svr4__
#ifdef ultrix
    if ( quota( Q_GETDLIM, uid, vol->v_gvs, dq ) != 0 ) {
	return( AFPERR_PARAM );
    }
#else ultrix

#ifndef USRQUOTA
#define USRQUOTA   0
#endif

#ifndef QCMD
#define QCMD(a,b)  (a)
#endif

#ifdef BSD4_4
    if ( quotactl( vol->v_gvs, QCMD(Q_GETQUOTA,USRQUOTA), 
		   uid, (char *)dq ) != 0 ) {
	return( AFPERR_PARAM );
    }
#else /* BSD4_4 */
    if ( quotactl(QCMD(Q_GETQUOTA, USRQUOTA), vol->v_gvs, uid,
		  (caddr_t) dq ) != 0 ) {
	return( AFPERR_PARAM );
    }
#endif  /* BSD4_4 */
#endif ultrix
#endif __svr4__

    return AFP_OK;
}


static int getquota( vol, dq, bsize)
    struct vol		*vol;
    struct dqblk	*dq;
    const u_int32_t     bsize;
{
    char *p;

#ifdef __svr4__
    char		buf[ MAXPATHLEN + 1];

    if ( vol->v_qfd == -1 && vol->v_gvs == NULL) {
	if (( p = mountp( vol->v_path, &vol->v_nfs)) == NULL ) {
	    syslog( LOG_INFO, "getquota: mountp %s fails", vol->v_path );
	    return( AFPERR_PARAM );
	}

	if (vol->v_nfs) {
	  if (( vol->v_gvs = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
	    syslog( LOG_ERR, "getquota: malloc: %m" );
	    return AFPERR_MISC;
	  }
	  strcpy( vol->v_gvs, p );

	} else {
	  sprintf( buf, "%s/quotas", p );
	  if (( vol->v_qfd = open( buf, O_RDONLY, 0 )) < 0 ) {
	    syslog( LOG_INFO, "open %s: %m", buf );
	    return( AFPERR_PARAM );
	  }
	}
	  
    }
#else
    if ( vol->v_gvs == NULL ) {
	if (( p = special( vol->v_path, &vol->v_nfs )) == NULL ) {
	    syslog( LOG_INFO, "getquota: special %s fails", vol->v_path );
	    return( AFPERR_PARAM );
	}

	if (( vol->v_gvs = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
	    syslog( LOG_ERR, "getquota: malloc: %m" );
	    return AFPERR_MISC;
	}
	strcpy( vol->v_gvs, p );
    }
#endif

    return vol->v_nfs ? getnfsquota(vol, uuid, bsize, dq) : 
      getfsquota(vol, uuid, dq);
}

static int overquota( dqblk )
    struct dqblk	*dqblk;
{
    struct timeval	tv;

    if ( dqblk->dqb_curblocks < dqblk->dqb_bsoftlimit ) {
	return( 0 );
    }
#ifdef ultrix
    if ( dqblk->dqb_bwarn ) {
	return( 0 );
    }
#else ultrix
    if ( gettimeofday( &tv, 0 ) < 0 ) {
	syslog( LOG_ERR, "overquota: gettimeofday: %m" );
	return( AFPERR_PARAM );
    }
    if ( !dqblk->dqb_btimelimit || dqblk->dqb_btimelimit > tv.tv_sec ) {
	return( 0 );
    }
#endif /* ultrix */
    return( 1 );
}

uquota_getvolspace( vol, bfree, btotal, bsize)
    struct vol	*vol;
    VolSpace	*bfree, *btotal;
    const u_int32_t bsize;
{
    struct dqblk	dqblk;

    if (getquota( vol, &dqblk, bsize) != 0 ) {
      return( AFPERR_PARAM );
    }

    /* no limit set for this user. it might be set in the future. */
    if (dqblk.dqb_bsoftlimit == 0 && dqblk.dqb_bhardlimit == 0) {
       *btotal = *bfree = ~((VolSpace) 0);
    } else if ( overquota( &dqblk )) {
	*btotal = (VolSpace) dbtob( dqblk.dqb_bhardlimit );
	*bfree = (VolSpace) 
	  dbtob( dqblk.dqb_bhardlimit ) - dbtob( dqblk.dqb_curblocks );
    } else {
	*btotal = (VolSpace) dbtob( dqblk.dqb_bsoftlimit );
	*bfree = (VolSpace) 
	  dbtob( dqblk.dqb_bsoftlimit ) - dbtob( dqblk.dqb_curblocks );
    }

    return( AFP_OK );
}

int utombits( bits )
    mode_t	bits;
{
    int		mbits;

    mbits = 0;

    mbits |= ( bits & ( S_IREAD >> 6 )) ? AR_UREAD : 0;
    mbits |= ( bits & ( S_IWRITE >> 6 )) ? AR_UWRITE : 0;
    mbits |= ( bits & ( S_IEXEC >> 6) ) ? AR_USEARCH : 0;

    return( mbits );
}

void utommode( stat, ma )
    struct stat		*stat;
    struct maccess	*ma;
{
    mode_t		mode;

    mode = stat->st_mode;

    ma->ma_world = utombits( mode );
    mode = mode >> 3;

    ma->ma_group = utombits( mode );
    mode = mode >> 3;

    ma->ma_owner = utombits( mode );

    if ( uuid == stat->st_uid ) {
	ma->ma_user = ma->ma_owner | AR_UOWN;
    } else if ( gmem( stat->st_gid )) {
	ma->ma_user = ma->ma_group;
    } else {
	ma->ma_user = ma->ma_world;
    }

    /*
     * There are certain things the mac won't try if you don't have
     * the "owner" bit set, even tho you can do these things on unix wiht
     * only write permission.  What were the things?
     */
    if ( ma->ma_user & AR_UWRITE ) {
	ma->ma_user |= AR_UOWN;
    }
}


gmem( gid )
    int	gid;
{
    int		i;

    for ( i = 0; i < ngroups; i++ ) {
	if ( groups[ i ] == gid ) {
	    return( 1 );
	}
    }
    return( 0 );
}

mode_t mtoubits( bits )
    u_char	bits;
{
    mode_t	mode;

    mode = 0;

    mode |= ( bits & AR_UREAD ) ? ( S_IREAD >> 6 ) : 0;
    mode |= ( bits & AR_UWRITE ) ? ( S_IWRITE >> 6 ) : 0;
    mode |= ( bits & AR_USEARCH ) ? ( S_IEXEC >> 6 ) : 0;

    return( mode );
}

mode_t mtoumode( ma )
    struct maccess	*ma;
{
    mode_t		mode;

    mode = 0;
    mode |= mtoubits( ma->ma_owner );
    mode = mode << 3;

    mode |= mtoubits( ma->ma_group );
    mode = mode << 3;

    mode |= mtoubits( ma->ma_world );

    return( mode );
}


setdeskmode( mode )
    mode_t	mode;
{
    char		wd[ MAXPATHLEN + 1];
    struct stat         st;
    char		modbuf[ 12 + 1], *m;
    struct dirent	*deskp, *subp;
    DIR			*desk, *sub;

    if ( getcwd( wd , MAXPATHLEN) == NULL ) {
	return( -1 );
    }
    if ( chdir( ".AppleDesktop" ) < 0 ) {
	return( -1 );
    }
    if (( desk = opendir( "." )) == NULL ) {
	if ( chdir( wd ) < 0 ) {
	    syslog( LOG_ERR, "setdeskmode: chdir %s: %m", wd );
	}
	return( -1 );
    }
    for ( deskp = readdir( desk ); deskp != NULL; deskp = readdir( desk )) {
	if ( strcmp( deskp->d_name, "." ) == 0 ||
		strcmp( deskp->d_name, ".." ) == 0 || strlen( deskp->d_name ) > 2 ) {
	    continue;
	}
	strcpy( modbuf, deskp->d_name );
	strcat( modbuf, "/" );
	m = strchr( modbuf, '\0' );
	if (( sub = opendir( deskp->d_name )) == NULL ) {
	    continue;
	}
	for ( subp = readdir( sub ); subp != NULL; subp = readdir( sub )) {
	    if ( strcmp( subp->d_name, "." ) == 0 ||
		    strcmp( subp->d_name, ".." ) == 0 ) {
		continue;
	    }
	    *m = '\0';
	    strcat( modbuf, subp->d_name );
	    /* XXX: need to preserve special modes */
	    if (stat(modbuf, &st) < 0) {
	        syslog( LOG_DEBUG, "setdeskmode: stat %s: %m", modbuf );
	        continue;
	    }	    

	    if (S_ISDIR(st.st_mode)) {
	      if ( chmod( modbuf,  DIRBITS | mode ) < 0 ) {
		syslog( LOG_DEBUG, "setdeskmode: chmod %s: %m", modbuf );
	      }
	    } else if ( chmod( modbuf,  mode ) < 0 ) {
	        syslog( LOG_DEBUG, "setdeskmode: chmod %s: %m", modbuf );
	    }

	}
	closedir( sub );
	/* XXX: need to preserve special modes */
	if ( chmod( deskp->d_name,  DIRBITS | mode ) < 0 ) {
	    syslog( LOG_DEBUG, "setdeskmode: chmod %s: %m", deskp->d_name );
	}
    }
    closedir( desk );
    if ( chdir( wd ) < 0 ) {
	syslog( LOG_ERR, "setdeskmode: chdir %s: %m", wd );
	return -1;
    }
    /* XXX: need to preserve special modes */
    if ( chmod( ".AppleDesktop",  DIRBITS | mode ) < 0 ) {
	syslog( LOG_DEBUG, "setdeskmode: chmod .AppleDesktop: %m" );
    }
    return( 0 );
}

setdirmode( mode, noadouble )
    const mode_t mode;
    const int noadouble;
{
    char		buf[ MAXPATHLEN + 1];
    struct stat		st;
    char		*m;
    struct dirent	*dirp;
    DIR			*dir;

    if (( dir = opendir( "." )) == NULL ) {
	syslog( LOG_ERR, "setdirmode: opendir .: %m" );
	return( -1 );
    }

    for ( dirp = readdir( dir ); dirp != NULL; dirp = readdir( dir )) {
	if ( *dirp->d_name == '.' ) {
	    continue;
	}
	if ( stat( dirp->d_name, &st ) < 0 ) {
	    syslog( LOG_DEBUG, "setdirmode: stat %s: %m", dirp->d_name );
	    continue;
	}

	if (S_ISREG(st.st_mode)) {
	    /* XXX: need to preserve special modes */
	    if (S_ISDIR(st.st_mode)) {
	      if ( chmod( dirp->d_name, DIRBITS | mode ) < 0 ) {
		syslog( LOG_DEBUG, "setdirmode: chmod %s: %m", dirp->d_name );
	      }
	    } else if ( chmod( dirp->d_name, mode ) < 0 ) {
		syslog( LOG_DEBUG, "setdirmode: chmod %s: %m", dirp->d_name );
	    }
	}
    }
    closedir( dir );
    if (( dir = opendir( ".AppleDouble" )) == NULL ) {
        if (noadouble)
	  goto setdirmode_noadouble;
	syslog( LOG_ERR, "setdirmode: opendir .AppleDouble: %m" );
	return( -1 );
    }
    strcpy( buf, ".AppleDouble" );
    strcat( buf, "/" );
    m = strchr( buf, '\0' );
    for ( dirp = readdir( dir ); dirp != NULL; dirp = readdir( dir )) {
	if ( strcmp( dirp->d_name, "." ) == 0 ||
		strcmp( dirp->d_name, ".." ) == 0 ) {
	    continue;
	}
	*m = '\0';
	strcat( buf, dirp->d_name );

	if ( stat( buf, &st ) < 0 ) {
	    syslog( LOG_DEBUG, "setdirmode: stat %s: %m", buf );
	    continue;
	}

	if (S_ISDIR(st.st_mode)) {
	  if ( chmod(buf,  DIRBITS | mode) < 0 ) {
	    syslog( LOG_DEBUG, "setdirmode: chmod %s: %m", buf );
	  }
	} else if ( chmod(buf, mode) < 0 ) {
	    syslog( LOG_DEBUG, "setdirmode: chmod %s: %m", buf );
	}
    }
    closedir( dir );

    /* XXX: use special bits to tag directory permissions */
      
    /* XXX: need to preserve special modes */
    if ( chmod( ".AppleDouble",  DIRBITS | mode ) < 0 ) {
	syslog( LOG_ERR, "setdirmode: chmod .AppleDouble: %m" );
	return( -1 );
    }

setdirmode_noadouble:
    /* XXX: need to preserve special modes */
    if ( chmod( ".",  DIRBITS | mode ) < 0 ) {
	syslog( LOG_ERR, "setdirmode: chmod .: %m" );
	return( -1 );
    }
    return( 0 );
}

setdeskowner( uid, gid )
    int		uid;
    int		gid;
{
    char		wd[ MAXPATHLEN + 1];
    char		modbuf[12 + 1], *m;
    struct dirent	*deskp, *subp;
    DIR			*desk, *sub;

    if ( getcwd( wd, MAXPATHLEN ) == NULL ) {
	return( -1 );
    }
    if ( chdir( ".AppleDesktop" ) < 0 ) {
	return( -1 );
    }
    if (( desk = opendir( "." )) == NULL ) {
	if ( chdir( wd ) < 0 ) {
	    syslog( LOG_ERR, "setdeskowner: chdir %s: %m", wd );
	}
	return( -1 );
    }
    for ( deskp = readdir( desk ); deskp != NULL; deskp = readdir( desk )) {
	if ( strcmp( deskp->d_name, "." ) == 0 ||
	     strcmp( deskp->d_name, ".." ) == 0 || 
	     strlen( deskp->d_name ) > 2 ) {
	    continue;
	}
	strcpy( modbuf, deskp->d_name );
	strcat( modbuf, "/" );
	m = strchr( modbuf, '\0' );
	if (( sub = opendir( deskp->d_name )) == NULL ) {
	    continue;
	}
	for ( subp = readdir( sub ); subp != NULL; subp = readdir( sub )) {
	    if ( strcmp( subp->d_name, "." ) == 0 ||
		 strcmp( subp->d_name, ".." ) == 0 ) {
		continue;
	    }
	    *m = '\0';
	    strcat( modbuf, subp->d_name );
	    /* XXX: add special any uid, ignore group bits */
	    if ( chown( modbuf, uid, gid ) < 0 ) {
		syslog( LOG_DEBUG, "setdeskown: chown %s: %m", modbuf );
	    }
	}
	closedir( sub );
	/* XXX: add special any uid, ignore group bits */
	if ( chown( deskp->d_name, uid, gid ) < 0 ) {
	    syslog( LOG_DEBUG, "setdeskowner: chown %s: %m", deskp->d_name );
	}
    }
    closedir( desk );
    if ( chdir( wd ) < 0 ) {
	syslog( LOG_ERR, "setdeskowner: chdir %s: %m", wd );
	return -1;
    }
    if ( chown( ".AppleDesktop", uid, gid ) < 0 ) {
	syslog( LOG_ERR, "setdeskowner: chown .AppleDesktop: %m" );
    }
    return( 0 );
}


/* uid/gid == 0 need to be handled as special cases. they really mean
 * that user/group should inherit from other, but that doesn't fit
 * into the unix permission scheme. we can get around this by
 * co-opting some bits. */
setdirowner( uid, gid, noadouble )
    int		uid;
    int		gid;
    const int   noadouble;
{
    char		buf[ MAXPATHLEN + 1];
    struct stat		st;
    char		*m;
    struct dirent	*dirp;
    DIR			*dir;

    if (( dir = opendir( "." )) == NULL ) {
	return( -1 );
    }
    for ( dirp = readdir( dir ); dirp != NULL; dirp = readdir( dir )) {
	if ( *dirp->d_name == '.' ) {
	    continue;
	};
	if ( stat( dirp->d_name, &st ) < 0 ) {
	    syslog( LOG_DEBUG, "setdirowner: stat %s: %m", dirp->d_name );
	    continue;
	}
	if (( st.st_mode & S_IFMT ) == S_IFREG ) {
	    if ( chown( dirp->d_name, uid, gid ) < 0 ) {
		syslog( LOG_DEBUG, "setdirowner: chown %s: %m", dirp->d_name );
	    }
	}
    }
    closedir( dir );
    if (( dir = opendir( ".AppleDouble" )) == NULL ) {
      if (noadouble)
	goto setdirowner_noadouble;
      return( -1 );
    }
    strcpy( buf, ".AppleDouble" );
    strcat( buf, "/" );
    m = strchr( buf, '\0' );
    for ( dirp = readdir( dir ); dirp != NULL; dirp = readdir( dir )) {
	if ( strcmp( dirp->d_name, "." ) == 0 ||
		strcmp( dirp->d_name, ".." ) == 0 ) {
	    continue;
	}
	*m = '\0';
	strcat( buf, dirp->d_name );
	if ( chown( buf, uid, gid ) < 0 ) {
	    syslog( LOG_DEBUG, "setdirowner: chown %d/%d %s: %m",
		    uid, gid, buf );
	}
    }
    closedir( dir );

    /*
     * We cheat: we know that chown doesn't do anything.
     */
    if ( stat( ".AppleDouble", &st ) < 0 ) {
	syslog( LOG_ERR, "setdirowner: stat .AppleDouble: %m" );
	return( -1 );
    }
    if ( gid && gid != st.st_gid && chown( ".AppleDouble", uid, gid ) < 0 ) {
	return( -1 );
    }

setdirowner_noadouble:
    if ( stat( ".", &st ) < 0 ) {
	return( -1 );
    }
    if ( gid && gid != st.st_gid && chown( ".", uid, gid ) < 0 ) {
	return( -1 );
    }

    return( 0 );
}
