/*
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Copyright (C) 2012  Joshua M. Clulow <josh@sysmgr.org>
 */

var log = console.log;
var crypto = require('crypto');
var $ = require('./common');
var lmhashbuf = require('./smbhash').lmhashbuf;
var nthashbuf = require('./smbhash').nthashbuf;


function encodeType1(hostname, ntdomain) {
  hostname = hostname.toUpperCase();
  ntdomain = ntdomain.toUpperCase();
  var hostnamelen = Buffer.byteLength(hostname, 'ascii');
  var ntdomainlen = Buffer.byteLength(ntdomain, 'ascii');

  var pos = 0;
  var buf = new Buffer(32 + hostnamelen + ntdomainlen);

  buf.write('NTLMSSP', pos, 7, 'ascii'); // byte protocol[8];
  pos += 7;
  buf.writeUInt8(0, pos);
  pos++;

  buf.writeUInt8(0x01, pos); // byte type;
  pos++;

  buf.fill(0x00, pos, pos + 3); // byte zero[3];
  pos += 3;

  buf.writeUInt16LE(0xb203, pos); // short flags;
  pos += 2;

  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
  pos += 2;
  buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
  pos += 2;

  var ntdomainoff = 0x20 + hostnamelen;
  buf.writeUInt16LE(ntdomainoff, pos); // short dom_off;
  pos += 2;

  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.writeUInt16LE(hostnamelen, pos); // short host_len;
  pos += 2;
  buf.writeUInt16LE(hostnamelen, pos); // short host_len;
  pos += 2;

  buf.writeUInt16LE(0x20, pos); // short host_off;
  pos += 2;

  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.write(hostname, 0x20, hostnamelen, 'ascii');
  buf.write(ntdomain, ntdomainoff, ntdomainlen, 'ascii');

  return buf;
}


/*
 * 
 */
function decodeType2(buf)
{
  var proto = buf.toString('ascii', 0, 7);
  if (buf[7] !== 0x00 || proto !== 'NTLMSSP')
    throw new Error('magic was not NTLMSSP');

  var type = buf.readUInt8(8);
  if (type !== 0x02)
    throw new Error('message was not NTLMSSP type 0x02');

  //var msg_len = buf.readUInt16LE(16);

  //var flags = buf.readUInt16LE(20);

  var nonce = buf.slice(24, 32);
  return nonce;
}

function encodeType3(username, hostname, ntdomain, nonce, password) {
  hostname = hostname.toUpperCase();
  ntdomain = ntdomain.toUpperCase();

  var lmh = new Buffer(21);
  lmhashbuf(password).copy(lmh);
  lmh.fill(0x00, 16); // null pad to 21 bytes
  var nth = new Buffer(21);
  nthashbuf(password).copy(nth);
  nth.fill(0x00, 16); // null pad to 21 bytes

  var lmr = makeResponse(lmh, nonce);
  var ntr = makeResponse(nth, nonce);

  var usernamelen = Buffer.byteLength(username, 'ucs2');
  var hostnamelen = Buffer.byteLength(hostname, 'ucs2');
  var ntdomainlen = Buffer.byteLength(ntdomain, 'ucs2');
  var lmrlen = 0x18;
  var ntrlen = 0x18;

  var ntdomainoff = 0x40;
  var usernameoff = ntdomainoff + ntdomainlen;
  var hostnameoff = usernameoff + usernamelen;
  var lmroff = hostnameoff + hostnamelen;
  var ntroff = lmroff + lmrlen;

  var pos = 0;
  var msg_len = 64 + ntdomainlen + usernamelen + hostnamelen + lmrlen + ntrlen;
  var buf = new Buffer(msg_len);

  buf.write('NTLMSSP', pos, 7, 'ascii'); // byte protocol[8];
  pos += 7;
  buf.writeUInt8(0, pos);
  pos++;

  buf.writeUInt8(0x03, pos); // byte type;
  pos++;

  buf.fill(0x00, pos, pos + 3); // byte zero[3];
  pos += 3;

  buf.writeUInt16LE(lmrlen, pos); // short lm_resp_len;
  pos += 2;
  buf.writeUInt16LE(lmrlen, pos); // short lm_resp_len;
  pos += 2;
  buf.writeUInt16LE(lmroff, pos); // short lm_resp_off;
  pos += 2;
  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.writeUInt16LE(ntrlen, pos); // short nt_resp_len;
  pos += 2;
  buf.writeUInt16LE(ntrlen, pos); // short nt_resp_len;
  pos += 2;
  buf.writeUInt16LE(ntroff, pos); // short nt_resp_off;
  pos += 2;
  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
  pos += 2;
  buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
  pos += 2;
  buf.writeUInt16LE(ntdomainoff, pos); // short dom_off;
  pos += 2;
  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.writeUInt16LE(usernamelen, pos); // short user_len;
  pos += 2;
  buf.writeUInt16LE(usernamelen, pos); // short user_len;
  pos += 2;
  buf.writeUInt16LE(usernameoff, pos); // short user_off;
  pos += 2;
  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.writeUInt16LE(hostnamelen, pos); // short host_len;
  pos += 2;
  buf.writeUInt16LE(hostnamelen, pos); // short host_len;
  pos += 2;
  buf.writeUInt16LE(hostnameoff, pos); // short host_off;
  pos += 2;
  buf.fill(0x00, pos, pos + 6); // byte zero[6];
  pos += 6;

  buf.writeUInt16LE(msg_len, pos); // short msg_len;
  pos += 2;
  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.writeUInt16LE(0x8201, pos); // short flags;
  pos += 2;
  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  buf.write(ntdomain, ntdomainoff, ntdomainlen, 'ucs2');
  buf.write(username, usernameoff, usernamelen, 'ucs2');
  buf.write(hostname, hostnameoff, hostnamelen, 'ucs2');
  lmr.copy(buf, lmroff, 0, lmrlen);
  ntr.copy(buf, ntroff, 0, ntrlen);

  return buf;
}

function makeResponse(hash, nonce)
{
  var out = new Buffer(24);
  for (var i = 0; i < 3; i++) {
    var keybuf = $.oddpar($.expandkey(hash.slice(i * 7, i * 7 + 7)));
    var des = crypto.createCipheriv('DES-ECB', keybuf, '');
    var str = des.update(nonce.toString('binary'), 'binary', 'binary');
    out.write(str, i * 8, i * 8 + 8, 'binary');
  }
  return out;
}

exports.encodeType1 = encodeType1;
exports.decodeType2 = decodeType2;
exports.encodeType3 = encodeType3;

// Convenience methods.

exports.challengeHeader = function (hostname, domain) {
  return 'NTLM ' + exports.encodeType1(hostname, domain).toString('base64');
};

exports.responseHeader = function (res, url, domain, username, password) {
  var serverNonce = new Buffer((res.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1], 'base64');
  var hostname = require('url').parse(url).hostname;
  return 'NTLM ' + exports.encodeType3(username, hostname, domain, exports.decodeType2(serverNonce), password).toString('base64')
};

// Import smbhash module.

exports.smbhash = require('./smbhash');
