
/* 
 * These constants is for protocol MSN 11
 * and using the last MSN messenger 7.0.0813
 * Licence: public domain
 */
#define MSN_PRODUCT_ID "PROD0101{0RM?UBW"
#define MSN_PRODUCT_KEY "CFHUR$52U_{VIX5T"
#define MSN_MAGIC_NUM 0x0E79A9C1UL

#include <openssl/md5.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>

#include <endian.h> 

# if __BYTE_ORDER == __BIG_ENDIAN
#define WORDS_BIGENDIAN 1
#endif
    
#ifdef HAVE_BYTESWAP_H
#include <byteswap.h>
#else
#define bswap_32(x) \
       ((((x) & 0xff000000) >> 24) |\
	(((x) & 0x00ff0000) >>  8) |\
        (((x) & 0x0000ff00) <<  8) |\
	(((x) & 0x000000ff) << 24))
#endif

#ifdef WORDS_BIGENDIAN
#define cpu_to_le32(x) bswap_32(x)
#else
#define cpu_to_le32(x) (x)
#endif

void pt(const unsigned char *md)
{
  int i;

  for (i=0; i<16;i++)
    printf("%02x",md[i]);
  printf("\n");
}

int docheck(const char *challenge, const char *result)
{
  MD5_CTX md5;
  unsigned char md[MD5_DIGEST_LENGTH];
  unsigned int md5split[MD5_DIGEST_LENGTH];
  unsigned int *mdints;
  unsigned char *chlbychar;
  unsigned char *chlstring;
  unsigned long long crchigh, crclow;	/* use 64 bits math */
  unsigned int i, len;
  static const char hexchars[]="0123456789abcdef";
  static char challenge_response[33];

  printf("challenge: %s\n", challenge);

  /* Calculate the md5 for "challenge+MSN_PRODUCT_KEY" */
  MD5_Init(&md5);
  MD5_Update(&md5, challenge, strlen(challenge));
  MD5_Update(&md5, MSN_PRODUCT_KEY, strlen(MSN_PRODUCT_KEY));
  MD5_Final(md, &md5);

  for (i=0; i<4; i++) 
   {
     /* read a little 32 bits in little-endian format */
     unsigned int a;
     a = (md[i*4+0])
       | (md[i*4+1]<<8)
       | (md[i*4+2]<<16)
       | (md[i*4+3]<<24);
     md5split[i] = a & 0x7FFFFFFF;
   }

  /* Concatenate and pad "challenge+MSN_PRODUCT_ID+00000" */
  len = strlen(challenge) + strlen(MSN_PRODUCT_ID);
  chlstring = malloc(len + 9);
  if (chlstring == NULL)
    return -1;
  memcpy(chlstring, challenge, strlen(challenge));
  memcpy(chlstring+strlen(challenge), MSN_PRODUCT_ID, strlen(MSN_PRODUCT_ID));
  while ( (len % 8) )
   {
     chlstring[len]='0';
     len++;
   }
  //chlstring[len]=0;
  //printf("pad: [%s]\n", chlstring);

  /* Make the key */
  chlbychar = chlstring;
  crchigh = crclow = 0;

  for (i=0; i <(len/4)-1; i+=2)
   {
     unsigned long long temp;
     /* read a 32 bits little endian */
     temp  = *chlbychar++;
     temp |= (*chlbychar++)<<8;
     temp |= (*chlbychar++)<<16;
     temp |= (*chlbychar++)<<24;
     temp *= MSN_MAGIC_NUM;
     temp %= 0x7FFFFFFF;
     temp += crchigh;
     temp *= md5split[0];
     temp += md5split[1];
     temp %= 0x7FFFFFFF;

     crchigh = *chlbychar++;
     crchigh |= (*chlbychar++)<<8;
     crchigh |= (*chlbychar++)<<16;
     crchigh |= (*chlbychar++)<<24;
     crchigh += temp;
     crchigh %= 0x7FFFFFFF;
     crchigh *= md5split[2];
     crchigh += md5split[3];
     crchigh %= 0x7FFFFFFF;

     crclow += crchigh;
     crclow += temp;

   }      
  free(chlstring);

  /* finalize the key */
  crchigh = (crchigh + md5split[1]) % 0x7FFFFFFF;
  crclow  = (crclow  + md5split[3]) % 0x7FFFFFFF;

  /* convert the key in little endian mode */
  crchigh = cpu_to_le32(crchigh);
  crclow  = cpu_to_le32(crclow);

  //printf("high: %8.8llx\n", crchigh);
  //printf("low: %8.8llx\n", crclow);

  /* XOR two 64bits numbers with the key */
  mdints = (unsigned int *)md;

  mdints[0] ^= crchigh;
  mdints[1] ^= crclow;
  mdints[2] ^= crchigh;
  mdints[3] ^= crclow;

  for (i=0; i<16; i++) {
     challenge_response[(i*2)+0] = hexchars[(md[i]>>4)];
     challenge_response[(i*2)+1] = hexchars[md[i]&0xF];
  }
  challenge_response[32] = 0;

  printf("response: %s", challenge_response);
  if (result)
   {
     if (strcmp(challenge_response, result) == 0)
       printf("  OK  ");
     else
       printf("  FAILED  ");
   }
  printf("\n");


  return 0;
}

int main(int argc, char **argv)
{
  int i;
  if (argc==1)
   {
     docheck("14890326678030297613", "ad5df75a7fa53070e7e524cbeb6613e5");
     docheck("10760964723810145612", "f5334a4e5c6f9540b9517ffadd66bec5");
     docheck("22838249463153087502", "ad31b243ef1ba6655d5cbb0c345b254f");
     docheck("43122731629871371520", "65744ca8b2bfb9a51f42fd483780f885");
   }
  else 
   {
     for (i=1; i<argc; i++)
       docheck(argv[i], NULL);
   }
  return 0;
}


