четверг, 26 октября 2017 г.

Windows: установка Oracle Instant Client

Понадобилось тут прицепиться из-под Windows 2003 к базе на Oracle 11g, если не ошибаюсь. Гугль вывел на страницу загрузки разных вариантов Oracle Instant Client.

Под линуксом всё оказалось просто: скачал нужный архив, распаковал из него папку instantclient_12_2, в строгом соответствии с инструкциями сделал пару линков на нужные библиотеки, прописал в /etc/ld.so.conf полный путь этого самого instantclient_12_2 и вызвал ldconfig. Всё. SQL*Plus сразу начал цепляться куда надо, и осталась одна проблема: Python 2.7 через cx_Oracle не сразу понял русские буквы, и потребовалось добавить в начало скрипта конструкцию:
import os
os.environ["NLS_LANG"] = "American_America.AL32UTF8"
А для SQL*Plus не потребовалось и этого.

Иное дело Windows 2003. Под ним я тоже распаковал instantclient_12_2, в эту же папку сгрузил файлы из архивов для ODBC и SQL*Plus, добавил её полный путь в системную переменную окружения PATH и попытался создать системный DSN. Безуспешно. На экран вывалились одна за одной ошибки "Не удается загрузить программы установки для драйвера ODBC Oracle in instantclient_12_2 из-за системной ошибки с кодом 127" и "Не удается загрузить программу установки или библиотеку транслятора", и начался квест, закончившийся полным провалом. Ставлю Microsoft Visual Studio 2013 Redistributable - не помогает. Нахожу замечательную утилиту Dependency Walker 2.2, которая, по аналогии с линуксовой ldd показывает зависимости библиотек, натравливаю её на sqora32.dll и sqoras32.dll, выясняется, что этим библиотекам для счастья нужны mfc110.dll и msvcr110.dll, нахожу их в установленных каких-то левых программах, копирую в instantclient_12_2 - всё без толку. Мало того, даже sqlplus.exe запускаться не желает. Может, в оракле перепутали 32-х и 64-х битную версии?

Разбираться не стал. Снёс Instant Client 12.2, поставил Instant Client 11.2. Тому тоже не хватает библиотек, на этот раз mfc80.dll и msvcr80.dll. Их найти легче, у меня они валялись в установленном клиенте SQL Server 2005. Подпихнул их в папку instantclient_11_2, окно настройки свойств подключения появилось.

Но на этом приключения не закончились. Как известно, описания оракловских подключений хранятся в файле tnsnames.ora, путь к папке которого по идее должен быть указан в переменной окружения TNS_ADMIN. Описываю своё подключение, выбираю его в окне настройки свойств "Oracle ODBC Driver Configuration" в поле TNS Sеrvice Name, пытаюсь сделать проверку подключения - получаю ошибку "ORA-12154 TNS:could not resolve the connect identifier specified". Пришлось схитрить: в поле TNS Sеrvice Name прописать подключение в формате host:port/service_name.

Ладно, так или иначе, подключился. Опять возникла проблема с русскими буквами. Причем на ровном месте. В SQL*Plus выдаётся всё хорошо, запрос
select * from v$nls_parameters where parameter like '%CHARACTERSET%';
показывает обнадёживающее "CL8MSWIN1251", то есть, никакая перекодировка не нужна, но простейший ASP-скрипт в кодировке cp1251:
Dim Conn
Set Conn = Server.CreateObject("ADODB.Connection")

Conn.Open "Provider=MSDASQL.1;Password=***;Persist Security Info=True;User ID=huh-muh;Data Source=MYORA"

Set RS = Server.CreateObject("ADODB.Recordset")
strSQL = "select 'привет' from dual"

RS.Open strSQL, Conn

RS.MoveFirst
Response.Write RS.Fields(0)

RS.Close
Set RS = Nothing

Conn.Close
Set Conn = Nothing
вместо обнадёживающего "привет" рисует на странице удручающие знаки вопроса: "??????". Оказывается, надо прописать параметр NLS_LANG=RUSSIAN_CIS.CL8MSWIN1251, но где это сделать, совершенно непонятно. В конце концов, пристроил этот параметр в реестр:
[HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE]
"NLS_LANG"="RUSSIAN_CIS.CL8MSWIN1251"
Правда, после этого неожиданно русифицировались SQL*Plus и сообщения оракла об ошибках, но и чёрт с ними. Главное, ASP-скрипты перестали глючить.

понедельник, 23 октября 2017 г.

Windows: установка visual C++ 2013 Redistributable

Если при установке vcredist_x86.exe (Update for Visual C++ 2013 and Visual C++ Redistributable Package) возникнет ошибка 0x800b010a (что-то там про цепочку сертификатов), то избавиться от неё помогает импорт сертификата MicRooCerAut2011_2011_03_22.crt во вкладку "Доверенные корневые центры сертификации" в окне управления сертификатами (вызывается, например, через свойства Интернет Эксплорера: Меню «Сервис» - Пункт меню «Свойства обозревателя» - Вкладка «Содержание» - Кнопка «Сертификаты»).

Литература:
A certificate chain could not be built to a trusted root authority

среда, 11 октября 2017 г.

HTA: двухфакторная аутентификация без смартфона

В продолжение этого поста написал программку на HTML Application. Брать с экрана QR-коды она, конечно, не может, но по уже готовой строке otpauth://totp/XXX генерировать пин-коды умеет.

Заодно пришлось написать на Javascript функции для SHA1 и HMAC-SHA1. В общем, бесполезный, но интересный опыт.

Текст программки:
<html>

<script language="Javascript">

////////////////////////////////////////////////////////////////////////////////////////////////////
// поле "Логин"
var login = "";
// если пустое, будем искать это значение в поле ключа

// поле "Ключ д/ф аутентификации"
var token = "otpauth://totp/Google:SomeUser?secret=KNXW2ZKTMVRXEZLU&issuer=Google";
// может быть в виде:
//    otpauth://totp/XXX?secret=XXX&issuer=XXX
//    secret=XXXX
//    в явном виде XxXx
////////////////////////////////////////////////////////////////////////////////////////////////////

</script>

<head>
 <title>Pin-код для двухфакторной аутентификации</title>
 <HTA:APPLICATION
  APPLICATIONNAME="Google_Auth"
  maximizeButton="no"
  minimizeButton="no"
  sysMenu="yes"
  contextMenu="yes"
  scroll="no"
 ></HTA:APPLICATION>
</head>

<body onload="self.window_onLoad()">

 <table width="100%" style="color:darkgray">
  <tr>
   <td width="50%"><span id="loginText"></span></td>
   <td width="50%" align="right"><span id="tokenText"></span></td>
  </tr>
 </table>

 <p align="center">Код аутентификатора:<br><span id="pin" align="center" style="font-size:30px;font-weight:bold">------</span></p>

 <table width="100%">
  <tr><td colspan="2" align="center">Текущее время UTC</td></tr>
  <tr><td>Ваш компьютер:</td><td><span id="localDT"></span></td></tr>
  <tr><td>Общемировое:</td><td><span id="globalDT"></span></td></tr>
 </table>

 <p align="center"><button onclick="self.close()">Выход</button></p>
</body>


<script language="Javascript">

var 
 IntervalLength = 30,
 PinTemplate = "000000",
 PinModulo = Math.pow(10, PinTemplate.length),
 sizeOfInt32 = 4;


var globalTimeDiff = 0,
 globalTimeChecked = false,
 xhr = new XMLHttpRequest(),
 previousInterval = 0;


function window_onLoad()
{
 self.resizeTo (350, 290);

 document.getElementById("loginText").innerText = getLoginValue();
 document.getElementById("tokenText").innerText = getTokenValue();

 self.localDate();
 self.setInterval (self.onTimer, 1000);
 self.globalDate();
}


function localDate()
{
 var dt = new Date(),
  D = dt.getUTCDate(),
  M = dt.getUTCMonth()+1,
  Y = dt.getUTCFullYear(),
  H = dt.getUTCHours(),
  m = dt.getUTCMinutes(),
  S = dt.getUTCSeconds(),
  offset = dt.getTimezoneOffset();

 dt.setSeconds(S + globalTimeDiff);

 var
  gD = dt.getUTCDate(),
  gM = dt.getUTCMonth()+1,
  gY = dt.getUTCFullYear(),
  gH = dt.getUTCHours(),
  gm = dt.getUTCMinutes(),
  gS = dt.getUTCSeconds();

 document.getElementById("localDT").innerText = ""
  + ((D < 10) ? "0" : "") + D
  + "." + ((M < 10) ? "0" : "") + M
  + "." + Y
  + " " + ((H < 10) ? "0" : "") + H
  + ":" + ((m < 10) ? "0" : "") + m
  + ":" + ((S < 10) ? "0" : "") + S;
  //+ " (" + (offset/60) + ")";

 document.getElementById("globalDT").innerText = globalTimeChecked ? ""
  + ((gD < 10) ? "0" : "") + gD
  + "." + ((gM < 10) ? "0" : "") + gM
  + "." + gY
  + " " + ((gH < 10) ? "0" : "") + gH
  + ":" + ((gm < 10) ? "0" : "") + gm
  + ":" + ((gS < 10) ? "0" : "") + gS : "----";
}


function globalDate()
{
 xhr.onreadystatechange = function ()
  {
   if (xhr.readyState === 4)
   {
    if (xhr.status === 200)
    {
     var dt = new Date();
     globalTimeDiff = 1*xhr.responseText - dt.getTime()/1000;
     globalTimeChecked = true;
    }
   }
  };
 xhr.open("GET", "http://time.xdy-ydx.ru/text", true);
 xhr.send(null);
}


function onTimer()
{
 localDate();
 generatePin();
}


function setPinCodeText(text)
{
 var e = document.getElementById("pin");
 if (e.innerText != text)
  e.innerText = text;
}


function getLoginValue()
{
 if (login != "")
  return login;

 if (token.substring(0, 15) == "otpauth://totp/")
 {
  var vals = token.substring(15).split("?");
  return vals[0];
 }

 return "";
}


function getTokenValue()
{
 if (token.substring(0, 15) == "otpauth://totp/")
 {
  var vals = token.substring(15).split(/\?|\&/);
  for (var i = 0; i < vals.length; i++)
   if (vals[i].substring(0, 7) == "secret=")
    return Base32_Decode(vals[i].substring(7));
 }

 if (token.substring(0, 7) == "secret=")
  return Base32_Decode(token.substring(7));

 return token;
}


// thanks to
// http://stackoverflow.com/questions/6421950/is-there-a-tutorial-on-how-to-implement-google-authenticator-in-net-apps
function generatePin()
{
 var dt = new Date();
 var interval = Math.floor(dt.getTime()/(1000*IntervalLength));

 if (interval == previousInterval)
  return;
 previousInterval = interval;

 var CounterBytes = pushInt64ToByteArray([], interval);

 var tokenValue = getTokenValue();
 var key = [];
 for (var i = 0; i < tokenValue.length; i++)
  key.push(tokenValue.charCodeAt(i));

 var hash = HMAC(SHA1, key, CounterBytes);
 var offset = hash[hash.length - 1] & 0xF;

 var selectedBytes = hash.slice(offset, offset + sizeOfInt32);
 var selectedInteger =
  (selectedBytes[0] << 24) +
  (selectedBytes[1] << 16) +
  (selectedBytes[2] << 8) +
  selectedBytes[3];

 var truncatedHash = selectedInteger & 0x7FFFFFFF;

 var PinNumber = "" + truncatedHash % PinModulo;

 var Pin = (PinTemplate + PinNumber).substring(PinTemplate.length + PinNumber.length - 6);

 setPinCodeText( Pin );

}


function pushInt32ToByteArray (arr, N)
{
 arr.push( (N >>> 24) & 0xFF );
 arr.push( (N >>> 16) & 0xFF );
 arr.push( (N >>>  8) & 0xFF );
 arr.push( (N >>>  0) & 0xFF );

 return arr;
}


function pushInt64ToByteArray (arr, N)
{
 var lo = N & 0xFFFFFFFF,
  hi = (N - lo) / 0x100000000;

 arr = pushInt32ToByteArray (arr, hi);
 arr = pushInt32ToByteArray (arr, lo);

 return arr;
}



function byteArrayToString (bytes)
{
 var s = "";
 for (var i = 0; i < bytes.length; i++)
  s += ((i == 0) ? "" : " ") + bytes[i].toString(16);
 return s;
}


function SHA1 (bytes)
{

 var shl = function (x, y)
  {

   return ( (x << y) + (x >>> (32-y)) );
  };

 var
  h0 = 0x67452301,
  h1 = 0xEFCDAB89,
  h2 = 0x98BADCFE,
  h3 = 0x10325476,
  h4 = 0xC3D2E1F0;

 buf = [];
 count = 0;
 for (var i = 0; i < bytes.length; i++)
 {
  buf.push(bytes[i]);
  count += 8; if (count == 512) count = 0;
 }

 buf.push(0x80);
 count += 8; if (count == 512) count = 0;

 while (count != 448)
 {
  buf.push(0);
  count += 8; if (count == 512) count = 0;
 }

 buf = pushInt64ToByteArray(buf, bytes.length*8);

 for (var i = 0; i < buf.length; i += 64)
 {
  var w = [];

  for (var j = 0; j < 16; j++)
   w.push( (buf[i+j*4] << 24) + (buf[i+j*4+1] << 16) + (buf[i+j*4+2] << 8) + buf[i+j*4+3] );

  for (var j = 16; j < 80; j++)
   w.push( shl(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1) );

  var
   a = h0,
   b = h1,
   c = h2,
   d = h3,
   e = h4,
   f = 0,
   k = 0,
   x;

  for (var j = 0; j < 80; j++)
  {
   if (j < 20)
   {
    f = (b & c) | ((~b) & d);
    k = 0x5A827999;
   }
   else
   if (j < 40)
   {
    f = b ^ c ^ d;
    k = 0x6ED9EBA1;
   }
   else
   if (j < 60)
   {
    f = (b & c) | (b & d) | (c & d);
    k = 0x8F1BBCDC;
   }
   else
   {
    f = b ^ c ^ d;
    k = 0xCA62C1D6;
   }

   x = ( shl(a, 5) + f + e + k + w[j]) & 0xFFFFFFFF;
   e = d;
   d = c;
   c = shl(b, 30) & 0xFFFFFFFF;
         b = a;
         a = x;
  }

  h0 = (h0 + a) & 0xFFFFFFFF;
  h1 = (h1 + b) & 0xFFFFFFFF;
  h2 = (h2 + c) & 0xFFFFFFFF;
  h3 = (h3 + d) & 0xFFFFFFFF;
  h4 = (h4 + e) & 0xFFFFFFFF;

 }

 var result = [];
 result = pushInt32ToByteArray(result, h0);
 result = pushInt32ToByteArray(result, h1);
 result = pushInt32ToByteArray(result, h2);
 result = pushInt32ToByteArray(result, h3);
 result = pushInt32ToByteArray(result, h4);

 return (result);
}


function HMAC (H, key, msg)
{
 var
  K0 = [],
  count = 0;

 for (var i = 0; i < key.length; i++)
 {
  K0.push(key[i]);
  count++;
 }

 while (count < 64)
 {
  K0.push(0);
  count++;
 }

 var chunk1 = [];

 for (var i = 0; i < K0.length; i++)
  chunk1.push(K0[i] ^ 0x36);

 for (var i = 0; i < msg.length; i++)
  chunk1.push(msg[i]);

 var hash1 = H(chunk1);

 var chunk2 = [];

 for (var i = 0; i < K0.length; i++)
  chunk2.push(K0[i] ^ 0x5c);

 for (var i = 0; i < hash1.length; i++)
  chunk2.push(hash1[i]);

 return H(chunk2);
}

// thanks to http://stackoverflow.com/questions/641361/base32-decoding
function Base32_Decode (key)
{
 var charToValue = function (value)
  {
   //65-90 == uppercase letters
   if (value < 91 && value > 64)
    return (value - 65);

   //50-55 == numbers 2-7
   if (value < 56 && value > 49)
    return (value - 24);

   //97-122 == lowercase letters
   if (value < 123 && value > 96)
    return (value - 97);

   return 0;
  }

 while(key.charAt(key.length-1) == "=")
  key = key.substring(0, key.length-1);

 var result = "";

 var
  curByte = 0,
  bitsRemaining = 8;
  mask = 0;

 for (var i = 0; i < key.length; i++)
 {
  var cValue = charToValue(key.charCodeAt(i));

  if (bitsRemaining > 5)
  {
   mask = cValue << (bitsRemaining - 5);
   curByte = (curByte | mask) & 0xFF;
   bitsRemaining -= 5;
  }
  else
  {
   mask = cValue >>> (5 - bitsRemaining);
   curByte = (curByte | mask) & 0xFF;
   result += String.fromCharCode(curByte);
   curByte = (cValue << (3 + bitsRemaining)) & 0xFF;
   bitsRemaining += 3;
  }
 }

 if ( ((key.length * 5 / 8)|0) != result.length )
   result += String.fromCharCode(curByte);

 return result;
}

</script>

</html>

вторник, 3 октября 2017 г.

Google Chrome: настройки прокси...

...можно подсмотреть тут:
chrome://net-internals/#proxy

Вообще, список всяких внутренних ссылок для настроек/диагностики можно вызвать так:
chrome://chrome-urls/