/*
    ncpmount.c
    Copyright (C) 1995, 1997 by Volker Lendecke

    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 of the License, 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

    Revision history:

	0.00  1995			Volker Lendecke
		Initial release.

	0.01  1996, January 20		Steven N. Hirsch <hirsch@emba.uvm.edu>
		If the ncpfs support is not loaded and we are using kerneld to
		autoload modules, then we don't want to do it here.  I added
		a conditional which leaves out the test and load code.

		Even if we _do_ want ncpmount to load the module, passing a
		fully-qualified pathname to modprobe causes it to bypass a 
		path search.  This may lead to ncpfs.o not being found on
 		some systems.

	0.02  1998, November 28		Wolfram Pienkoss <wp@bszh.de>
		NLS added. Thanks Petr Vandrovec for the idea of charset select
		and many hints.

	0.03  1999, September 9		Petr Vandrovec <vandrove@vc.cvut.cz>
 		Added ttl option.
		Reverted back to exec("/sbin/nwmsg") for incomming messages. 
		This allows tknwmsg work again...

	0.04  1999, September 9		Paul Rensing <paulr@dragonsys.com>
		Added resetting behavior for SIGTERM. This fixes ncpmount from 
		perl scripts.

	1.00  1999, November 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added license.
		Replaced nds_get_tree_name with NWIsDSServer.

	1.01  2000, January 16		Petr Vandrovec <vandrove@vc.cvut.cz>
		Changes for 32bit uids.
		
	1.02  2000, April 4		Petr Vandrovec <vandrove@vc.cvut.cz>
		Changed /sbin/nwmsg to $(sbindir)/nwmsg (recomended by
			MandrakeSoft).

	1.03  2000, April 28		Petr Vandrovec <vandrove@vc.cvut.cz>
		Silently ignore 'noauto' option, mount-2.10f (at least)
			passes it down to mount.ncp.

	1.04  2000, June 25		Petr Vandrovec <vandrove@vc.cvut.cz>
		Add NCP/TCP support.

	1.05  2000, October 28		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added nfsextras support.
		Changed some constants to symbolic names (SA_*)

	1.06  2001, January 7		Patrick Pollet <patrick.pollet@insa-lyon.fr>
		Made all error exit codes differents to help debug Pam module

	1.07  2001, February 25		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added UNIX socket handling for global features.

	1.08  2001, September 23	Petr Vandrovec <vandrove@vc.cvut.cz>
		Fix multiple mounts when using UDP or TCP.

	1.09  2002, January 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Verify uid/gid/owner arguments carefully. It should not be
		security problem, but who knows which program derives some 
		security information from file uid/gid...

	1.10  2002, February 27		Petr Vandrovec <vandrove@vc.cvut.cz>
		Erase password quickly from command line.
		Clear command line completely.
		Simpler command line handling.

	1.11  2002, April 19		Peter Schwindt <schwindt@ba-loerrach.de>
		Add option -A to the help text.

 */

#include "ncpm_common.h"
#include "mount_login.h"

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ncp/ext/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/mount.h>
#include <mntent.h>
#include <ncp/kernel/ipx.h>
#include <ncp/nwclient.h>
#include <sys/ioctl.h>
#ifdef CONFIG_NATIVE_UNIX
#include <linux/un.h>
#endif

#include "private/libintl.h"
#define _(X) gettext(X)

#include "../lib/ncpi.h"

static void usage(void);
static void help(void);

void veprintf(const char* message, va_list ap) {
	vfprintf(stderr, message, ap);
}

static void opt_set_tree(struct ncp_mount_info* info, const char* param) {
	if (strlen(param) > MAX_TREE_NAME_CHARS) {
		errexit(65, _("Specified tree name `%s' is too long\n"), param);
	}
	info->tree = param;
}

static void opt_set_bindery(struct ncp_mount_info* info, unsigned int param) {
	info->force_bindery_login = param;
}

static void opt_set_volume(struct ncp_mount_info* info, const char* param) {
	info->volume = param;
}

static void opt_set_allow_multiple(struct ncp_mount_info* info, unsigned int param) {
	info->allow_multiple_connections = param;
}

static void opt_set_auth(struct ncp_mount_info* info, const char* param) {
	info->auth_src = param;
}

static struct optinfo opts[] = {
	{'T', "tree",		FN_STRING,	opt_set_tree,		0},
	{'b', "bindery",	FN_NOPARAM,	opt_set_bindery,	1},
	{'V', "volume",		FN_STRING,	opt_set_volume,		0},
	{'m', "multiple",	FN_NOPARAM,	opt_set_allow_multiple,	1},
	{'a', "auth",		FN_STRING,	opt_set_auth,		0},
	{0,   NULL,		0,		NULL,			0}
};

int
main(int argc, char *argv[])
{
	struct ncp_mount_info info;
	struct stat st;
	char mount_name[256];
#ifdef NDS_SUPPORT
	NWDSContextHandle ctx;
	NWCONN_HANDLE auth_conn;
#endif

	int result;

	long err;

	struct ncp_conn_spec spec;

	struct ncp_conn *conn;

	int opt;

#ifdef CONFIG_NATIVE_UNIX
	struct sockaddr_un server_un;
#endif

	char *tmp_mount;

	int mount_ncp = 0;
	int opt_n = 0;
	int opt_v = 0;

	verify_argv(argc, argv);

	setlocale(LC_ALL, "");
	bindtextdomain(NCPFS_PACKAGE, LOCALEDIR);
	textdomain(NCPFS_PACKAGE);
	
	init_mount_info(&info);

	while ((opt = getopt(argc, argv, "a:CS:U:c:u:g:f:d:P:nh?vV:t:r:o:"
		"sN:y:p:bi:mA:T:"
#ifdef MOUNT2
		"2"
#endif
#ifdef MOUNT3
		"345"
#endif
	        )) != EOF)
	{
		char tmpopt[2];

		switch (opt)
		{
		case 'C':
		case 's':
		case 'b':
		case 'm':
			optarg = NULL;
		case 'a':
		case 'S':
		case 'T':
		case 'U':
		case 'c':
		case 'u':
		case 'g':
		case 'f':
		case 'd':
		case 'V':
		case 't':
		case 'r':
		case 'i':
		case 'A':
		case 'y':
		case 'p':
			tmpopt[0] = opt;
			tmpopt[1] = 0;
			proc_option(opts, &info, tmpopt, optarg);
			break;
		case 'P':
			if (!mount_ncp) opt_n = 0;	/* clear -n (nopasswd) */
			tmpopt[0] = opt;
			tmpopt[1] = 0;
			proc_option(opts, &info, tmpopt, optarg);
			break;
		case 'n':
			opt_n = 1;	/* no passwd or no update /etc/fstab */
			break;
		case 'h':
		case '?':
			help();
			exit(27);
		case 'v':
			opt_v = 1;	/* version or verbose */
			break;	
#ifdef MOUNT2
		case '2':
			info.version = 2;
			break;
#endif /* MOUNT2 */
#ifdef MOUNT3
		case '3':
			info.version = 3;
			break;
		case '4':
			info.version = 4;
			break;
		case '5':
			info.version = 5;
			break;
#endif /* MOUNT3 */
		case 'N':
			{
				char *inp;
				char *out;

				inp = optarg;

				while ((out = strtok(inp, ",;:"))!=NULL) {
					inp=NULL;
					if (!strcasecmp(out, "OS2"))
						proc_option(opts, &info, "noos2", NULL);
					else if (!strcasecmp(out, "LONG"))
						proc_option(opts, &info, "nolong", NULL);
					else if (!strcasecmp(out, "NFS"))
						proc_option(opts, &info, "nonfs", NULL);
					else {
						errexit(128, _("Unknown namespace \"%s\"\n"), out);
					}
				}
			};
			break;
		case 'o':
			{
				char* arg;

				arg = optarg;
				mount_ncp = 1;
				while (arg) {
					char* end;
					char* parm;

					arg += strspn(arg, " \t");
					if (!*arg) break;
					end = strchr(arg, ',');
					if (end) *end++ = 0;

					parm = strchr(arg, '=');
					if (parm) *parm++ = 0;

					proc_option(opts, &info, arg, parm);
					arg = end;
				};
			}
			break;
		default:
			usage();
			return -1;
		}
	}
	
	if (opt_n && !mount_ncp) {
		opt_n = 0;
		proc_option(opts, &info, "passwd", "");
	}
	if (opt_v && !mount_ncp) {
		fprintf(stderr, _("ncpfs version %s\n"), NCPFS_VERSION);
		exit(30);
	}
	if (optind < argc - 1) {
		char* s;
		char* u;

		s = strdup(argv[optind++]);
		if (s) {
			u = strchr(s, '/');
			if (u) *u++ = 0;
		} else
			u = NULL;
		if (!info.server && !info.tree) info.server = s;
		if (!info.user) info.user = u;
	}
	if (info.server && info.tree) {
		errexit(66, _("Both tree and server name were specified. It is not allowed.\n"));
	}
	if (geteuid() != 0)
	{
		errexit(26, _("%s must be installed suid root\n"), progname);
	}
	if (optind != argc - 1)
	{
		usage();
		return -1;
	}
	realpath(argv[optind], mount_point);

	if (stat(mount_point, &st) == -1)
	{
		errexit(31, _("Could not find mount point %s: %s\n"),
			mount_point, strerror(errno));
	}
	if (mount_ok(&st) != 0)
	{
		errexit(32, _("Cannot to mount on %s: %s\n"),
			mount_point, strerror(errno));
	}

	get_passwd(&info);

	if (myuid != info.mdata.mounted_uid) {
		if (setreuid(info.mdata.mounted_uid, -1)) {
			errexit(76, _("Cannot impersonate as requested: %s\n"), strerror(errno));
		}
	}
#ifdef NDS_SUPPORT
	if (info.tree) {
		static char vol[64];
		static char serv[64];
		nuint32 ctxf;
		
		if (!info.volume) {
			errexit(70, _("You must specify NDS volume name if you specified tree name.\n"));
		}
		err = NWDSCreateContextHandle(&ctx);
		if (err) {
			mycom_err(err, _("in create context"));
			exit(68);
		}
		if (!NWDSGetContext(ctx, DCK_FLAGS, &ctxf)) {
			ctxf |= DCV_XLATE_STRINGS | DCV_DEREF_ALIASES | DCV_TYPELESS_NAMES | DCV_DEREF_BASE_CLASS;
			NWDSSetContext(ctx, DCK_FLAGS, &ctxf);
		}
		err = NWCCOpenConnByName(NULL, info.tree, NWCC_NAME_FORMAT_NDS_TREE,
			NWCC_OPEN_NEW_CONN, 
			NWCC_RESERVED, &conn);
		if (err) {
			NWDSFreeContext(ctx);
			mycom_err(err, _("in tree search"));
			exit(67);
		}
		err = nds_login_auth(conn, info.user, info.password);
		if (err) {
			NWCCCloseConn(conn);
			NWDSFreeContext(ctx);
			mycom_err(err, _("in nds login"));
			exit(68);
		}
		NWDSAddConnection(ctx, conn);
		err = NWCXGetNDSVolumeServerAndResourceName(ctx,
			info.volume, serv, sizeof(serv),
			vol, sizeof(vol));
		if (err) {
			NWDSFreeContext(ctx);
			NWCCCloseConn(conn);
			mycom_err(err, _("in volume search"));
			exit(69);
		}
		NWDSFreeContext(ctx);
		NWCCCloseConn(conn);
		info.server = serv;
		strcpy(info.NWpath + 2, vol);
		info.NWpath[0] = 1;
		info.NWpath[1] = strlen(vol);
		info.pathlen = strlen(vol) + 2;
	} else 
#endif
	{
		if (info.volume) {
			info.pathlen = ncp_path_to_NW_format(info.volume, info.NWpath, sizeof(info.NWpath));
			info.remote_path = info.volume;
			if (info.pathlen < 0) {
				errexit(18, _("Volume path `%s' is invalid: `%s'\n"), info.volume, strerror(-info.pathlen));
			};
			if (info.pathlen == 1) {
				info.mdata.mounted_vol = "";
				info.remote_path = "/";
			} else if (info.NWpath[0] != 1) {
				info.mdata.mounted_vol = "dummy";
			} else if (strlen(info.volume) > NCP_VOLNAME_LEN) {
				errexit(19, _("Volume name `%s' is too long\n"), info.volume);
			} else {
				info.mdata.mounted_vol = info.volume;
			}
		}
	}
	err = NWDSCreateContextHandle(&ctx);
	if (err) {
		mycom_err(err, _("in create context"));
		exit(113);
	}
	if (info.auth_src) {
		NWDSChar user[MAX_DN_BYTES];

		err = ncp_open_mount(info.auth_src, &auth_conn);
		if (err) {
			NWDSFreeContext(ctx);
			mycom_err(err, _("opening mount %s"), info.auth_src);
			exit(114);
		}
		NWDSAddConnection(ctx, auth_conn);
		err = NWDSWhoAmI(ctx, user);
		if (err) {
			NWDSFreeContext(ctx);
			NWCCCloseConn(auth_conn);
			mycom_err(err, _("retrieving user name"));
			exit(117);
		}
		memset(&spec, 0, sizeof(spec));
		strncpy(spec.server, info.server, sizeof(spec.server));
		strncpy(spec.user, user, sizeof(spec.user));
	} else {
		if ((err = ncp_find_conn_spec3(info.server, info.user, info.password, 1, 
				info.mdata.uid, 1, &spec))
		    != 0)
		{
			NWDSFreeContext(ctx);
			mycom_err(err, _("in find_conn_spec"));
			exit(33);
		}
		if (info.upcase_password != 0)
		{
			str_upper(spec.password);
		}
		auth_conn = NULL;
	}
	info.mdata.server_name = spec.server;

	if (info.mdata.dir_mode == 0)
	{
		info.mdata.dir_mode = info.mdata.file_mode;
		if ((info.mdata.dir_mode & S_IRUSR) != 0)
			info.mdata.dir_mode |= S_IXUSR;
		if ((info.mdata.dir_mode & S_IRGRP) != 0)
			info.mdata.dir_mode |= S_IXGRP;
		if ((info.mdata.dir_mode & S_IROTH) != 0)
			info.mdata.dir_mode |= S_IXOTH;
	}
#if defined(CONFIG_NATIVE_IP) || defined(CONFIG_NATIVE_UNIX)
	if (info.server_name) {
#if defined(CONFIG_NATIVE_UNIX)
		if (info.server_name[0] == '/') {
			server_un.sun_family = AF_UNIX;
			memcpy(server_un.sun_path, "\000ncpfs", 6);
			memcpy(&info.mdata.serv_addr, &server_un, 16);
		} else
#endif
#if defined(CONFIG_NATIVE_IP)		
		{
			nuint tran;

			if ((!info.allow_multiple_connections)&&
			    ((tmp_mount = ncp_find_permanent(&spec)) != NULL))
			{
				NWDSFreeContext(ctx);
				if (auth_conn) {
					NWCCCloseConn(auth_conn);
				}
				fprintf(stderr,
					_("You already have mounted server %s\nas user "
					"%s\non mount point %s\n"), spec.server, spec.user,
					tmp_mount);
				exit(35);
			}

			tran = NT_UDP;
			if (info.nt.valid && info.nt.value == NT_TCP) {
				tran = info.nt.value;
			}
			err = ncp_find_server_addr(&info.server_name, OT_FILE_SERVER, (struct sockaddr*)&info.mdata.serv_addr,
					sizeof(info.mdata.serv_addr), tran);
			if (err) {
				NWDSFreeContext(ctx);
				if (auth_conn) {
					NWCCCloseConn(auth_conn);
				}
				eprintf(_("Get host address `%s': %s\n"), info.server_name, strnwerror(err));
				exit(1);
			}
		}
#else
		{
			goto ncpipx;
		}
#endif		
	} else 
#endif	/* CONFIG_NATIVE_IP */
	{
ncpipx:;
		if ((!info.allow_multiple_connections)&&
		    ((tmp_mount = ncp_find_permanent(&spec)) != NULL))
		{
			NWDSFreeContext(ctx);
			if (auth_conn) {
				NWCCCloseConn(auth_conn);
			}
			fprintf(stderr,
				_("You already have mounted server %s\nas user "
				"%s\non mount point %s\n"), spec.server, spec.user,
				tmp_mount);
			exit(35);
		}
		if ((err = ncp_find_fileserver(spec.server, (struct sockaddr*)&info.mdata.serv_addr, sizeof(info.mdata.serv_addr))) != 0)
		{
			NWDSFreeContext(ctx);
			if (auth_conn) {
				NWCCCloseConn(auth_conn);
			}
			mycom_err(err, _("when trying to find %s"),
				spec.server);
			exit(36);
		}
	}

	err = proc_buildconn(&info);
	if (err) {
		NWDSFreeContext(ctx);
		if (auth_conn) {
			NWCCCloseConn(auth_conn);
		}
		exit(err);
	}

	info.mdata.mount_point = mount_point;

	strcpy(mount_name, spec.server);
	strcat(mount_name, "/");
	strcat(mount_name, spec.user);

	result = ncp_mount(mount_name, &info);
	if (result)
	{
		NWDSFreeContext(ctx);
		if (auth_conn) {
			NWCCCloseConn(auth_conn);
		}
		mycom_err(result, _("in mount(2)"));
		exit(51);
	}
	err = proc_aftermount(&info, &conn);
	if (err) {
		umount(info.mdata.mount_point);
		NWDSFreeContext(ctx);
		if (auth_conn) {
			NWCCCloseConn(auth_conn);
		}
		exit(err);
	}
#ifdef NDS_SUPPORT
	if (info.auth_src != NULL) {
		err = NWDSAuthenticateConn(ctx, conn);
		if (err) {
			NWDSFreeContext(ctx);
			if (auth_conn) {
				NWCCCloseConn(auth_conn);
			}
			mycom_err(err, _("in authenticate connection"));
			exit(115);
		}
	} else if (!info.force_bindery_login && NWIsDSServer(conn, NULL))
	{
		if ((err = nds_login_auth(conn, spec.user, spec.password)))
		{
			if (err != NWE_PASSWORD_EXPIRED) {
				mycom_err(err, _("in nds login"));
				fprintf(stderr, _("Login denied.\n"));
				ncp_close(conn);
				umount(mount_point);
				NWDSFreeContext(ctx);
				if (auth_conn) {
					NWCCCloseConn(auth_conn);
				}
				exit(55);
			}
	    		fprintf(stderr, _("Your password has expired\n"));
		}
	}
	else
	{
#endif	
	if ((err = ncp_login_user(conn, spec.user, spec.password)) != 0)
	{
		struct nw_property p;
		struct ncp_prop_login_control *l
		= (struct ncp_prop_login_control *) &p;

		if (err != NWE_PASSWORD_EXPIRED)
		{
			mycom_err(err, _("in login"));
			fprintf(stderr, _("Login denied\n"));
			ncp_close(conn);
			umount(mount_point);
			NWDSFreeContext(ctx);
			if (auth_conn) {
				NWCCCloseConn(auth_conn);
			}
			exit(56);
		}
		fprintf(stderr, _("Your password has expired\n"));

		if ((err = ncp_read_property_value(conn, NCP_BINDERY_USER,
						   spec.user, 1,
						   "LOGIN_CONTROL", &p)) == 0)
		{
			fprintf(stderr, _("You have %d login attempts left\n"),
				l->GraceLogins);
		}
	}
#ifdef NDS_SUPPORT
	}
#endif
	err = NWDSFreeContext(ctx);
	if (err) {
		if (auth_conn) {
			NWCCCloseConn(auth_conn);
		}
		mycom_err(err, _("in free context"));
		exit(116);
	}
	if (auth_conn) {
		NWCCCloseConn(auth_conn);
	}
	if ((err = ncp_mount_specific(conn, -1, info.NWpath, info.pathlen)) != 0) 
	{
		ncp_close(conn);
		umount(mount_point);
		errexit(57, _("Cannot access path \"%s\": %s\n"), info.remote_path, strerror(-err));
	}
	ncp_close(conn);

	if (!opt_n) {
		add_mnt_entry(mount_name, mount_point, info.flags);
	}
	return 0;
}

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

static void
help(void)
{
	printf(_("\n"
		 "usage: %s [options] mount-point\n"), progname);
	/* printf() is macro in glibc2.2... */
	fprintf(stdout,
	     _("\n"
	       "-S server      Server name to be used\n"
	       "-A dns_name    DNS server name to be used when mounting over TCP or UDP\n"
	       "-U username    Username sent to server\n"
	       "-V volume      Volume to mount, for NFS re-export\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 uid         uid to identify the connection to mount on\n"
	       "               Only makes sense for root\n"
	       "-t time_out    Waiting time (in 1/100s) to wait for\n"
	       "               an answer from the server. Default: 60\n"
	       "-r retry_count Number of retry attempts. Default: 5\n"
	       "-C             Don't convert password to uppercase\n"
	       "-P password    Use this password\n"
	       "-n             Do not use any password\n"
	       "               If neither -P nor -n are given, you are\n"
	       "               asked for a password.\n"
	       "-s             Enable renaming/deletion of read-only files\n"
	       "-h             print this help text\n"
	       "-v             print ncpfs version number\n"
	       "%s%s"
	       "-m             Allow multiple logins to server\n"
	       "-N os2,nfs     Do not use specified namespaces on mounted volume\n"
	       "-y charset     character set used for input and display\n"
	       "-p codepage    codepage used on volume, including letters `cp'\n"
	       "\n"),
#ifdef NDS_SUPPORT
	       _("-b             Force bindery login to NDS servers\n")
#else
	       ""
#endif /* NDS_SUPPORT */
	       ,
#ifdef SIGNATURES
	       _("-i level       Signature level, 0=never, 1=supported, 2=preferred, 3=required\n")
#else
	       ""
#endif /* SIGNATURES */
	       );
}
