///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1998, Microsoft Corp. All rights reserved.
//
// FILE
//
//    rasuser.cpp
//
// SYNOPSIS
//
//    This file defines the class RasUser.
//
// MODIFICATION HISTORY
//
//    03/20/1998    Original version.
//    04/24/1998    Allow null domain for local server.
//    05/19/1998    Converted to NtSamHandler.
//    06/03/1998    Always use RAS/MPR for local users.
//    06/23/1998    Use DCLocator to find server.
//    07/09/1998    Always use RasAdminUserGetInfo
//    07/11/1998    Switch to IASGetRASUserInfo.
//    07/28/1998    Handle case where IAS is running on a DC.
//    08/10/1998    Fix initialization error codes.
//    03/10/1999    Use LDAP when possible.
//    03/23/1999    Store the user's DN.
//
///////////////////////////////////////////////////////////////////////////////

#include <ias.h>
#include <iastlutl.h>

#define IASSAMAPI

#include <iaslsa.h>
#include <iasntds.h>
#include <iasparms.h>

#include <sdoias.h>

#include <rasuser.h>
#include <userschema.h>

//////////
// Attributes that should be retrieved for each user.
//////////
const PCWSTR USER_PARMS[] =
{
   L"userParameters",
   NULL
};

HRESULT RasUser::initialize() throw ()
{
   //////////
   // Let's get everything that could fail out of the way first.
   //////////

   PIASATTRIBUTE attrs[3];
   DWORD error = IASAttributeAlloc(3, attrs);
   if (error) { return HRESULT_FROM_WIN32(error); }

   //////////
   // Initialize the dial-in bit attributes.
   //////////

   attrs[0]->dwId = IAS_ATTRIBUTE_ALLOW_DIALIN;
   attrs[0]->Value.itType = IASTYPE_BOOLEAN;
   attrs[0]->Value.Boolean = TRUE;
   attrs[1]->dwFlags = 0;
   allowAccess.attach(attrs[0], false);

   attrs[1]->dwId = IAS_ATTRIBUTE_ALLOW_DIALIN;
   attrs[1]->Value.itType = IASTYPE_BOOLEAN;
   attrs[1]->Value.Boolean = FALSE;
   attrs[1]->dwFlags = 0;
   denyAccess.attach(attrs[1], false);

   attrs[2]->dwId = RADIUS_ATTRIBUTE_SERVICE_TYPE;
   attrs[2]->Value.itType = IASTYPE_ENUM;
   attrs[2]->Value.Enumerator = 4;
   attrs[2]->dwFlags = IAS_INCLUDE_IN_ACCEPT;
   callbackFramed.attach(attrs[2], false);

   return S_OK;
}

void RasUser::finalize() throw ()
{
   allowAccess.release();
   denyAccess.release();
   callbackFramed.release();
}

IASREQUESTSTATUS RasUser::processUser(
                              IASRequest& request,
                              PCWSTR domainName,
                              PCWSTR username
                              )
{
   IASTraceString("Using downlevel dial-in parameters.");

   DWORD error;
   RAS_USER_0 ru0;

   // Try using LDAP first since it's fastest.
   PLDAPMessage res;
   error = IASNtdsQueryUserAttributes(
               domainName,
               username,
               LDAP_SCOPE_SUBTREE,
               const_cast<PWCHAR*>(USER_PARMS),
               &res
               );
   if (error == NO_ERROR)
   {
      // Retrieve the connection for this message.
      LDAP* ld = ldap_conn_from_msg(NULL, res);

      LDAPMessage* entry = ldap_first_entry(ld, res);
      if (entry)
      {
         // Store the user's DN.
         PWCHAR dn = ldap_get_dnW(ld, entry);
         IASStoreFQUserName(request, DS_FQDN_1779_NAME, dn);
         ldap_memfree(dn);

         // There is at most one attribute.
         PWCHAR *str = ldap_get_valuesW(
                           ld,
                           entry,
                           const_cast<PWCHAR>(USER_PARMS[0])
                           );

         // It's okay if we didn't get anything, the API can handle NULL
         // UserParameters.
         error = IASParmsQueryRasUser0((str ? *str : NULL), &ru0);

         ldap_value_freeW(str);
      }
      else
      {
         error = ERROR_NO_SUCH_USER;
      }

      ldap_msgfree(res);
   }
   else if (error == ERROR_DS_NOT_INSTALLED)
   {
      // No DS, so fall back to SAM APIs.
      error = IASGetRASUserInfo(username, domainName, &ru0);
   }

   if (error)
   {
      IASTraceFailure("Per-user attribute retrieval", error);

      HRESULT hr = IASMapWin32Error(error, IAS_SERVER_UNAVAILABLE);

      return IASProcessFailure(request, hr);
   }

   // Used for injecting single attributes.
   ATTRIBUTEPOSITION pos, *first, *last;
   first = &pos;
   last  = first + 1;

   //////////
   // Insert the always present Allow-Dialin attribute.
   //////////

   if ((ru0.bfPrivilege & RASPRIV_DialinPrivilege) == 0)
   {
      first->pAttribute = denyAccess;
   }
   else
   {
      first->pAttribute = allowAccess;
   }

   IASTraceString("Inserting attribute msNPAllowDialin.");
   OverwriteAttribute(request, first, last);

   //////////
   // Insert the "Callback Framed" service type if callback is allowed.
   //////////

   if ((ru0.bfPrivilege & RASPRIV_CallbackType) != RASPRIV_NoCallback)
   {
      first->pAttribute = callbackFramed;

      IASTraceString("Inserting attribute msRADIUSServiceType.");
      OverwriteAttribute(request, first, last);
   }

   //////////
   // Insert the Callback-Number if present.
   //////////

   if (ru0.bfPrivilege & RASPRIV_AdminSetCallback)
   {
      IASAttribute callback(true);
      callback->dwId = RADIUS_ATTRIBUTE_CALLBACK_NUMBER;
      callback.setOctetString(ru0.wszPhoneNumber);
      callback.setFlag(IAS_INCLUDE_IN_ACCEPT);

      first->pAttribute = callback;

      IASTraceString("Inserting attribute msRADIUSCallbackNumber.");
      OverwriteAttribute(request, first, last);
   }

   IASTraceString("Successfully retrieved per-user attributes.");
   return IAS_REQUEST_STATUS_HANDLED;
}