/*
 *	Network Queueing System (NQS)
 *  This version of NQS is Copyright (C) 1992  John Roman
 *
 *  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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
*  PROJECT:     Network Queueing System
*  AUTHOR:      John Roman
*
*  Modification history:
*
*       Version Who     When            Description
*       -------+-------+---------------+-------------------------
*       V01.10  JRR                     Initial version.
*       V01.20  JRR     16-Jan-1992	Added support for RS6000.
*       V01.3   JRR     13-Feb-1992	Now pass logfile to mapuser to print
*					reason for problems.
*       V01.4   JRR     28-Feb-1992	Add Cosmic V2 changes.
*       V01.5   JRR     03-Mar-1992	And start cleaning them up.
*	V01.6	JRR	15-May-1992	Bugfix found with RS6000 version.
*	V01.7	JRR	17-Jun-1992	Added header.
*	V01.8	JRR	31-Aug-1992	Finduser should be local.
*					Tolerate any whitespace between hostname
*					and username.
*	V01.9	JRR	23-Dec-1992	Compare hostnames in a case-insensitive
*					manner.	
*					Change index=>strchr
*	V01.10	JRR	06-Apr-1993	Added support for DECOSF.
*	V01.11	JRR	12-Aug-1993	Removed non-networked code.
*	V01.12	JRR	05-Aug-1994	Fixes from Bruno Wolff.
*/
/*++ mapuser.c - Network Queueing System
 *
 * $Source: /usr2/jrroma/nqs/nqs-3.36/lib/RCS/mapuser.c,v $
 *
 * DESCRIPTION:
 *
 *	Given a user's identity on another host, return
 *	a pointer to that user's password file entry on this host.
 *
 *
 *	Author:
 *	-------
 *	Robert W. Sandstrom, Sterling Software Incorporated.
 *	May 9, 1986.
 *
 *
 * STANDARDS VIOLATIONS:
 *   None.
 *
 * REVISION HISTORY: ($Revision: 1.12 $ $Date: 1994/09/02 17:37:55 $ $State: Exp $)
 * $Log: mapuser.c,v $
 * Revision 1.12  1994/09/02  17:37:55  jrroma
 * Version 3.36
 *
 * Revision 1.11  1993/09/10  13:55:15  jrroma
 * Version 3.35
 *
 * Revision 1.10  93/07/13  21:31:28  jrroma
 * Version 3.34
 * 
 * Revision 1.9  93/02/05  23:13:16  jrroma
 * Version 3.31
 * 
 * Revision 1.8  92/12/22  15:46:13  jrroma
 * Version 3.30
 * 
 * Revision 1.7  92/06/18  13:24:09  jrroma
 * Added gnu header
 * 
 * Revision 1.6  92/06/18  09:41:47  jrroma
 * Version 3.21
 * 
 * Revision 1.5  92/03/03  16:34:39  jrroma
 * Fixed up Cosmic V2 changes.
 * 
 * Revision 1.4  92/02/28  15:02:13  jrroma
 * Added Cosmic V2 changes.
 * 
 * Revision 1.3  92/02/14  10:21:45  jrroma
 * Add logging messages.
 * 
 * Revision 1.2  92/01/16  16:49:44  jrroma
 * Added support for RS6000.
 * 
 * Revision 1.1  92/01/16  16:48:18  jrroma
 * Initial revision
 * 
 *
 */

#include "nqs.h"
#include <pwd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>

#ifndef __CEXTRACT__
#if __STDC__

static int access ( char *accessfname, char *fromhostname, struct passwd *passwd, FILE *logfile );
static int local_finduser ( char *user, char *toname );
static int map_user_name ( char *machine, char *user, char *toname );
static int l_strcmp ( char *str1,  char *str2 );

#else /* __STDC__ */

static int access (/* char *accessfname, char *fromhostname, struct passwd *passwd, FILE *logfile */);
static int local_finduser (/* char *user, char *toname */);
static int map_user_name (/* char *machine, char *user, char *toname */);
static int l_strcmp (/* char *str1, char *str2 */);

#endif /* __STDC__ */
#endif /* __CEXTRACT__ */

extern int  Debug;


/*** mapuser
 *
 *
 *	struct passwd *mapuser: Given a user's identity on another host,
 *	return a pointer to that user's password file entry on this host.
 *
 *	mapuser first checks the /etc/hosts.equiv file to see if access
 *	is permitted.  The format of the /etc/hosts.equiv file is
 *
 *	hostname username
 *	fqdn username
 *
 *	Both the hostname and fully qualified domain name may be required.
 *	
 *	If access is not permitted,  the user's .rhosts file is checked.
 *	Its format is the same as the /etc/hosts.equiv above.
 *
 *	If access is still not permitted,  the file /etc/hosts.nqs is
 *	checked.  The details are in the code below,  but at the simplest, 
 *	the file can be the same format at the .rhosts and /etc/hosts.equiv
 *	files.  It is also possible to get more complicated,  and map
 *	a username on a remote machine to a local machine.  An example
 *	is:
 *
 *	(beaker.monsanto.com model7.monsanto.com) jrroma=mgkreb
 *
 *	in this case user jrroma on beaker or model7 is mapped to username
 *	mgkreb on the local machine.
 *
 */
struct passwd *mapuser (fromuid, fromusername, frommid, fromhostname, mapbyuid,
	logfile)
uid_t fromuid;				/* Client's uid on remote machine */
char *fromusername;			/* Client username on remote machine */
Mid_t frommid;				/* Nmap machine id of remote machine */
char *fromhostname;			/* PRINCIPAL name of remote machine */
short mapbyuid;				/* Boolean */
FILE  *logfile;				/* logfile if problem */
{
	uid_t touid;			/* Mapped user-id */
	char dotrhostspath [256];	/* Users .rhost pathname */
	register struct passwd *passwd;	/* Password entry for account */
        static char toname[MAX_ACCOUNTNAME];
        short local_name_exists;

     	/*
	 *  The selected mapping algorithm operates by user-id.
	 */
	switch (nmap_get_uid (frommid, fromuid, &touid)) {
 		case NMAP_SUCCESS:
		case NMAP_DEFMAP:
			return (fetchpwuid (touid));
	}
	/*
	 *  The selected mapping algorithm operates by name.
	 */
	passwd = fetchpwnam (fromusername);
	local_name_exists = 0;
	if (passwd != (struct passwd *) 0) local_name_exists = 1;
	if (Debug > 4) {
	    fprintf(logfile, "D$mapuser: fromusername >%s<\n",  fromusername);
	    fprintf(logfile, "D$mapuser: fromhostname >%s<\n",  fromhostname);
	    if (local_name_exists) fprintf(logfile,  "D$mapuser: pwd exists\n");
	    else fprintf(logfile, "D$mapuser: pwd not exist\n");
	    fprintf(logfile,  "D$mapuser: fromuid %d\n", fromuid);
	    fflush(logfile);
	}
	if ( (fromuid != 0) && (strcmp(fromusername, "root")) ) {
	    /*
	     *  We are not mapping root.
	     *  Examine the /etc/hosts.equiv file for machine
	     *  equivalency.
	     */
	    if (access ("/etc/hosts.equiv", fromhostname,
			   (struct passwd *) 0, logfile) ) {
		if (Debug > 0) {
		    fprintf(logfile,  
		            "I$mapuser: hosts.equiv allows access from %s\n",  
			    fromhostname);
		    fflush(logfile);
		}
	        return (passwd);	/* Access allowed */
	    }
	}
	/*
	 *  Determine if the user's account specifically allows access.
	 */
        if (local_name_exists) {
	    strcpy (dotrhostspath, passwd->pw_dir);
	    strcat (dotrhostspath, "/.rhosts");
	    if (access (dotrhostspath, fromhostname, passwd, logfile)) {
		if (Debug > 0) {
		    fprintf(logfile, "I$mapuser: Access allowed for %s from %s\n", 
				fromusername,  fromhostname);
		    fflush(logfile);
		}
		return (passwd);		/* Access allowed */
	    }
	}
        /*
         * Determine if /etc/hosts.nqs has a mapping
         */
        switch(map_user_name(fromhostname, fromusername, toname)) {
        case 0:
            if (Debug > 0) {
                fprintf(logfile,"I$map_user_name: %s@%s -> %s \n",
                                fromusername,fromhostname,toname);
		fflush(logfile);
	    }
            if ((passwd = fetchpwnam(toname)) != (struct passwd *) 0)
                        return (passwd);
            break;
        case -1:
            if (Debug > 0) {
                fprintf(logfile,"D$map_user_name: machine not found \n");
		fflush(logfile);
	    }
	    break;
        case -2:
            if (Debug > 0) {
                fprintf(logfile,"D$map_user_name: user not found \n");
		fflush(logfile);
	    }
            break;
        case -3:
            if (Debug >0) {
                fprintf(logfile,
                     "D$map_user_name: cannot open /etc/hosts.nqs\n");
		fflush(logfile);
	    }
            break;
        }
	return ((struct passwd *) 0);
}


/*** access
 *
 *
 *	int access():
 *
 *	Return non-zero if the specified Berkeley style access file
 *	grants access to the specified remote account.
 *
 *	If the password entry parameter:  passwd  is non-null, then
 *	the access equivalency file being scanned MUST contain lines
 *	of the form:
 *
 *		machinename username
 *
 *	and any such access equivalency file MUST be owned by the
 *	account identified by the given password entry.
 */
static int access (accessfname, fromhostname, passwd, logfile)
char *accessfname;			/* Access file name */
char *fromhostname;			/* Name of client machine */
register struct passwd *passwd;		/* Password file entry */
FILE *logfile;				/* Where to print if error */
{
	struct stat statbuf;		/* Stat() buffer */
	char accesshost [256];		/* Access file line buffer */
	register FILE *accessfile;	/* Access file */
	register char *cp, *acp;
	register char *user;

	accessfile = fopen (accessfname, "r");
	if (accessfile != (FILE *) 0) {
	    /*
	     * The access file was successfully opened.
	     */
	    if (passwd != (struct passwd *) 0) {
	        /*
	         *  Verify ownership of the .rhosts file, and ensure
	         *  that (on Berkeley systems), that the file is not
	         *  a symbolic link.
	         */
	        if (fstat (fileno (accessfile), &statbuf) == -1) {
		    fprintf(logfile, "I$Fstat of %s failed with errno %d\n",
			    accessfname, errno);
		    fflush(logfile);
		    return (0);
	        }
	        if (statbuf.st_uid != passwd->pw_uid ||
		       (statbuf.st_mode & S_IFMT) == S_IFLNK) {
		    fprintf(logfile, "I$Uids don't match.\n", errno);
		    fflush(logfile);
		    return (0);
	        }
	    }
	    while (fgets (accesshost, sizeof (accesshost),
			      accessfile) != (char *) 0) {
		if (accesshost[0] == '#') continue;
		/*
		 *  We have an access file line to interpret.
		 */
		cp = strchr (accesshost, '\n');
		if (cp != (char *) 0) *cp = '\0';
		if (passwd != (struct passwd *) 0) {
		    /*
		     *  We are inspecting a user account
		     *  .rhost file.  A username field is
		     *  required.
		     */
		    cp = accesshost;
		    /* 
		     * The first token is the hostname.  Scan until
		     * reach whitespace.
		     */
		    while (*++cp && !isspace(*cp) ) {
			;
		    }
		    /*
		     * The second token is the username.  Scan until
		     * reach non-whitespace.
		     */
		    if (*cp) {
		        *cp = '\0';	    /* terminate hostname */
			while (*++cp && isspace(*cp) ) {
			    ;
			}
		    }
		    /*
		     * Scan past the second token until we reach
		     * whitespace or null.
		     */
		    acp = cp;
		    while (*++acp && !isspace(*acp) ) {
			;
		    }
		    if (*acp) *acp = '\0';		    
		    if (cp != (char *) 0) {
			/*
			 *  A username field is present
			 *  (as required).
			 */
			if (!l_strcmp (accesshost, fromhostname)&&
				 !strcmp (cp, passwd->pw_name)) {
					return (1);
			}
		    }
		}
		else if (!l_strcmp (accesshost, fromhostname)) {
		    /*
		     *  Client account is at an equivalent host.
		     */
		    return (1);
		}
	    }
	}
	if (Debug > 2) {
	    if (passwd != (struct passwd *) 0)
		fprintf(logfile, "I$No access for user %s from %s\n", 
				passwd->pw_name, fromhostname);
	    else fprintf(logfile,  "I$No access from %s\n",  fromhostname);
	    fflush(logfile);
	}
	return (0);			/* No access */
}
/*** map_user_name
 *
 *
 *
 *    int map_user_name()
 *
 *    Return non-zero if the /etc/hosts.nqs file
 *    grants access to the specified remote account
 */
static int map_user_name (machine, user, toname)
char *machine;                                /* remote machine name */
char *user;                           /* remote user name */
char *toname;                         /* mapped user name */
{
      FILE *accessfile;               /* hosts.nqs file */
      static char buffer[BUFSIZ];     /* hosts.nqs line buffer */
      char *cp;                       /* current line */
      char *paren;                    /* paren found */
      short list;                     /* true if a list of names */
      int found = 0;                  /* true if name found */

      accessfile = fopen ("/etc/hosts.nqs", "r");
      if (accessfile == (FILE *) 0) return(-3);
      while (fgets(buffer,sizeof(buffer),accessfile)) {
          cp = strchr(buffer, '\n');
          if (cp) *cp = '\0';
          cp = strtok(buffer," \t");
          switch (*cp) {
              case '(':       /* this is a list of machines */
                 cp++;
                  do {
                      paren = strchr(cp,')');
                      if (paren) *paren = '\0';
                      if (l_strcmp(machine, cp) == 0) {
                          found++;
                      }
                      if (!paren) cp = strtok((char *)0," \t");
                  } while (!paren);
                  if (found && local_finduser(user, toname)==0)
                              return(0);
                  break;
              case '*':
                  if (local_finduser(user, toname)==0)
                          return(0);
                  break;
              case '#':
              case '\n':

              case '\0':
                  break;
              default:
                  if (l_strcmp(machine, cp) == 0) {
                      if (local_finduser(user, toname)==0)
                              return(0);
                  }
                  break;
          }
      }
      return (-1);    /* not found */
}


/*** local_finduser
 *
 *
 *    int local_finduser();
 *
 *    Returns the mapped user in toname if the user is found
 *    in the current line
 *
 *    Returns
 *     0:     if the current line grants access
 *    -2:     if the user is not found
 */
static int local_finduser(user, toname)
char *user, *toname;
{
      char *cp, *paren, *equal;
      int found = 0;
      int userlist = 0;

      while(cp = strtok((char *)0, " \t")) {
          userlist++;
          switch (*cp) {
          case '(':       /* is this a list of users? */
              cp++;
              do {
                  paren = strchr(cp,')');
                  if (paren) *paren = '\0';
                  if (strcmp(user, cp) == 0) found++;
                  if (!paren) cp = strtok((char *)0," \t");
              } while (cp && !paren);
              if (found) {
                  equal = strchr(paren+1,'=');
                  if (equal)
                       strcpy(toname,equal+1);
                  else
                       strcpy(toname,user);
                  return(0);
              }
              break;
          case '*':
              if (*(cp+1) == '=')
                  strcpy(toname,cp+2);
              else
                  strcpy(toname,user);
              return(0);
              break;
          default:
              equal = strchr(cp,'=');
              if (equal) *equal = '\0';
              if (strcmp(user, cp) == 0) {
                  if (equal)
                      strcpy(toname,equal+1);
                  else
                      strcpy(toname,user);
                  return(0);
              }
              break;
          }
      }
      if (!found && !userlist) {
          strcpy(toname,user);
          return(0);
      }
      else
         return(-2);
}
/* l_strcmp():
 * 
 * Do string comparison regardless of case.
 * 
 * Returns 0 if the same,  1 otherwise.
 */
static int l_strcmp(str1, str2)
char *str1;
char *str2;
{
    char c1, c2;
    char *ptr1, *ptr2;
 
    /*
     * If they are different lengths, automatically not the same.
     */
    if ( strlen(str1) != strlen(str2) ) return (1);
    ptr1 = str1;
    ptr2 = str2;
    while (*ptr1 != '\0') {
	c1 = *ptr1++;
	c2 = *ptr2++;
	if ( tolower(c1) != tolower(c2) ) return (1);
    }
    return (0);
}
