RCS_ID_C="$Id: login.c,v 1.9 1994/05/02 19:55:55 jraja Exp $";
/*
 * login.c -- log in using usergroup.library
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * Copyright © 1994 AmiTCP/IP Group, <AmiTCP-group@hut.fi>
 *                  Helsinki University of Technology, Finland.
 *
 * Created      : Tue Jan 11 14:06:41 1994 ppessi
 * Last modified: Tue Mar 22 09:54:04 1994 ppessi
 *
 */

#include "login_rev.h"

static const char version[] = VERSTAG;
static const char copyright[] =
  "AmiTCP/IP version 3 beta\n"
  "Copyright © 1994 AmiTCP/IP Group, <amitcp-group@hut.fi>\n"
  "                 Helsinki University of Technology, Finland.\n"
  "Copyright © 1980--1991 The Regents of the University of California.\n"
  "            All rights reserved.\n";

/****** utilities/login ******************************************************

    NAME
        login - log into the computer

    VERSION
        $Id: login.c,v 1.9 1994/05/02 19:55:55 jraja Exp $

    SYNOPSIS
        login [-a] [-f] [-p] [-h hostname] [user]

    DESCRIPTION

        The login utility logs users (and pseudo-users) into the computer
        system.

        If no user is specified, or if a user is specified and authentication
        of the user fails, login prompts for a user name.  Authentication of
        users is done via passwords.

        The options are as follows:

        -a      The -a option is used when a user is logging in on the
                console and wants to get ownership of processes running on
                WorkBench.  Currently, this is default behauviour.

        -f      The -f option is used when a user name is specified to
                indicate that proper authentication has already been done and
                that no password need be requested.  This option may only be
                used by the super-user or when an already logged in user is
                logging in as themselves.

        -h      The -h option specifies the host from which the connection was
                received.  It can be used by various daemons such as telnetd.
                This option may only be used by the super-user.

        -p      By default, login discards any previous environment of CLI.
                The -p option disables this behavior.  This option is
                implied by -a option.

        If the file `AmiTCP:db/nologin' exists, login dislays its contents to
        the user and exits.  This is used (by shutdown) to prevent users from
        logging in when the system is about to go down.

        Immediately after logging a user in, login displays the system
        copyright notice, the date and time the user last logged in, the
        message of the day as well as other information.  If the file
        `.hushlogin' exists in the user's home directory, all of these
        messages are suppressed.  This is to simplify logins for non-human
        users, such as uucp.  Login then records an entry in the wtmp and utmp
        files and executes the user's command interpretor.

        Login enters information into the environment specifying the user's
        home directory (HOME), command interpreter (SHELL) and user name
        (both LOGNAME and USER).  It assigns the directory HOME: to user's
        home directory.

    FILES
        AmiTCP:db/motd          message-of-the-day
        AmiTCP:db/nologin       disallows logins
        .hushlogin              makes login quieter

    SEE ALSO
        passwd, rlogin, usergroup.library/getpass()

    HISTORY
        A login command appeared in Version 6 AT&T UNIX.

****************************************************************************
*/

#include <sys/time.h>
#include <dos/rdargs.h>

#if defined(__SASC)
#include <proto/dos.h>
extern struct ExecBase *SysBase;
#include <pragmas/exec_sysbase_pragmas.h>
#include <clib/exec_protos.h>
#include <proto/usergroup.h>
/* Disable ^C signaling */
void __regargs __chkabort(void) {}
#else
#include <clib/exec_protos.h>
#include <clib/usergroup_protos.h>
#include <clib/dos_protos.h>
#endif

#include <sys/param.h>
#include <sys/syslog.h>

#include <string.h>
#include <stdlib.h>

#include <pwd.h>
#include <grp.h>
#include <utmp.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>

#include "config.h"
#include "pathnames.h"

#include <bsdsocket.h>

BPTR Stdin, Stdout, Stderr;
APTR WinPtr = NULL;
struct RDArgs *rdargs = NULL;

struct Library *SocketBase = NULL;
static UBYTE SOCKETNAME[] = "bsdsocket.library";
#define SOCKETVERSION 2		/* minimum bsdsocket version to use */
int errno;

static void 
syslog_AmiTCP(ULONG level, const STRPTR format, ...)
{
  va_list va;
  va_start(va, format);
  if (SocketBase) {
    vsyslog(level, format, (LONG *)va);
  }
  va_end(va);
}

static int gethostname_AmiTCP(char *name, int namelen)
{
  if (SocketBase) {
    return (int)gethostname(name, namelen);
  } else {
    char *cp = getenv("HOSTNAME");

    if (cp == NULL) {
      errno = ENOENT;
      return -1;
    }

    strncpy(name, cp, namelen);
    free(cp);
    return 0;
  }
}

#define syslog      syslog_AmiTCP
#define gethostname gethostname_AmiTCP

static void resume_amiga_stdio(void)
{
  struct Process* me = (struct Process *)FindTask(NULL);
  me->pr_WindowPtr = WinPtr;

  if (rdargs)
    FreeArgs(rdargs);
  rdargs = NULL;

  if (SocketBase)
    CloseLibrary(SocketBase);
  SocketBase = NULL;
}

static void startup_amiga_stdio(void)
{
  struct Process* me = (struct Process *)FindTask(NULL);
  Stdin = me -> pr_CIS;
  Stdout = me -> pr_COS;
  Stderr = (me -> pr_CES ? me -> pr_CES : Stdout);

  /* Remove requesters */
  WinPtr = me->pr_WindowPtr;
  me->pr_WindowPtr = (void *)-1L;
  atexit(resume_amiga_stdio);

  /* Open SocketBase (only for logging) */
  if (!SocketBase) {
    if ((SocketBase = OpenLibrary(SOCKETNAME, SOCKETVERSION)) != NULL) {
      SetErrnoPtr(&errno, sizeof(errno));
    }
  }
}

void endexit(int retval)
{
  exit(retval);
}

char *hostname, *username;
int failures;

void badlogin(char *name);
void getloginname(void);

void main()
{
  char *domain, localhost[MAXHOSTNAMELEN];
  int cnt, ask, rootlogin = 0, quietlog;
  uid_t uid = getuid();
  struct passwd *pwd;
  char linebuf[128];
  
  const char *template = 
    "-f=FORCE/S,-p=PRESERVE/S,-a=ALL/S,-h=HOST/K,USERNAME";
  struct {
    LONG   a_force;
    LONG   a_preserve;
    LONG   a_all;
    STRPTR a_host;
    STRPTR a_name;
  } args[1] = { 0 };
  BYTE oldpriority;

  startup_amiga_stdio();

#ifdef HAVE_OPENLOG
  openlog("login", LOG_ODELAY, LOG_AUTH);
#endif

  domain = NULL;

  if (gethostname(localhost, sizeof(localhost)) < 0)
    syslog(LOG_ERR, "couldn't get local hostname: %m");
  else
    domain = index(localhost, '.');

  rdargs = ReadArgs((STRPTR)template, (LONG *)args, NULL);
  if (!rdargs) {
    if (uid == 0)
      syslog(LOG_ERR, "invalid arguments \"%s\"", GetArgStr());
    PrintFault(IoErr(), "login");
    endexit(RETURN_ERROR);
  }

  if (args->a_host) {
#ifdef notyet
    if (uid != 0) {
      FPrintf(Stderr, "login: host option: %s\n", strerror(EPERM));
      endexit(RETURN_ERROR);
    }
#endif
    hostname = args->a_host;
  }

  if (args->a_name) {
    ask = 0;
    username = args->a_name;
  } else {
    ask = 1;
  }

  for (cnt = 0;; ask = 1) {
    char *p, *salt;
    int match;

    if (ask) {
      args->a_force = 0;
      getloginname();
    }
    if (strlen(username) > UT_NAMESIZE)
      username[UT_NAMESIZE] = '\0';

    if (pwd = getpwnam(username))
      salt = pwd->pw_passwd;
    else
      salt = "xx";

    /*
     * if we have a valid account name, and it doesn't have a
     * password, or the FORCE option was specified and the caller
     * is root or the caller isn't changing their uid, don't
     * authenticate.
     */
    if (pwd && (*pwd->pw_passwd == '\0' ||
		args->a_force && (uid == 0 || uid == pwd->pw_uid)))
      break;
    args->a_force = 0;
    if (pwd && pwd->pw_uid == 0)
      rootlogin = 1;

    oldpriority = SetTaskPri(FindTask(NULL), -98);

    p = getpass("Password:");

    if (pwd) {
      match = !strcmp(crypt(p, salt), pwd->pw_passwd);
    }
     
    bzero(p, strlen(p));

    SetTaskPri(FindTask(NULL), oldpriority);

    if (pwd != NULL && match)
      break;

    PutStr("Login incorrect\n");

    failures++;

    /* we allow 10 tries, but after 3 we start backing off */
    if (++cnt > 3) {
      if (cnt >= 10) {
	badlogin(username);
	endexit(RETURN_ERROR);
      }
      Delay((u_int)((cnt - 3) * 5 * 50));
    }
  }    

  /*
   * check nologin if user has got no force (member of wheel)
   */
  if (pwd->pw_gid != 0)  {
    BPTR nologin = Open(_PATH_NOLOGIN, MODE_OLDFILE);
 
    if (nologin) {
      LONG n;

      FPuts(Stdout, "No logins are allowed.\n");
      for (;;) {
	n = Read(nologin, linebuf, sizeof(linebuf));
	Write(Stdout, linebuf, n);
	if (n != sizeof(linebuf))
	  break;
      }
      Close(nologin);
      exit(RETURN_ERROR);
    }
  }

  {
    BPTR homedir, oldcwd, hushlock;
    
    homedir = Lock(pwd->pw_dir, SHARED_LOCK);

    if (homedir == NULL || !SetCurrentDirName(pwd->pw_dir)) {
      FPrintf(Stderr, "Cannot change to home directory %s\n"
	      "Logging in with home = \"SYS:\".\n", pwd->pw_dir);
      if (homedir) 
	UnLock(homedir);
      homedir = Lock("SYS:", SHARED_LOCK);
      SetCurrentDirName(pwd->pw_dir = "SYS:");
    } 
    oldcwd = CurrentDir(homedir);

    if (hushlock = Lock(_PATH_HUSHLOGIN, SHARED_LOCK))
      UnLock(hushlock);

    quietlog = hushlock != 0;

    UnLock(oldcwd);

    /* Make assign to home dir */
    if (homedir = DupLock(homedir)) {
      if (!AssignLock("HOME", homedir)) {
	PrintFault(IoErr(), "AssignLock"); 
      }
    } else {
      PrintFault(IoErr(), "DupLock"); 
    }
  }

  if (!quietlog) {
    BPTR motd;
    /* Print the last login time */
    struct lastlog *ll = getlastlog(pwd->pw_uid);

    if (!quietlog && ll != NULL && ll->ll_time != 0) {
      char *lfp, *tp = ctime(&ll->ll_time);
      if (lfp = index(tp, '\n'))
	*lfp = '\0';
      Printf("Last login: %19s ", tp);
      if (*ll->ll_host != '\0')
	Printf("from %s\n", ll->ll_host);
      else 
	Printf("\n");
    }

    /* Print copyright message */
    Write(Stdout, (UBYTE *)copyright, sizeof(copyright) - 1);

    /* Print message of the day */
    if (motd = Open(_PATH_MOTDFILE, MODE_OLDFILE)) {
      LONG n;
      for (;;) {
	n = Read(motd, linebuf, sizeof(linebuf));
	Write(Stdout, linebuf, n);
	if (n != sizeof(linebuf))
	  break;
      }
      Close(motd);
    }
  }

#ifndef HAVE_SUID
  /* Emulate suid bit effect */
  if (args->a_force == 0)
    (void)setreuid(-1, 0);
#endif

  (void)setgid(pwd->pw_gid);

  initgroups(username, pwd->pw_gid);

  SetVar("HOME", pwd->pw_dir, -1, GVF_GLOBAL_ONLY | LV_VAR);
  SetVar("SHELL", pwd->pw_shell, -1, GVF_GLOBAL_ONLY | LV_VAR);
  SetVar("LOGNAME", pwd->pw_name, -1, GVF_GLOBAL_ONLY | LV_VAR);
  SetVar("USER", pwd->pw_name, -1, GVF_GLOBAL_ONLY | LV_VAR);

  if (setlogin(pwd->pw_name) < 0 && getuid() == 0) 
    syslog(LOG_ERR, "setlogin() failure: %s", ug_StrError(errno));

  if ( setlastlog(pwd->pw_uid, username, 
		  hostname ? hostname : "Console") == -1)
    syslog(LOG_ERR, "setlastlog() failure: %s", ug_StrError(errno));

  if (rootlogin)
    (void) setuid(0);
  else
    (void) setuid(pwd->pw_uid);

  exit(0);
}

#define	NBUFSIZ		(UT_NAMESIZE + 1)

/*
 * Ask for login name
 */
void getloginname(void)
{
  int ch;
  register char *p;
  static char nbuf[NBUFSIZ + 1];
  
  for (;;) {
    PutStr("login: "); Flush(Stdout);
    for (p = nbuf; (ch = FGetC(Stdin)) != '\n'; ) {
      if (ch == -1) {
	/* EOF */
	Write(Stdout, "\n", 1);
	badlogin(username);
	endexit(RETURN_ERROR);
      }
      if (p < nbuf + (NBUFSIZ - 1))
	*p++ = ch;
    }
    if (p > nbuf)
      if (nbuf[0] == '-') {
	PutStr("login names may not start with '-'.\n");
      } else {
	*p = '\0';
	username = nbuf;
	break;
      }
  }
}

/*
 * log a bad login
 */
void badlogin(char *name)
{
  if (failures == 0)
    return;

  if (hostname) {
    syslog(LOG_NOTICE, "%ld LOGIN FAILURE%s FROM %s",
	   failures, failures > 1 ? "S" : "", hostname);
    syslog(LOG_AUTHPRIV|LOG_NOTICE,
	   "%ld LOGIN FAILURE%s FROM %s, %s",
	   failures, failures > 1 ? "S" : "", hostname, name);
  } else 
    {
    syslog(LOG_NOTICE, "%ld LOGIN FAILURE%s",
	   failures, failures > 1 ? "S" : "");
    syslog(LOG_AUTHPRIV|LOG_NOTICE,
	   "%ld LOGIN FAILURE%s %s",
	   failures, failures > 1 ? "S" : "", name);
  }
}
