///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, Microsoft Corp. All rights reserved.
//
// FILE
//
//    logresult.cpp
//
// SYNOPSIS
//
//    Defines the function IASRadiusLogResult.
//
///////////////////////////////////////////////////////////////////////////////

#include <radcommon.h>
#include <iastlutl.h>
#include <iasutil.h>
#include <logresult.h>

// Dummy attribute ID for the stringized reason code.
const DWORD IAS_ATTRIBUTE_REASON_STRING = 0xBADF00D;

// An empty string.
WCHAR emptyString[1];

// Create a newly-allocated copy of a string.
PWSTR copyString(PCWSTR sz) throw ()
{
   if (sz)
   {
      // Calculate the number of bytes.
      size_t nbyte = (wcslen(sz) + 1) * sizeof(WCHAR);

      // Allocate the memory.
      PVOID p = LocalAlloc(LMEM_FIXED, nbyte);
      if (p)
      {
         // Copy the string and return.
         return (PWSTR)memcpy(p, sz, nbyte);
      }
   }

   // If anything went wrong, return an empty string.
   return emptyString;
}

// Frees a string returned by copyString.
void freeString(PWSTR sz) throw ()
{
   if (sz != emptyString) { LocalFree(sz); }
}

// Format an integer value.
PWSTR formatInteger(DWORD value) throw ()
{
   WCHAR buffer[11], *p = buffer + 10;
   *p = L'\0';
   do { *--p = L'0' + (WCHAR)(value % 10); } while (value /= 10);
   return copyString(p);
}

// Format a parameter value.
PWSTR formatParameter(DWORD value) throw ()
{
   WCHAR buffer[13], *p = buffer + 12;
   *p = L'\0';
   do { *--p = L'0' + (WCHAR)(value % 10); } while (value /= 10);
   *--p = L'%';
   *--p = L'%';
   return copyString(p);
}

// Format the IAS_ATTRIBUTE_PROVIDER_TYPE value.
PWSTR formatProviderType(DWORD type) throw ()
{
   switch (type)
   {
      case IAS_PROVIDER_WINDOWS:
         return formatParameter(IASP_PROVIDER_WINDOWS);
      case IAS_PROVIDER_RADIUS_PROXY:
         return formatParameter(IASP_PROVIDER_RADIUS_PROXY);
      default:
         return formatParameter(IASP_NONE);
   }
}

// Format the IAS_ATTRIBUTE_AUTHENTICATION_TYPE value.
PWSTR formatAuthType(DWORD type) throw ()
{
   switch (type)
   {
      case IAS_AUTH_PAP:
         return copyString(L"PAP");
      case IAS_AUTH_MD5CHAP:
         return copyString(L"MD5-CHAP");
      case IAS_AUTH_MSCHAP:
         return copyString(L"MS-CHAPv1");
      case IAS_AUTH_MSCHAP2:
         return copyString(L"MS-CHAPv2");
      case IAS_AUTH_EAP:
         return copyString(L"EAP");
      case IAS_AUTH_ARAP:
         return copyString(L"ARAP");
      case IAS_AUTH_NONE:
         return copyString(L"Unauthenticated");
      case IAS_AUTH_CUSTOM:
         return copyString(L"Extension");
      case IAS_AUTH_MSCHAP_CPW:
         return copyString(L"MS-CHAPv1 CPW");
      case IAS_AUTH_MSCHAP2_CPW:
         return copyString(L"MS-CHAPv2 CPW");
      default:
         return formatInteger(type);
   }
}

// Format the NAS-Port-Type value.
PWSTR formatPortType(DWORD type) throw ()
{
   switch (type)
   {
      case 0:
         return copyString(L"Async");
      case 1:
         return copyString(L"Sync");
      case 2:
         return copyString(L"ISDN Sync");
      case 3:
         return copyString(L"ISDN Async V.120");
      case 4:
         return copyString(L"ISDN Async V.110");
      case 5:
        return copyString(L"Virtual");
      case 6:
        return copyString(L"PIAFS");
      case 7:
        return copyString(L"HDLC Clear Channel");
      case 8:
        return copyString(L"X.25");
      case 9:
        return copyString(L"X.75");
      case 10:
        return copyString(L"G.3 Fax");
      case 11:
        return copyString(L"SDSL");
      case 12:
        return copyString(L"ADSL-CAP");
      case 13:
        return copyString(L"ADSL-DMT");
      case 14:
        return copyString(L"IDSL");
      case 15:
        return copyString(L"Ethernet");
      case 16:
        return copyString(L"xDSL");
      case 17:
        return copyString(L"Cable");
      case 18:
        return copyString(L"Wireless - Other");
      case 19:
        return copyString(L"Wireless - IEEE 802.11");
      default:
        return formatInteger(type);
   }
}

PWSTR formatAttribute(
          IRequest* request,
          IAttributesRaw* raw,
          DWORD dwId,
          DWORD defaultValue
          ) throw ()
{
   // Is this one of the 'special' attributes ?
   switch (dwId)
   {
      case IAS_ATTRIBUTE_REASON_CODE:
      {
         LONG reason = 0;
         request->get_Reason(&reason);
         return formatInteger(reason);
      }

      case IAS_ATTRIBUTE_REASON_STRING:
      {
         LONG reason = 0;
         request->get_Reason(&reason);
         return formatParameter(reason + 0x1000);
      }
   }

   // Get a single attribute with the give ID.
   DWORD posCount = 1;
   ATTRIBUTEPOSITION pos;
   raw->GetAttributes(&posCount, &pos, 1, &dwId);

   // If it's not present, use the defaultValue parameter.
   if (!posCount) { return formatParameter(defaultValue); }

   // Otherwise, save and release.
   const IASVALUE& val = pos.pAttribute->Value;
   IASAttributeRelease(pos.pAttribute);

   // Format the value.
   switch (val.itType)
   {
      case IASTYPE_ENUM:
      case IASTYPE_INTEGER:
      {
         switch (dwId)
         {
            case RADIUS_ATTRIBUTE_NAS_PORT_TYPE:
               return formatPortType(val.Enumerator);

            case IAS_ATTRIBUTE_PROVIDER_TYPE:
               return formatProviderType(val.Enumerator);

            case IAS_ATTRIBUTE_AUTHENTICATION_TYPE:
               return formatAuthType(val.Enumerator);

            // Fall through.
         }

         return formatInteger(val.Integer);
      }

      case IASTYPE_INET_ADDR:
      {
         WCHAR buffer[16];
         return copyString(ias_inet_htow(val.InetAddr, buffer));
      }

      case IASTYPE_STRING:
      {
         if (val.String.pszWide)
         {
            return copyString(val.String.pszWide);
         }
         else
         {
            USES_CONVERSION;
            return copyString(A2W(val.String.pszAnsi));
         }
      }

      case IASTYPE_OCTET_STRING:
      {
         return copyString(IAS_OCT2WIDE(val.OctetString));
      }
   }

   return emptyString;
}

///////
// An InsertionString definition consists of the attribute ID and a
// defaultValue to be used if the attribute isn't present.
///////
struct InsertionString
{
   DWORD attrID;
   DWORD defaultValue;
};

// Insertion strings for an Access-Accept.
InsertionString ACCEPT_ATTRS[] =
{
   { RADIUS_ATTRIBUTE_USER_NAME,              IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_FULLY_QUALIFIED_USER_NAME, IASP_UNDETERMINED },
   { RADIUS_ATTRIBUTE_NAS_IP_ADDRESS,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_IDENTIFIER,         IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_CLIENT_NAME,               IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_CLIENT_IP_ADDRESS,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_CALLING_STATION_ID,     IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_PORT_TYPE,          IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_PORT,               IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_PROXY_POLICY_NAME,         IASP_NONE         },
   { IAS_ATTRIBUTE_PROVIDER_TYPE,             IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_REMOTE_SERVER_ADDRESS,     IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_NP_NAME,                   IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_AUTHENTICATION_TYPE,       IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_EAP_FRIENDLY_NAME,         IASP_UNDETERMINED },
   { ATTRIBUTE_UNDEFINED,                     IASP_UNDETERMINED }
};

// Insertion strings for an Access-Reject.
InsertionString REJECT_ATTRS[] =
{
   { RADIUS_ATTRIBUTE_USER_NAME,              IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_FULLY_QUALIFIED_USER_NAME, IASP_UNDETERMINED },
   { RADIUS_ATTRIBUTE_NAS_IP_ADDRESS,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_IDENTIFIER,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_CALLED_STATION_ID,      IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_CALLING_STATION_ID,     IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_CLIENT_NAME,               IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_CLIENT_IP_ADDRESS,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_PORT_TYPE,          IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_PORT,               IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_PROXY_POLICY_NAME,         IASP_NONE         },
   { IAS_ATTRIBUTE_PROVIDER_TYPE,             IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_REMOTE_SERVER_ADDRESS,     IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_NP_NAME,                   IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_AUTHENTICATION_TYPE,       IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_EAP_FRIENDLY_NAME,         IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_REASON_CODE,               IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_REASON_STRING,             IASP_UNDETERMINED },
   { ATTRIBUTE_UNDEFINED,                     IASP_UNDETERMINED }
};

// Insertion strings for a discarded request.
InsertionString DISCARD_ATTRS[] =
{
   { RADIUS_ATTRIBUTE_USER_NAME,              IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_FULLY_QUALIFIED_USER_NAME, IASP_UNDETERMINED },
   { RADIUS_ATTRIBUTE_NAS_IP_ADDRESS,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_IDENTIFIER,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_CALLED_STATION_ID,      IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_CALLING_STATION_ID,     IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_CLIENT_NAME,               IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_CLIENT_IP_ADDRESS,         IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_PORT_TYPE,          IASP_NOT_PRESENT  },
   { RADIUS_ATTRIBUTE_NAS_PORT,               IASP_NOT_PRESENT  },
   { IAS_ATTRIBUTE_PROXY_POLICY_NAME,         IASP_NONE         },
   { IAS_ATTRIBUTE_PROVIDER_TYPE,             IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_REMOTE_SERVER_ADDRESS,     IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_REASON_CODE,               IASP_UNDETERMINED },
   { IAS_ATTRIBUTE_REASON_STRING,             IASP_UNDETERMINED },
   { ATTRIBUTE_UNDEFINED,                     IASP_UNDETERMINED }
};

VOID
WINAPI
IASRadiusLogResult(
    IRequest* request,
    IAttributesRaw* raw
    )
{
   // We only care about Access-Requests.
   LONG type = 0;
   request->get_Request(&type);
   if (type != IAS_REQUEST_ACCESS_REQUEST) { return; }

   // Determine which response type this is.
   request->get_Response(&type);
   DWORD eventID;
   InsertionString* attrs;
   switch (type)
   {
      case IAS_RESPONSE_ACCESS_ACCEPT:
         eventID = IAS_RESPONSE_ACCEPT;
         attrs = ACCEPT_ATTRS;
         break;

      case IAS_RESPONSE_ACCESS_REJECT:
         eventID = IAS_RESPONSE_REJECT;
         attrs = REJECT_ATTRS;
         break;

      case IAS_RESPONSE_DISCARD_PACKET:
         eventID = IAS_RESPONSE_DISCARD;
         attrs = DISCARD_ATTRS;
         break;

      default:
         return;
   }

   // Format the insertion strings.
   PWSTR strings[24];
   DWORD numStrings = 0;
   for ( ; attrs->attrID != ATTRIBUTE_UNDEFINED; ++attrs, ++numStrings)
   {
      strings[numStrings] = formatAttribute(
                                request,
                                raw,
                                attrs->attrID,
                                attrs->defaultValue
                                );
   }

   // Report the event.
   IASReportEvent(
       eventID,
       numStrings,
       0,
       (PCWSTR*)strings,
       NULL
       );

   // Free the insertion strings.
   while (numStrings--)
   {
      freeString(strings[numStrings]);
   }
}