/*
 * Name: rumba.c
 * Description: Main function and argument parsing.
 * Author: original source for smbmount: Volker Lendecke,
 *     this version: Christian Starkjohann <cs@hal.kph.tuwien.ac.at>
 * Date: 1997-01-03
 * Copyright: GNU-GPL
 * Tabsize: 4
 */


#include "syshdr.h"
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <netdb.h>
#include "psinode.h"
#include "my_defines.h"
#include <syslog.h>

#ifndef INADDR_NONE	/* Address indicating an error return. */
#	define	INADDR_NONE		0xffffffffUL
#endif

/* ------------------------------------------------------------------------- */

int		debug_mode = 0;
char	fake_dot_in_root = 1;
char	fake_dotdot_in_root = 1;
char	rw_is_safe = 0;
char	allow_readonly = 0;
char	use_extended = 0;
char	use_syslog = 0;
int		conf_uid, conf_gid, conf_dirmode = 0777, conf_filemode = 0777;
char	cfg_workgroup[16];


char 	*progname;

/* ------------------------------------------------------------------------- */

static void	vlogprintf(int level, const char *format, va_list vlist)
{
char	buffer[32768];	/* should we alloc a larger chunk of virtual mem? */

	if(use_syslog){
		vsprintf(buffer, format, vlist);
		syslog(level, "%s", buffer);
	}else{
		vfprintf(stderr, format, vlist);
	}
}

/* ------------------------------------------------------------------------- */

void	debprintf(const char *format, ...)
{
va_list		vlist;

	va_start(vlist, format);
	vlogprintf(LOG_INFO, format, vlist);
	va_end(vlist);
}

/* ------------------------------------------------------------------------- */

void	eprintf(const char *format, ...)
{
va_list		vlist;

	va_start(vlist, format);
	vlogprintf(LOG_ERR, format, vlist);
	va_end(vlist);
}

/* ------------------------------------------------------------------------- */

static inline void	str_upper(char *name)
{
	while(*name){
		*name = toupper(*name);
		name ++;
	}
}

/* ------------------------------------------------------------------------- */

static void	print_version(void)
{
	printf("%s: Version 1.2 (2003-12-29)\n", progname);
	exit(0);
}

/* ------------------------------------------------------------------------- */

static void
usage(void)
{
	printf("usage: %s //server/service mount-point [options]\n", progname);
	printf("Try `%s -h' for more information\n", progname);
}

/* ------------------------------------------------------------------------- */

static void
help(void)
{
	printf("\n");
	printf("usage: %s //server/service mount-point [options]\n", progname);
	printf("\n"
		"-s servername  Netbios name of server\n"
		"-c clientname  Netbios name of client\n"
		"-U username    Username sent to server\n"
		"-W workgroup   Workgroup or domain string sent to the server\n"
		"-u uid         uid the mounted files get\n"
		"-g gid         gid the mounted files get\n"
		"-f mode        permission the files get (octal notation)\n"
		"-d mode        permission the dirs get (octal notation)\n"
		"-C             Don't convert password to uppercase\n"
		"-P password    Use this password\n"
		"-i             Read password from stdin\n"
		"-n             Do not use any password\n"
		"               If neither -P nor -n are given, you are\n"
		"               asked for a password.\n"
		"-S             Read/write access is safe. Without this\n"
		"               option, files are closed when switching from\n"
		"               read to write or vice versa\n"
		"-w             allow setting files to read only\n"
		"-e             use getattrE and setattrE smb commands\n"
		"-F .           don\'t fake . entry in root directory\n"
		"-F ..          don\'t fake .. entry in root directory\n"
		"-h             print this help text\n"
		"-v             print version number and exit\n"
		"\n"
		"-p port        Port to connect to (used only for testing)\n"
		"-m max_xmit    max_xmit offered (used only for testing)\n"
		"-D debug-mode  activate debugging (bitmask, hex/dec/octal notation)\n"
		"               this option also forces foreground operation\n"
		"\n");
}

/* ------------------------------------------------------------------------- */

static int
extract_service(char *service, char **server, char **share, char **root,
                char **user)
{
char		*share_start;
char		*root_start;
char		*user_start;
static char	s_server[64];
static char	s_share [64];
static char	s_root  [64];
static char	s_user  [64];
char		*complete_service;

#ifdef __GNUC__	/* gcc can do variable sized arrays */
char service_copy[strlen(service)+1];
#else
char service_copy[4096];

	if(strlen(service) > 4095)	/* emergency brake */
		service[4095] = 0;
#endif

	strcpy(service_copy, service);
	complete_service = service_copy;
	strcpy(cfg_workgroup, "WORKGROUP");

	if (strlen(complete_service) < 4 || complete_service[0] != '/') {
		return -1;
	}
	while (complete_service[0] == '/') complete_service += 1;
	share_start = strchr(complete_service, '/');
	if (share_start == NULL) {
		return -1;
	}
	*share_start++ = '\0';
	root_start = strchr(share_start, '/');
	if (root_start != NULL) {
		*root_start++ = '\0';
	}
	if ((strlen(complete_service) > 63) || (strlen(share_start) > 63)) {
		eprintf("server or share too long: %s\n", service);
		return -1;
	}
	if (root_start != NULL) {
		int i;
		if (strlen(root_start) > 62) {
			eprintf("root too long: %s\n", root_start);
			return -1;
		}
		s_root[0] = '/';
		strcpy(s_root + 1, root_start);
		for (i = 0; s_root[i] != '\0'; i++) {
			if (s_root[i] == '/') {
				s_root[i] = '\\';
			}
		}
		/* i == strlen(s_root) */
		if (s_root[i-1] == '\\') {
			s_root[i-1] = '\0';
		}
	} else {
		s_root[0] = '\0';
	}
	user_start = strchr(share_start, '%');
	if (user_start != NULL) {
		user_start[0] = '\0';
		user_start += 1;
		strcpy(s_user, user_start);
	} else {
		s_user[0] = '\0';
	}

	strcpy(s_server, complete_service);
	strcpy(s_share,  share_start);

	*server = s_server;
	*share  = s_share;
	*root   = s_root;
	*user   = s_user;
	return 0;
}

/* ------------------------------------------------------------------------- */

static char	*my_realpath(char *path, char *real)
{
char	pathdir[MAXPATHLEN], *p, *q;

	if(realpath(path, real) != NULL)
		return real;
	strncpy(pathdir, path, MAXPATHLEN);
	pathdir[MAXPATHLEN - 1] = 0;
	p = strrchr(pathdir, '/');
	if(p != NULL){
		*p++ = 0;
		if(realpath(pathdir, real) != NULL){
			q = real + strlen(real);
			if(q - real < MAXPATHLEN){
				*q++ = '/';
				strncpy(q, p, MAXPATHLEN - (q - real));
			}
			return real;
		}
	}
	return NULL;
}

/* ------------------------------------------------------------------------- */

int	main(int argc, char **argv)
{
int				opt, um, i;
struct passwd	*pwd;
struct group	*grp;
char			mountpoint_abs[MAXPATHLEN + 1];
struct hostent	*h;
int				got_password, upcase_password;
int				port = -1, max_xmit = -1;
char			server_name[17], client_name[17];
char			username[64], password[64], run_as_daemon;
char			*mount_point, *server, *share, *root, *user_dummy, *p;
static fh_t		root_fh[32/sizeof(fh_t)] = {0};
unsigned		ipAddr;

	progname = argv[0];
	openlog(progname, LOG_PID, LOG_DAEMON);
	*username = *password = *server_name = *client_name = 0;
	got_password = 0;
	upcase_password = 1;
	run_as_daemon = 1;
	if(argc == 2 && strcmp(argv[1], "-v") == 0){
		print_version();
	}
	if(argc < 3 || (argc == 2 && strcmp(argv[1], "-h") == 0)){
		help();
		exit(0);
	}
#ifndef BSD4_4_LITE2
	if (geteuid() != 0) {
		eprintf("%s must be installed suid root\n", progname);
	}
#endif
	mount_point = argv[2];
	if(extract_service(argv[1], &server, &share, &root, &user_dummy) != 0) {
		usage();
		return 1;
	}
	argv += 2;
	argc -= 2;

	ipAddr = inet_addr(server);
	if(ipAddr == INADDR_NONE){	/* name was given, not numeric */
		if ((h = gethostbyname(server)) == NULL) {
			eprintf("%s: unknown host\n", server);
			return 1;
		}
	}else{
		char hostName[256];
		if((h = gethostbyaddr((char*)&ipAddr, sizeof(ipAddr), AF_INET)) == NULL){
			eprintf("%s: unknown host\n", server);
			return 1;
		}
		/* Brian Willette: Now we will set the server name to the dns
		 * hostname, hopefully this will be the same as the netbios name for
		 * the server.
		 * We do this because the user supplied no hostname, and we
		 * need one for netbios, this is the best guess choice we have
		 * NOTE: If the names are different between DNS and netbios on
		 * the windows side, the user MUST use the -s option.
		 */
		for(i=0; h->h_name[i]!='.' && h->h_name[i]!=0 && i<255; i++){
			hostName[i] = h->h_name[i];
		}
		hostName[i] = 0;
		/* Make sure the hostname is 16 characters or less (for Netbios) */
		if(strlen(hostName) <= 16) {
			strcpy(server_name, hostName);
		}
	}
	if(getenv("USER")){
		strcpy(username, getenv("USER"));
		str_upper(username);
	}
	if(username[0] == 0 && getenv("LOGNAME")){
		strcpy(username,getenv("LOGNAME"));
		str_upper(username);
	}

	conf_uid = getuid();
	conf_gid = getgid();
	um = umask(0);
	umask(um);
	conf_filemode = 0777 & ~um;
	conf_dirmode  = 0;

	while((opt=getopt(argc, argv, "Chewp:s:c:U:W:u:g:if:d:m:P:nD:F:Sv")) != EOF){
		switch(opt){
		case 'C':
			upcase_password = 0;
			break;
		case 'p':
			port = atoi(optarg);
			break;
		case 's':
			if(strlen(optarg) > 16){
				eprintf("Server name too long: %s\n", optarg);
				return 1;
			}
			strcpy(server_name, optarg);
			break;
		case 'c':
			if(strlen(optarg) > 16){
				eprintf("Client name too long: %s\n", optarg);
				return 1;
			}
			strcpy(client_name, optarg);
			break;
		case 'U':
			if(strlen(optarg) > 63){
				eprintf("Username too long: %s\n", optarg);
				return 1;
			}
			strcpy(username, optarg);
			break;
		case 'W':
			if(strlen(optarg) >= sizeof(cfg_workgroup)){
				eprintf("Workgroup/Domain too long (max %d): %s\n", sizeof(cfg_workgroup) - 1, optarg);
				return 1;
			}
			strcpy(cfg_workgroup, optarg);
			str_upper(cfg_workgroup);
			break;
		case 'u':
			if(isdigit(optarg[0])){
				conf_uid = atoi(optarg);
			}else{
				pwd = getpwnam(optarg);
				if(pwd == NULL){
					eprintf("Unknown user: %s\n", optarg);
					return 1;
				}
				conf_uid = pwd->pw_uid;
			}
			break;
		case 'g':
			if(isdigit(optarg[0])){
				conf_gid = atoi(optarg);
			}else{
				grp = getgrnam(optarg);
				if(grp == NULL){
					eprintf("Unknown group: %s\n", optarg);
					return 1;
				}
				conf_gid = grp->gr_gid;
			}
			break;
		case 'f':
			conf_filemode = strtol(optarg, NULL, 8);
			break;
		case 'd':
			conf_dirmode = strtol(optarg, NULL, 8);
			break;
		case 'm':
			max_xmit = atoi(optarg);
			break;
		case 'P':
			strcpy(password, optarg);
#ifndef SOLARIS	/* solaris obviously can't change parameters */
			for(i=0;optarg[i]!=0;i++)
				optarg[i] = '*';
#endif
			got_password = 1;
			break;
		case 'i':
			if(fgets(password, sizeof(password), stdin) != NULL){
				if((p = strrchr(password, '\n')) != NULL)
					*p = 0;
				got_password = 1;
			}else{
				perror("reading password:");
			}
			break;
		case 'n':
			got_password = 1;
			break;
		case 'w':
			allow_readonly = 1;
			break;
		case 'e':
			use_extended = 1;
			break;
		case 'D':
			run_as_daemon = 0;
			debug_mode = strtol(optarg, NULL, 0);
			break;
		case 'F':
			if(strcmp(optarg, ".") == 0)
				fake_dot_in_root = 0;
			else if(strcmp(optarg, "..") == 0)
				fake_dotdot_in_root = 0;
			else
				eprintf("Parameter of -F must be \".\" or \"..\"\n");
			break;
		case 'S':
			rw_is_safe = 1;
			break;
		case 'v':
			print_version();
			exit(0);
		case 'h':
			help();
			exit(0);
		default:
			return 1;
		}
	}

	if(conf_dirmode == 0){
		conf_dirmode = conf_filemode;
		conf_dirmode |= (conf_dirmode & 0444) >> 2;
	}
	if(!got_password){
		char *pw;
		if((pw = getenv("PASSWD")))
			strcpy(password, pw);
		else
			strcpy(password, getpass("Password: "));
	}
	if(upcase_password){
		str_upper(password);
	}
	
	kernel_init();
	fo_init();
	psi_init(2048, 1000, 1400);
	psi_define_root(root, root_fh);
	if(fo_mount(server, server_name, client_name, share, root, username,
										password, max_xmit, port) != 0){
		exit(1);
	}
	if (my_realpath(mount_point, mountpoint_abs) == NULL) {
		perror(mount_point);
		exit (1);
	}
	mount_and_dispatch(mountpoint_abs, nfs_dispatch, root_fh, run_as_daemon);
	return 0;
}
