/*
*         OpenPBS (Portable Batch System) v2.3 Software License
*
* Copyright (c) 1999-2000 Veridian Information Solutions, Inc.
* All rights reserved.
*
* ---------------------------------------------------------------------------
* For a license to use or redistribute the OpenPBS software under conditions
* other than those described below, or to purchase support for this software,
* please contact Veridian Systems, PBS Products Department ("Licensor") at:
*
*    www.OpenPBS.org  +1 650 967-4675                  sales@OpenPBS.org
*                        877 902-4PBS (US toll-free)
* ---------------------------------------------------------------------------
*
* This license covers use of the OpenPBS v2.3 software (the "Software") at
* your site or location, and, for certain users, redistribution of the
* Software to other sites and locations.  Use and redistribution of
* OpenPBS v2.3 in source and binary forms, with or without modification,
* are permitted provided that all of the following conditions are met.
* After December 31, 2001, only conditions 3-6 must be met:
*
* 1. Commercial and/or non-commercial use of the Software is permitted
*    provided a current software registration is on file at www.OpenPBS.org.
*    If use of this software contributes to a publication, product, or
*    service, proper attribution must be given; see www.OpenPBS.org/credit.html
*
* 2. Redistribution in any form is only permitted for non-commercial,
*    non-profit purposes.  There can be no charge for the Software or any
*    software incorporating the Software.  Further, there can be no
*    expectation of revenue generated as a consequence of redistributing
*    the Software.
*
* 3. Any Redistribution of source code must retain the above copyright notice
*    and the acknowledgment contained in paragraph 6, this list of conditions
*    and the disclaimer contained in paragraph 7.
*
* 4. Any Redistribution in binary form must reproduce the above copyright
*    notice and the acknowledgment contained in paragraph 6, this list of
*    conditions and the disclaimer contained in paragraph 7 in the
*    documentation and/or other materials provided with the distribution.
*
* 5. Redistributions in any form must be accompanied by information on how to
*    obtain complete source code for the OpenPBS software and any
*    modifications and/or additions to the OpenPBS software.  The source code
*    must either be included in the distribution or be available for no more
*    than the cost of distribution plus a nominal fee, and all modifications
*    and additions to the Software must be freely redistributable by any party
*    (including Licensor) without restriction.
*
* 6. All advertising materials mentioning features or use of the Software must
*    display the following acknowledgment:
*
*     "This product includes software developed by NASA Ames Research Center,
*     Lawrence Livermore National Laboratory, and Veridian Information
*     Solutions, Inc.
*     Visit www.OpenPBS.org for OpenPBS software support,
*     products, and information."
*
* 7. DISCLAIMER OF WARRANTY
*
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT
* ARE EXPRESSLY DISCLAIMED.
*
* IN NO EVENT SHALL VERIDIAN CORPORATION, ITS AFFILIATED COMPANIES, OR THE
* U.S. GOVERNMENT OR ANY OF ITS AGENCIES BE LIABLE FOR ANY DIRECT OR INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This license will be governed by the laws of the Commonwealth of Virginia,
* without reference to its choice of law rules.
*/
#include <pbs_config.h>   /* the master config generated by configure */

#include <pthread.h>

#include <stdio.h>
#include "libpbs.h"
#include "string.h"
#include "list_link.h"
#include "attribute.h"
#include "server_limits.h"
#include "server.h"
#include "pbs_job.h"
#include "credential.h"
#include "batch_request.h"
#include "pbs_error.h"
#include "log.h"
#include "../lib/Liblog/pbs_log.h"
#include "../lib/Liblog/log_event.h"
#include "svrfunc.h"
#include "mcom.h"
#include "utils.h"
#include "svr_func.h" /* get_svr_attr_* */
#include "net_cache.h"
#include "../lib/Libnet/lib_net.h"
#include "ji_mutex.h"

/* Global Data */

extern int    LOGLEVEL;
extern struct credential conn_credent[PBS_NET_MAX_CONNECTIONS];
extern char *pbs_o_host;
extern char  server_host[];
extern char *msg_permlog;

extern char server_localhost[];

extern char *PJobState[];

extern int site_allow_u(char *, char *);

int svr_chk_owner_generic(struct batch_request *preq, char *owner, char *submit_host);
int svr_authorize_req(struct batch_request *preq, char *owner, char *submit_host);


/*
 * svr_chk_owner - compare a user name from a request and the name of
 * the user who owns the job.
 *
 * Return 0 if same, non 0 if user is not the job owner
 */


int svr_chk_owner(

  struct batch_request *preq,  /* I */
  job                  *pjob)  /* I */

  {
  char  owner[PBS_MAXUSER + 1];

  get_jobowner(pjob->ji_wattr[JOB_ATR_job_owner].at_val.at_str, owner);


  return svr_chk_owner_generic(preq, owner, get_variable(pjob, pbs_o_host));
  }  /* END svr_chk_owner() */


int svr_chk_owner_generic(

  struct batch_request *preq,
  char *owner,
  char *submit_host)

  {

  char *pu;
  char  rmtuser[PBS_MAXUSER + 1];

  /* map user@host to "local" name */

  pu = site_map_user(preq->rq_user, preq->rq_host);

  if (pu == NULL)
    {
    /* FAILURE */

    return(-1);
    }

  snprintf(rmtuser, sizeof(rmtuser), "%s", pu);

  pu = site_map_user(owner, submit_host);

  return(strcmp(rmtuser, pu));
  }


/*
 * svr_authorize_jobreq - determine if requestor is authorized to make
 * request against the job.  This is only called for batch requests
 * against jobs, not manager requests against queues or the server.
 *
 *  Returns 0 if authorized (job owner, operator, administrator)
 *   -1 if not authorized.
 */

int svr_authorize_jobreq(

  struct batch_request *preq,  /* I */
  job                  *pjob)  /* I */

  {
  char  owner[PBS_MAXUSER + 1];

  get_jobowner(pjob->ji_wattr[JOB_ATR_job_owner].at_val.at_str, owner);

  return svr_authorize_req(preq, owner, get_variable(pjob, pbs_o_host));
  }  /* END svr_authorize_jobreq() */






int svr_authorize_req(

  struct batch_request *preq,
  char *owner,
  char *submit_host)

  {
  /* does requestor have special privileges? */

  if ((preq->rq_perm & (ATR_DFLAG_OPRD | ATR_DFLAG_OPWR |
                        ATR_DFLAG_MGRD | ATR_DFLAG_MGWR)) != 0)
    {
    /* request authorized */

    return(0);
    }

  /* is requestor the job owner? */

  if (svr_chk_owner_generic(preq, owner, submit_host) == 0)
    {
    /* request authorized */

    return(0);
    }

  /* not authorized */

  return(-1);
  }





/*
 * svr_get_privilege - get privilege level of a user.
 *
 * Privilege is granted to a user at a host.  A user is automatically
 * granted "user" privilege.  The user@host pair must appear in
 * the server's administrator attribute list to be granted "manager"
 * privilege and/or appear in the operators attribute list to be
 * granted "operator" privilege.  If either acl is unset, then root
 * on the server machine is granted that privilege.
 *
 * If "PBS_ROOT_ALWAYS_ADMIN" is defined, then root always has privilege
 * even if not in the list.
 *
 * The returns are based on the access permissions of attributes, see
 * attribute.h.
 */

int svr_get_privilege(

  char *user,  /* I */
  char *host)  /* I */

  {
  int        is_root = 0;
  int        priv = (ATR_DFLAG_USRD | ATR_DFLAG_USWR);
  int        num_host_chars;
  char       uh[PBS_MAXUSER + PBS_MAXHOSTNAME + 2];
  char       host_no_port[PBS_MAXHOSTNAME+1];
  char      *colon_loc = NULL;
  char       log_buf[LOCAL_LOG_BUF_SIZE];
  char      *other_host;
  int        other_priv = 0;
  int        my_err;
  pbs_net_t  server_addr;
  pbs_net_t  connect_addr;
#ifndef __CYGWIN__
  pbs_net_t  local_server_addr;
#endif

  if (!user)
    {
    sprintf(log_buf, "Invalid user: %s", "null");

    log_record(PBSEVENT_SECURITY, PBS_EVENTCLASS_SERVER, __func__, log_buf);

    return(0);
    }

  /* user name cannot be longer than PBS_MAXUSER*/
  if (strlen(user) > PBS_MAXUSER)
    {
    sprintf(log_buf, "Invalid user: %s", user);

    log_record(PBSEVENT_SECURITY, PBS_EVENTCLASS_SERVER, __func__, log_buf);
     
    return(0);
    }

  if (!host)
    return(0);

  colon_loc = strchr(host, ':');

  /* if the request host has port information in it, we want to strip it out */

  if (colon_loc == NULL) 
    {
    /* no colon found */
    num_host_chars = strlen(host);

    sprintf(host_no_port, "%s", host);
    }
  else
    {
    num_host_chars = colon_loc - host;

    /* actually remove the colon for host_no_port */
    *colon_loc = '\0';
    sprintf(host_no_port,"%s",host);
    *colon_loc = ':';
    }

  /* num_host_chars cannot be more than PBS_MAXHOSTNAME */
  if (num_host_chars > PBS_MAXHOSTNAME)
    {
    snprintf(log_buf, sizeof(log_buf), "Invalid host: %s", host);

    log_record(PBSEVENT_SECURITY, PBS_EVENTCLASS_SERVER, __func__, log_buf);

    return(0);
    }

  sprintf(uh, "%s@%s", user, host);

  server_addr = get_hostaddr(&my_err, server_host);
  connect_addr = get_hostaddr(&my_err, host_no_port);

#ifdef __CYGWIN__
  if ((IamAdminByName(user)) && 
      (server_addr == connect_addr))
    {
    return(priv | ATR_DFLAG_MGRD | ATR_DFLAG_MGWR | ATR_DFLAG_OPRD | ATR_DFLAG_OPWR);
    }
#else /* __CYGWIN__ */

  local_server_addr = get_hostaddr(&my_err, server_localhost);

  if ((strcmp(user, PBS_DEFAULT_ADMIN) == 0) &&
      ((connect_addr == server_addr) || 
       (connect_addr == local_server_addr)))
    {
    is_root = 1;

#ifdef PBS_ROOT_ALWAYS_ADMIN
    if (is_root)
      {
      /* This statement allows us to compile with gcc-warnings */
      /* if PBS_ROOT_ALWAYS_ADMIN is true is_root is assigned but never used */
      ;
      }
    return(priv | ATR_DFLAG_MGRD | ATR_DFLAG_MGWR | ATR_DFLAG_OPRD | ATR_DFLAG_OPWR);
#endif
    }
#endif /* __CYGWIN__ */

  pthread_mutex_lock(server.sv_attr_mutex);
  if (!(server.sv_attr[SRV_ATR_managers].at_flags & ATR_VFLAG_SET))
    {
#ifndef PBS_ROOT_ALWAYS_ADMIN
    if (is_root)
      priv |= (ATR_DFLAG_MGRD | ATR_DFLAG_MGWR);
#endif
    }
  else if (acl_check(&server.sv_attr[SRV_ATR_managers], uh, ACL_User))
    {
    priv |= (ATR_DFLAG_MGRD | ATR_DFLAG_MGWR);
    }

  if (!(server.sv_attr[SRV_ATR_operators].at_flags & ATR_VFLAG_SET))
    {
#ifndef PBS_ROOT_ALWAYS_ADMIN
    if (is_root)
      priv |= (ATR_DFLAG_OPRD | ATR_DFLAG_OPWR);
#endif
    }
  else if (acl_check(&server.sv_attr[SRV_ATR_operators], uh, ACL_User))
    {
    priv |= (ATR_DFLAG_OPRD | ATR_DFLAG_OPWR);
    }
  pthread_mutex_unlock(server.sv_attr_mutex);

  /* resolve using the other hostname (if available) and give the higher privilege */
  other_host = get_cached_fullhostname(host, NULL);
  
  if ((other_host != NULL) &&
      (strcmp(host, other_host)))
    other_priv = svr_get_privilege(user, other_host);

  if (other_priv > priv)
    priv = other_priv;

  return(priv);
  }  /* END svr_get_privilege() */





/*
 * authenticate_user - authenticate user by checking name against credential
 *         provided on connection via Authenticate User request.
 *
 * Returns 0 if user is who s/he claims, non-zero error code if not
 */

int authenticate_user(

  struct batch_request *preq,  /* I */
  struct credential    *pcred,
  char   **autherr) /* O */

  {
  int    rc;
  char   uath[PBS_MAXUSER + PBS_MAXHOSTNAME + 1];
  time_t time_now = time(NULL);
  char   error_msg[1024];
  bool   acl_enabled = false;

#ifdef MUNGE_AUTH
 
  if (strncmp(preq->rq_user, pcred->username, PBS_MAXUSER))
    {
    /* extra check for munge */
    struct array_strings *my_acl = NULL;
    char uh[PBS_MAXUSER + PBS_MAXHOSTNAME + 2];

    sprintf(uh, "%s@%s", preq->rq_user, pcred->hostname);
    
    get_svr_attr_arst(SRV_ATR_authusers, &my_acl); 
    if ((acl_check_my_array_string(my_acl, uh, ACL_User_Host)) == 0)
      {
      *autherr = strdup("User not in authorized user list.");
      sprintf(error_msg, "%s Requested user %s: requested from host %s",
                     *autherr, preq->rq_user, preq->rq_host);
      log_event(PBSEVENT_ADMIN, PBS_EVENTCLASS_SERVER, __func__, error_msg);
      return(PBSE_BADCRED);
      }
    }
#else
  if (strncmp(preq->rq_user, pcred->username, PBS_MAXUSER))
    {
    *autherr = strdup("Users do not match");
    sprintf(error_msg, "%s: Requested user %s: credential user %s: requested from host %s",
                   *autherr, preq->rq_user, pcred->username, preq->rq_host);
    log_event(PBSEVENT_ADMIN, PBS_EVENTCLASS_SERVER, __func__, error_msg);
    return(PBSE_BADCRED);
    }
#endif

  if (strncmp(preq->rq_host, pcred->hostname, PBS_MAXHOSTNAME))
    {
    struct sockaddr_in *sai1;
    struct sockaddr_in *sai2;
    struct addrinfo    *addr_info1 = NULL;
    struct addrinfo    *addr_info2 = NULL;

    sai1 = get_cached_addrinfo(preq->rq_host);
    sai2 = get_cached_addrinfo(pcred->hostname);

    if ((sai1 == NULL) &&
        (pbs_getaddrinfo(preq->rq_host, NULL, &addr_info1) == PBSE_NONE))
      {
      sai1 = (struct sockaddr_in *)addr_info1->ai_addr;
      }

    if ((sai2 == NULL) &&
        (pbs_getaddrinfo(pcred->hostname, NULL, &addr_info2) == PBSE_NONE))
      {
      sai2 = (struct sockaddr_in *)addr_info2->ai_addr;
      }

    if ((sai1 == NULL) ||
        (sai2 == NULL) ||
        (memcmp(sai1, sai2, sizeof(struct sockaddr_in))))
      {
      *autherr = strdup("Hosts do not match");
      
      sprintf(error_msg, "%s: Requested host %s: credential host: %s",
        *autherr, preq->rq_host, pcred->hostname);
      log_event(PBSEVENT_ADMIN, PBS_EVENTCLASS_SERVER, __func__, error_msg);
    
      return(PBSE_BADCRED);
      }
    }

  if (pcred->timestamp)
    {
    long lifetime = 0;
    if (get_svr_attr_l(SRV_ATR_CredentialLifetime, &lifetime) == PBSE_NONE)
      {
      /* use configured value if set */
      }
    else 
      {
      /* if not use the default */
      lifetime = CREDENTIAL_LIFETIME;
      }

    /* negative values mean that credentials have an infinite lifetime */
    if (lifetime > -1)
      {
      if ((pcred->timestamp - CREDENTIAL_TIME_DELTA > time_now) ||
          (pcred->timestamp + lifetime < time_now))
        {
        return(PBSE_EXPIRED);
        }
      }

    }

  /* If Server's Acl_User enabled, check if user in list */
  get_svr_attr_b(SRV_ATR_AclUserEnabled, &acl_enabled);
  if (acl_enabled)
    {
    struct array_strings *acl_users = NULL;
    snprintf(uath, sizeof(uath), "%s@%s", preq->rq_user, preq->rq_host);
    
    get_svr_attr_arst(SRV_ATR_AclUsers, &acl_users);
    if (acl_check_my_array_string(acl_users, uath, ACL_User) == 0)
      {
      int       my_err;
      pbs_net_t connect_addr = get_hostaddr(&my_err, preq->rq_host);
      pbs_net_t server_addr = get_hostaddr(&my_err, server_host);

#ifdef __CYGWIN__

      if ((!IamAdminByName(preq->rq_user)) || 
          (connect_addr != server_addr))
        {
        return(PBSE_PERM);
        }
#else /* __CYGWIN__ */
#ifdef PBS_ROOT_ALWAYS_ADMIN
      if ((strcmp(preq->rq_user, PBS_DEFAULT_ADMIN) != 0) ||
          (connect_addr != server_addr))
        {
        return(PBSE_PERM);
        }

#else /* PBS_ROOT_ALWAYS_ADMIN */
      return(PBSE_PERM);

#endif /* PBS_ROOT_ALWAYS_ADMIN */
#endif /* __CYGWIN__ */

      }
    }

  /* A site stub for additional checking */

  rc = site_allow_u(preq->rq_user, preq->rq_host);

  return(rc);
  }  /* END authenticate_user() */





void chk_job_req_permissions(

  job                  **pjob_ptr, /* M */
  struct batch_request  *preq) /* I */

  {
  job  *pjob = *pjob_ptr;
  char  tmpLine[MAXLINE];
  char  log_buf[LOCAL_LOG_BUF_SIZE];

  if (svr_authorize_jobreq(preq, pjob) == -1)
    {
    sprintf(log_buf, msg_permlog,
      preq->rq_type,
      "Job",
      pjob->ji_qs.ji_jobid,
      preq->rq_user,
      preq->rq_host);

    log_event(PBSEVENT_SECURITY,PBS_EVENTCLASS_JOB,pjob->ji_qs.ji_jobid,log_buf);

    req_reject(PBSE_PERM, 0, preq, NULL, "operation not permitted");

    unlock_ji_mutex(pjob, __func__, "1", LOGLEVEL);

    *pjob_ptr = NULL;
    }
  else if (pjob->ji_qs.ji_state >= JOB_STATE_EXITING)
    {
    /* job has completed */

    switch (preq->rq_type)
      {

      case PBS_BATCH_Rerun:

        /* allow re-run to be executed for completed jobs */

        /* NO-OP */

        break;

      default:

        sprintf(log_buf, "%s %s",
          pbse_to_txt(PBSE_BADSTATE),
          PJobState[pjob->ji_qs.ji_state]);

        log_event(PBSEVENT_DEBUG,PBS_EVENTCLASS_JOB,pjob->ji_qs.ji_jobid,log_buf);

        snprintf(tmpLine, sizeof(tmpLine), 
          "invalid state for job - %s",
          PJobState[pjob->ji_qs.ji_state]);

        req_reject(PBSE_BADSTATE, 0, preq, NULL, tmpLine);

        unlock_ji_mutex(pjob, __func__, "2", LOGLEVEL);

        *pjob_ptr = NULL;

        break;
      }  /* END switch (preq->rq_type) */
    }    /* END if (pjob->ji_qs.ji_state >= JOB_STATE_EXITING) */

  /* SUCCESS - request is valid */
  } /* END chk_job_req_permissions() */





/*
 * chk_job_request - check legality of a request against a job
 *
 * this checks the most conditions common to most job batch requests.
 * It also returns a pointer to the job if found and the tests pass.
 */


job *chk_job_request(

  char                 *jobid,  /* I */
  struct batch_request *preq)   /* I */

  {
  job *pjob = NULL;

  if ((pjob = svr_find_job(jobid, FALSE)) == NULL)
    {
    log_event(
      PBSEVENT_DEBUG,
      PBS_EVENTCLASS_JOB,
      jobid,
      pbse_to_txt(PBSE_UNKJOBID));

    req_reject(PBSE_UNKJOBID, 0, preq, NULL, "cannot locate job");

    return(NULL);
    }

  /* if we aren't authorized, pjob will be set to NULL in chk_job_req_permissions */
  chk_job_req_permissions(&pjob,preq);

  return(pjob);
  }  /* END chk_job_request() */



