/*
 * radlogin.c  RADIUS based /bin/login replacement
 *
 * Version:    $Id: radlogin.c,v 1.9 2008/01/09 07:04:18 sobomax Exp $
 *
 * Copyright (C) 1995,1996 Lars Fenneberg
 * Copyright (C) 2007 The FreeRADIUS Project
 *
 * See the file COPYRIGHT for the respective terms and conditions.
 *
 */

static char	rcsid[] =
		"$Id: radlogin.c,v 1.9 2008/01/09 07:04:18 sobomax Exp $";

#include	<config.h>
#include	<includes.h>
#include	<freeradius-client.h>
#include	<messages.h>
#include	<pathnames.h>
#include	<radlogin.h>

ENV *env = NULL;
static char *pname = NULL;
static rc_handle *rh = NULL;

static RETSIGTYPE
alarm_handler(int sn)
{
	fprintf(stderr, SC_TIMEOUT, rc_conf_int(rh, "login_timeout"));
	sleep(1);
	exit(ERROR_RC);
}

static int
login_allowed(char *tty)
{
	FILE *fp;
	char fname[PATH_MAX];
	int c;

	strcpy(fname, rc_conf_str(rh, "nologin"));
	if (access(fname, F_OK) < 0) {
		if (tty) {
			snprintf(fname, sizeof(fname), "%s.%s", rc_conf_str(rh, "nologin"), tty);
			if (access(fname, F_OK) < 0)
				return 1;
		} else {
			return 1;
		}
	}

	if ((fp = fopen(fname, "r")) != NULL)
	{
		while ((c = fgetc(fp)) != EOF)
		{
			if (c == '\n')
				fputc('\r', stdout);
			fputc(c, stdout);
		}
		fflush(stdout);
		fclose(fp);
	} else {
		printf(SC_NOLOGIN);
	}
	return 0;
}

static char *
subst_placeholders(char *str, char *tty)
{
	char *p,*q;
	static char buf[4096];
#if defined(HAVE_UNAME)
	struct utsname uts;
#endif
#if !defined(HAVE_STRUCT_UTSNAME_DOMAINNAME) && defined(HAVE_GETDOMAINNAME)
	char domainname[256];
#endif

#if defined(HAVE_UNAME)
	uname(&uts);
#endif

	p = str;
	q = buf;

	while (*p != '\0') {
		switch (*p) {
			case '\\':
				if (*(p+1) == '\0')
					break;
				p++;
				switch (*p) {
					case 'I':
						strcpy(q, rcsid);
						q += strlen(rcsid);
						break;
					case 'L':
					case 'P':
						strcpy(q, tty);
						q += strlen(tty);
						break;
#if defined(HAVE_UNAME)
					case 'S':
						strcpy(q, uts.sysname);
						q += strlen(uts.sysname);
						break;
					case 'N':
						strcpy(q, uts.nodename);
						q += strlen(uts.nodename);
						break;
					case 'R':
						strcpy(q, uts.release);
						q += strlen(uts.release);
						break;
					case 'V':
						strcpy(q, uts.version);
						q += strlen(uts.version);
						break;
					case 'M':
						strcpy(q, uts.machine);
						q += strlen(uts.machine);
						break;
#endif
					case 'D':
#if defined(HAVE_STRUCT_UTSNAME_DOMAINNAME)
						strcpy(q, uts.domainname);
						q += strlen(uts.domainname);
#elif defined(HAVE_GETDOMAINNAME)
						getdomainname(domainname, sizeof(domainname));
						strcpy(q, domainname);
						q += strlen(domainname);
#endif
						break;
					case '\\':
						*q = '\\';
						q++;
						break;
				}
				break;
#if defined(HAVE_UNAME)
			case '@':
				strcpy(q, uts.nodename);
				q += strlen(uts.nodename);
				break;
#endif
			case '\n':
				strcpy(q,"\r\n");
				q += 2;
				break;
			default:
				*q = *p;
				q++;
				break;
		}

		p++;
	}
	*q = '\0';

	return buf;
}

static void
usage(void)
{
	fprintf(stderr,"Usage: %s [-Vhnd] [-f <config_file>] [-i <client_port>] [-m <login_tries>]\n\n", pname);
	fprintf(stderr,"  -V		output version information\n");
	fprintf(stderr,"  -h		output this text\n");
	fprintf(stderr,"  -n		don't display issue file\n");
	fprintf(stderr,"  -f		filename of alternate config file\n");
	fprintf(stderr,"  -i		ttyname to send to the server\n");
	fprintf(stderr,"  -m		maximum login tries (overrides value in config file)\n");
        exit(ERROR_RC);
}

static void
version(void)
{
	fprintf(stderr,"%s: %s\n", pname ,rcsid);
	exit(ERROR_RC);
}

int
main (int argc, char **argv)
{
	char		username[128];
	char		passwd[AUTH_PASS_LEN + 1];
	int 		tries, remaining, c;
	uint32_t		client_port;
	void 		(*login_func)(rc_handle *, char const *);
	FILE		*fp;
	char 		buf[4096];
	char		tty[1024], *p;
	int		noissue = 0;
	int		maxtries = 0;
	char		*ttyn  = NULL;
	char            *path_radiusclient_conf = RC_CONFIG_FILE;

        extern char *optarg;
        extern int optind;

	pname = (pname = strrchr(argv[0],'/'))?pname+1:argv[0];

	rc_openlog(pname);

	while ((c = getopt(argc,argv,"f:m:i:nhV")) > 0)
	{
		switch(c) {
			case 'f':
				path_radiusclient_conf = optarg;
				break;
			case 'i':
				ttyn = optarg;
				break;
			case 'n':
				noissue = 1;
				break;
			case 'm':
				maxtries = atoi(optarg);
				break;
			case 'V':
				version();
				break;
			case 'h':
				usage();
				break;
			default:
				exit(ERROR_RC);
				break;
		}
	}

	if ((rh = rc_read_config(path_radiusclient_conf)) == NULL)
		exit(ERROR_RC);

	if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary")) != 0)
		exit (ERROR_RC);

	if (rc_read_mapfile(rh, rc_conf_str(rh, "mapfile")) != 0)
		exit (ERROR_RC);

	if (ttyn != NULL)
	{
		client_port = rc_map2id(rh, ttyn);

		if ((p = strrchr(ttyn, '/')) == NULL)
			strncpy(tty, ttyn, sizeof(tty));
		else
			strncpy(tty, p+1, sizeof(tty));
	}
	else
	{
		ttyn = ttyname(0);
		if (ttyn)
		{
			if ((p = strrchr(ttyn, '/')) == NULL)
				strncpy(tty, ttyn, sizeof(tty));
			else
				strncpy(tty, p+1, sizeof(tty));

			client_port = rc_map2id(rh, ttyn);
		}
		else
		{
			*tty = '\0';
			client_port = 0;
		}
	}

#ifdef SETVBUF_REVERSED
	setvbuf(stdout, _IONBF, NULL, 0);
#else
	setvbuf(stdout, NULL, _IONBF, 0);
#endif

	if ((argc - optind) == 1)
	{
		strncpy(username,argv[optind], sizeof(username));
	}
	else
	{
		*username = '\0';

		if (!noissue) {
			if (rc_conf_str(rh, "issue") && ((fp = fopen(rc_conf_str(rh, "issue"), "r")) != NULL))
			{
				while (fgets(buf, sizeof(buf), fp) != NULL)
					fputs(subst_placeholders(buf, tty), stdout);

				fflush(stdout);
				fclose(fp);
			} else {
				fputs(subst_placeholders(SC_DEFAULT_ISSUE, tty), stdout);
				fflush(stdout);
			}
		}
	}

	if ((env = rc_new_env(ENV_SIZE)) == NULL)
	{
		rc_log(LOG_CRIT, "rc_new_env: FATAL: out of memory");
		abort();
	}

#ifdef SECURITY_DISABLED
	if (rc_import_env(env,environ) < 0)
	{
		rc_log(LOG_CRIT, "rc_import_env: FATAL: not enough space for environment (increase ENV_SIZE)");
		abort();
	}
#else
	rc_add_env(env, "IFS", " ");
	rc_add_env(env, "PATH", RC_SECURE_PATH);
#endif

	signal(SIGALRM, alarm_handler);

	remaining = rc_conf_int(rh, "login_timeout");

	if (!maxtries)
		maxtries = rc_conf_int(rh, "login_tries");

	tries = 1;
	while (tries <= maxtries)
	{
	 alarm(remaining);

	 while (!*username) {
	 	p = rc_getstr (rh, SC_LOGIN, 1);
	 	if (p)
	 		strncpy(username, p, sizeof(username));
	 	else
	 		exit (ERROR_RC);
	 }
	 p = rc_getstr(rh, SC_PASSWORD,0);
	 if (p)
	 	strncpy (passwd, p, sizeof (passwd));
	 else
		exit (ERROR_RC);

	 remaining = alarm(0);

	 login_func = NULL;

 	 if (rc_conf_int(rh, "auth_order") & AUTH_LOCAL_FST)
 	 {
 	 	login_func = auth_local(username, passwd);

 	 	if (!login_func)
 	 		if (rc_conf_int(rh, "auth_order") & AUTH_RADIUS_SND)
 	 			login_func = auth_radius(rh, client_port, username, passwd);
 	 }
 	 else
 	 {
		login_func = auth_radius(rh, client_port, username, passwd);
 	 	if (!login_func)
 	 		if (rc_conf_int(rh, "auth_order") & AUTH_LOCAL_SND)
 	 			login_func = auth_local(username, passwd);
 	 }

	 memset(passwd, '\0', sizeof(passwd));

	 if (login_func != NULL) {
	 	if (login_allowed(tty)) {
	 		(*login_func)(rh, username);
		} else {
			sleep(1);
			exit (ERROR_RC);
		}
	}

	 *username = '\0';

	 if ((++tries) <= maxtries) {
		alarm(remaining);
	 	sleep(tries * 2);
	 	remaining = alarm(0);
	 }

	}

	fprintf(stderr, SC_EXCEEDED);
	sleep(1);

	exit (ERROR_RC);
}
