/*
 * Command line program to create a file with a specific mode setting.
 * 
 * This program has code from GNU coreutils lib/modechange.c which
 * has been modified by Andreas Henriksson <andreas@fjortis.info>.
 *
 * Build:   gcc -DBASEDIR=\"/tmp/foo\" mkdirmodechange.c
 *          gcc -DNOBASEPATH mkdirmodechange.c
 *
 * Usage:   ./a.out <directory> <mode> [<owner-uid> <owner-dig>]
 *
 * Example: ./a.out foo1 0755
 *          ./a.out foo2 0755 1000 1000
 *
 * Written for Neon at Reelab (reelab.se), 2005-07-14.
 */

/* modechange.c -- file mode manipulation

   Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003 Free Software
   Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  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.  */

/* Written by David MacKenzie <djm@ai.mit.edu> */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>

/* The traditional octal values corresponding to each mode bit.  */
#define SUID 04000
#define SGID 02000
#define SVTX 01000
#define RUSR 00400
#define WUSR 00200
#define XUSR 00100
#define RGRP 00040
#define WGRP 00020
#define XGRP 00010
#define ROTH 00004
#define WOTH 00002
#define XOTH 00001
#define ALLM 07777		/* all octal mode bits */

int mode_compile(const char *mode_string, mode_t * mode)
{
	unsigned long octal_value;	/* The mode value, if octal.  */

	octal_value = strtoul(mode_string, NULL, 8);
	if (octal_value != ULONG_MAX) {
		if (octal_value != (octal_value & ALLM))
			return -1;

		/* Help the compiler optimize the usual case where mode_t uses
		   the traditional octal representation.  */
		*mode = ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
			  && S_IRUSR == RUSR && S_IWUSR == WUSR
			  && S_IXUSR == XUSR && S_IRGRP == RGRP
			  && S_IWGRP == WGRP && S_IXGRP == XGRP
			  && S_IROTH == ROTH && S_IWOTH == WOTH
			  && S_IXOTH == XOTH)
			 ? octal_value
			 : (mode_t) ((octal_value & SUID ? S_ISUID : 0)
				     | (octal_value & SGID ? S_ISGID : 0)
				     | (octal_value & SVTX ? S_ISVTX : 0)
				     | (octal_value & RUSR ? S_IRUSR : 0)
				     | (octal_value & WUSR ? S_IWUSR : 0)
				     | (octal_value & XUSR ? S_IXUSR : 0)
				     | (octal_value & RGRP ? S_IRGRP : 0)
				     | (octal_value & WGRP ? S_IWGRP : 0)
				     | (octal_value & XGRP ? S_IXGRP : 0)
				     | (octal_value & ROTH ? S_IROTH : 0)
				     | (octal_value & WOTH ? S_IWOTH : 0)
				     | (octal_value & XOTH ? S_IXOTH : 0)));

		return 0;
	}
	return -1;
}

#ifndef NOBASEPATH

#ifndef BASEDIR
#	error "Basepath enabled but no BASEDIR defined!"
#endif

int validatepath(char *inputdir, char dir[])
{
	char builddir[PATH_MAX];

	strcpy(builddir, BASEDIR);
	if (*inputdir != '/') {
		strcat(builddir, "/");
		strcat(builddir, inputdir);
	} else {
		strcpy(builddir, inputdir);
	}

	/* invalid characters in path or outside BASEDIR */
	if (strchr(builddir, '.') || strncmp(builddir, BASEDIR, strlen(BASEDIR))) {
		return -1;
	}

	strcpy(dir, builddir);
	return 0;
}
#endif

int main(int argc, char **argv)
{
	char* modestr;
	char* inputdir;
	char dir[PATH_MAX];
	mode_t mode;

	if (argc < 3) {
		fprintf(stderr,
			"Usage: %s <directory> <mode> [<owner-uid> <owner-gid>]\n",
			argv[0]);
		exit(1);
	}

	inputdir = argv[1];
	modestr = argv[2];

#ifdef NOBASEPATH
	strncpy(dir, inputdir, PATH_MAX);
#else
	if (validatepath(inputdir, dir)) {
		fprintf(stderr, "Error: invalid directory.\n");
		exit(2);
	}
#endif

	if (mode_compile(modestr, &mode)) {
		fprintf(stderr, "Compiling mode '%s' failed.\n", modestr);
		exit(2);
	}

	if (setuid(0)) {
		fprintf(stderr, "Warning: setuid(0) failed.\n");
	}

	if (mkdir(dir, mode)) {
		fprintf(stderr,
			"Error: creating '%s' with mode '%s' (%d) failed!\n",
			dir, modestr, mode);
		exit(3);
	}

	if (argc == 5) {
		uid_t uid;
		gid_t gid;

		uid = (uid_t) atoi(argv[3]);
		gid = (gid_t) atoi(argv[4]);

		if (chown(argv[1], uid, gid)) {
			fprintf(stderr,
				"Warning: changing owner of '%s' to '%d/%d' failed.\n",
				argv[1], uid, gid);
		}
	}

	return 0;
}

