четверг, 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/

воскресенье, 24 сентября 2017 г.

Пободался с samba

Оказывается, есть куча полезных команд, позволяющих её налаживать.

Например, testparm - проверяет smb.conf на корректность.

Или wbinfo -t - проверяет, жива ли учетная запись данного компьютера в домене.

Или вот ещё, можно попробовать авторизоваться в домене командой wbinfo -a domain\\user и посмотреть, какие ошибки при этом появятся.

В общем, учиться и учиться...

среда, 2 августа 2017 г.

MS Word: избавиться от установки при запуске

Странно себя ведёт Microsoft Word 2007: при запуске появляется окно установки, что-то там пару минут неторопливо делает, и лишь затем открывается основное окно программы. При запуске Microsoft Excel 2007 ничего подобного не наблюдается.

Оказывается, это может быть из-за того, что на компьютере установлены две версии офиса. Чтобы отучить ворд от его нехорошей привычки, надо в разделе HCU\Software\Microsoft\Office\14.0\Word\Options добавить DWORD-параметр NoRereg со значением 1.

пятница, 14 июля 2017 г.

Windows XP: сделать снимок окна терминальной сессии

Возникла проблема: как сделать снимок активного окна, если это окно открыто в бесшовной терминальной сессии (через Citrix Receiver, что ли). Простое нажатие PrintScreen даёт какие-то странные результаты.

Выручила комбинация:
CTRL+SHIFT+PrintScreen

Подсмотрел тут.

пятница, 30 июня 2017 г.

Linux: PDF-редактор

Оказывается, есть на свете добрые люди.
Обнаружился редактор PDF под линуксом, бесплатный для некоммерческого использования:
Master PDF Editor

пятница, 16 июня 2017 г.

WinXP+IE8: куки

Такая ситуация: пользователя перевели в другой домен, а про куки забыли. Соответственно, они остались в старом профиле. Возник вопрос: как перенести куки?

Оказалось, куки хранятся тут:
C:\Documents and Settings\UserName\Cookies
в виде кучки текстовых файлов и индекса index.dat.

Просто скопировать их в новый профиль не удалось: какой-то процесс держит index.dat, а без его перезаписи вновь прибывшие куки не видны. Наверно, можно было бы перезагрузиться в режиме командной строки и попробовать перезаписать этот файл оттуда, но лень. Поэтому был найден другой способ подсунуть куки в Internet Explorer 8.

Сделать это можно так. Перейти на нужный сайт, в адресной строке набрать javascript-код такого вида:
javascript:document.cookie="newCookieName=newcookieValue; expires=01-Jan-2020";void(0);
и нажать Enter.

Литература:

https://social.msdn.microsoft.com/Forums/ie/en-US/eed564af-f3e0-48ef-ac60-b85dbd44f3d0/how-to-add-manually-a-cookie-in-ie?forum=iewebdevelopment

понедельник, 22 мая 2017 г.

Посмотреть события Windows в командной строке

Посмотреть системный журнал в командной строке:
wevtutil.exe qe System /c:10 /f:text /rd:true
Смысл ключей такой:
qe System - запросить журнал "Система"
/c:10 - выдать 10 первых записей
/f:text - в виде текста
/rd:true - порядок сортировки - от новых к старым

Перезагрузить компьютер из командной строки
shutdown -r -f

понедельник, 27 февраля 2017 г.

Как своровать ресурсы из файла swf - продолжение

Нашел тут замечательную утилиту: JPEXS Free Flash Decompiler.

Она мало того, что предоставляет возможность просматривать ресурсы (изображения/звуки/видео) флэш-роликов, так еще и позволяет эти ресурсы выгружать/подменять, редактировать скрипты и т.д. В общем, масса полезных свойств. Один минус: работает на java и тормозит не по-детски.

Но это мелочи, зато удалось пропатчить несколько хентайных флэшек из своей коллекции: цензурку там убрать, скорость понизить и т.п. Особенно в роликах студии JSK: у них там буквально пару спрайтов подменишь - и вуаля. ^_^

В общем, рекомендую.

воскресенье, 19 февраля 2017 г.

Philips Xenium E181: связь через Bluetooth с Linux

Всё началось, как обычно, с пустяка. Как я уже когда-то говорил, с незапамятных времён мне верой и правдой служил замечательный сотовый телефон Samsung SGH-X100. Это была удобная звонилка, компактная, с приятным интерфейсом и прикольным белым медведем на заставке. Конечно не без недостатков - на ней как-то странно выглядели кириллические ответы на запрос баланса, MMS-сообщения не открывались и т.д. Да и с компьютером я её не успел подружить: сначала было как-то не нужно, а когда стало нужно, найти соответствующий USB-кабель оказалось проблематично.

Но всему приходит конец. В последнее время сотик постарел - испортилась пластмассовая защелка аккумулятора - и стал отключаться в самый неподходящий момент. Поэтому было принято трудное решение: купить другую звонилку. Выбор пал на Philips Xenium E181. Во-первых, какие-то зашкаливающие показатели аптайма, и, во-вторых, конечно, цена.

Сам телефон прост и безыскусен. Скорее даже это аккумулятор с возможностью позвонить. Умеет заряжаться от компьютера через micro-USB. Умеет работать с двумя симками (мне это не актуально). Есть слот для micro-SD (карту памяти надо покупать отдельно). Есть полноразмерный female USB-порт - для подзарядки(!) других устройств (открытый, могли бы положить заглушку, а то в кармане в него лезет всякий мусор, пришлось заказывать резиновую затычку на алиэкспрессе). Есть фотокамера 0.3 Mpx (качество снимков, разумеется, никакое). Есть радио. Есть фонарик (вот это полезно). Есть диктофон. Что ещё надо для счастья!

Как выяснилось, для счастья надо, чтобы была возможность обмениваться файлами с этим чудом инженерной мысли. Если подключить телефон по USB, он предлагает перейти в режим накопительного устройства, но моя убунта 15.10 его в качестве флэшки, увы, не воспринимает. Поэтому возникла надежда наладить файловый обмен через Bluetooth. С этой целью с дальней полки был вытащен, очищен от пыли и воткнут в свободный порт компьютера некий Bluetooth-адаптер. Линукс его, к счастью, без проблем подхватил, о чем свидетельствует вывод команды lsusb:
Bus 003 Device 002: ID 1310:0001 Roper Class 1 Bluetooth Dongle
Дальше началась терра инкогнита.

Оказывается, для работы с блютусом под линуксом используется некий программный продукт bluez и куча утилит, нещадно его эксплуатирующих. В частности, для настройки подключения к устройствам Bluetooth можно использовать утилиту bluetoothctl. Выглядит это так:
$ sudo bluetoothctl
[NEW] Controller 00:0B:0D:09:04:DF BlueZ 5.40 [default]

(включили блютус)
[bluetooth]# power on
Changing power on succeeded

(включили сканирование окружающего пространства на предмет наличия других блютус-устройств)
[bluetooth]# scan on
Discovery started
[CHG] Controller 00:0B:0D:09:04:DF Discovering: yes
[NEW] Device 89:61:8F:C0:48:BA Philips E181

(посмотрели информацию о своём адаптере)
[bluetooth]# show
Controller 00:0B:0D:09:04:DF
 Name: BlueZ 5.40
 Alias: BlueZ 5.40
 Class: 0x000104
 Powered: yes
 Discoverable: yes
 Pairable: yes
 UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
 UUID: A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)
 UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb)
 UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
 UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
 Modalias: usb:v1D6Bp0246d0528
 Discovering: no

(посмотрели информацию о найденном телефоне)
[bluetooth]# info 89:61:8F:C0:48:BA
Device 89:61:8F:C0:48:BA
 Name: Philips E181
 Alias: Philips E181
 Class: 0x5a0204
 Icon: phone
 Paired: no
 Trusted: no
 Blocked: no
 Connected: no
 LegacyPairing: yes

(сказали, что мы этому устройству доверяем)
[bluetooth]# trust 89:61:8F:C0:48:BA
[CHG] Device 89:61:8F:C0:48:BA Trusted: yes

(попытались сделать сопряжение и обломились)
[bluetooth]# pair 89:61:8F:C0:48:BA
Attempting to pair with 80:61:8F:C0:48:BA
[CHG] Device 89:61:8F:C0:48:BA Connected: yes
Failed to pair: org.bluez.Error.AuthenticationFailed
[CHG] Device 89:61:8F:C0:48:BA Connected: no

(оказывается, надо еще зарегистрировать некого агента)
[bluetooth]# agent on
Agent registered

(попытались сделать сопряжение, на этот раз успешно)
[bluetooth]# pair 89:61:8F:C0:48:BA 
Attempting to pair with 89:61:8F:C0:48:BA
[CHG] Device 89:61:8F:C0:48:BA Connected: yes
Request PIN code
[agent] Enter PIN code: 0000
[CHG] Device 89:61:8F:C0:48:BA Modalias: bluetooth:v0046p0802d0903
[CHG] Device 89:61:8F:C0:48:BA UUIDs: 00001105-0000-1000-8000-00805f9b34fb
[CHG] Device 89:61:8F:C0:48:BA UUIDs: 00001112-0000-1000-8000-00805f9b34fb
[CHG] Device 89:61:8F:C0:48:BA UUIDs: 0000111f-0000-1000-8000-00805f9b34fb
[CHG] Device 89:61:8F:C0:48:BA UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device 89:61:8F:C0:48:BA Paired: yes
Pairing successful
[CHG] Device 89:61:8F:C0:48:BA Connected: no
[CHG] Device 89:61:8F:C0:48:BA Connected: yes
[CHG] Device 89:61:8F:C0:48:BA Connected: no

(На этом счастье закончилось)
[bluetooth]# connect 89:61:8F:C0:48:BA 
Attempting to connect to 89:61:8F:C0:48:BA
[CHG] Device 89:61:8F:C0:48:BA Connected: yes
Failed to connect: org.bluez.Error.NotAvailable
[CHG] Device 89:61:8F:C0:48:BA Connected: no

(заплакали и вышли)
[bluetooth]# exit
В общем, сопрячь телефон с компьютером удалось, а вот наладить постоянный коннект - нет.

Однако, оказалось, не всё потеряно. Гугль посоветовал выполнить такую команду:
$ sdptool browse 89:61:8F:C0:48:BA
Browsing 89:61:8F:C0:48:BA ...
Service Name: Voiceg ateway
Service RecHandle: 0x10001
Service Class ID List:
  "Handsfree Audio Gateway" (0x111f)
  "Generic Audio" (0x1203)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 2
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
Profile Descriptor List:
  "Handsfree" (0x111e)
    Version: 0x0105

Service Name: AUDIO Gateway
Service RecHandle: 0x10002
Service Class ID List:
  "Headset Audio Gateway" (0x1112)
  "Generic Audio" (0x1203)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 1
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
Profile Descriptor List:
  "Headset" (0x1108)
    Version: 0x0102

Service Name: OBEX Object Push
Service RecHandle: 0x10003
Service Class ID List:
  "OBEX Object Push" (0x1105)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 5
  "OBEX" (0x0008)
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
Среди этого нам интересен последний пункт, OBEX Object Push. Как видим, для передачи файлов нужно использовать канал 5.
Передачу эту на предварительно сопряженный через bluetoothctl телефон можно успешно осуществить следующей командой:
obexftp -b 89:61:8F:C0:48:BA -B 5 -p myfile.mp3 
Увы, на этом счастье изменило мне окончательно. Получить какой бы то ни было файл в CLI или даже просто просмотреть содержимое карты памяти сотика мне так и не удалось:
$ obexftp -b 89:61:8F:C0:48:BA -B 5 -l /
Connecting..\done
Receiving "/"... Sending ""...|failed: /
failed: /
The operation failed with return code 1
Disconnecting...failed: disconnect

Пришлось обратиться к GUI. В xubuntu нашлась некое приложение blueman-applet, которое, будучи запущенным,
во-первых, позволяет телефону находить компьютер и инициировать подключение, во-вторых, успешно держит коннект с телефоном, и, в-третьих, не менее успешно ловит файлы, отправляемые с этого телефона на компьютер. Единственная тонкость: оно не любит русские буквы в пути, по которому будет сохранять принятые файлы. Точнее, не оно, а питон 2.7, на котором оно написано. В модуле /usr/lib/python2.7/site-packages/blueman/plugins/applet/TransferService.py в методе _on_transfer_completed присутствует строка:
        if os.path.exists(os.path.join(dest_dir, filename)):
            ...
В слаквари перед ней есть костыль:
        # We get bytes from pygobject under python 2.7
        if hasattr(dest_dir, "upper",) and hasattr(dest_dir, "decode"):
            dest_dir = dest_dir.decode("UTF-8")

        if os.path.exists(os.path.join(dest_dir, filename)):
            ...
а в убунте нет, поэтому питон дохнет с ошибкой "Ordinal not in range". Правда, в слаквари с этой утилитой свои тонкости: она "из коробки" не работает - не хватает прав. Пришлось подправить файл /etc/dbus-1/system.d/bluetooth.conf, заменив в нем policy user="root" на policy group="wheel". Говорят, что это моветон, но пусть уж так, чем никак. В общем, может быть, когда-нибудь попробую сделать на том же питоне консольную программку с аналогичными полезными свойствами, но с более понятным жизненным циклом.

Остались за бортом этого краткого обзора некие загадочные и, судя по всему, полезные команды:
hcitool scan
hcitool dev
sudo rfcomm connect /dev/rfcomm0 89:61:8F:C0:48:BA <канал>
но что они означают и как могут облегчить всем жизнь, я вникать пока не стал.

Литература:

https://wiki.archlinux.org/index.php/Bluetooth
https://www.garron.me/en/go2linux/how-transfer-files-bluetooth-linux.html
https://help.ubuntu.com/community/BluetoothSetup

вторник, 17 января 2017 г.

Windows 8: установка .NET Framework 3.5

Понадобилось тут запустить на "Windows 8 для одного языка" некую самописную программку, написанную лет пять назад. Ну, понадобилось - и понадобилось, что тут такого. Однако, в процессе установки открылось, что за эти годы прошла целая эпоха.

Программка при запуске потребовала наличие .NET Framework 3.5. Оказывается, в современных Windows этот фреймворк по умолчанию отключен, и его нужно дополнительно включать/устанавливать, выбрав соответствующий (первый) пункт в списке доступных компонентов Windows ("Панель управления" - "Программы и компоненты" - "Включение и отключение компонентов Windows"). Но приключение только начиналось.

При попытке включить вышеупомянутый флажок система попыталась скачать из Центра обновлений Windows недостающие файлы - и обломалась с ошибкой 0x800F0906. Гугление привело к выводу, что можно попробовать включить эту опцию оффлайн при наличии образа/установочного диска. Образа, конечно, под рукой не оказалось, пришлось его скачивать с сайта Microsoft по ссылке:
https://www.microsoft.com/ru-ru/software-download/windows8ISO
Интересно, что эта ссылка работает, если заходить на неё из-под линукса, а из-под windows посетителя перебрасывает на ссылку:
https://www.microsoft.com/ru-ru/software-download/windows8,
которая предлагает скачать некую утилиту, готовящую для вас нужный образ диска.

Так или иначе, образ диска получил. После этого примонтировал его (команда "Подключить" контекстного меню), у меня он сел на букву E:. Дальше все наперебой предлагают выполнить команду:
DISM.exe /Online /Enable-Feature /FeatureName:NetFx3 /All /LimitAccess /Source:E:\sources\sxs
Вот только фигушки: у меня это всё повисело на 65.8% и завершилось ошибкой 0x800F081F.

Дальше начался марафон. Предлагают поправить групповую политику при помощи gpedit.msc - в системе нет gpedit.msc. Предлагают удалить обновления KB2966826 и KB2966828 - но и эти обновления не установлены. В общем, что делать - непонятно.

В конце концов дело оказалось в следующем. Прибил Kaspersky Internet Security - и нужное обновление успешно скачалось (и установилось) из Центра обновлений Windows.