ZTMSブログ

アプリ終了時のOpenSSLメモリ解放

自分のコードがメモリリーク(解放漏れ)していないか調べようと、Visual C++ でアプリケーション終了時に解放されていないメモリ領域を教えてくれるデバッグ用の関数 _CrtSetDbgFlag を使ってみたら、ものすごい大量のリーク領域が報告された。なんだこりゃ?いったい何事か??と思ったら、どうやらOpenSSLの中でメモリ解放されていないもよう。

そういえばOpenSSLは SSL_library_init 関数で初期化(利用開始)するが、対応する終了用の関数がない。ヒドス・・・。
ググっていろいろ試して、SSL_library_init に対応する終了関数はこんな感じで、いちおうちゃんと解放されるようになった・・のかな・・?
#include "openssl/conf.h"
#include "openssl/engine.h"
void SSL_library_fin( void )
{
ENGINE_cleanup();
CONF_modules_unload(1);
ERR_free_strings();
CRYPTO_cleanup_all_ex_data();
EVP_cleanup();
ERR_remove_state(0);
sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
}

これでやっと自分のコードが解放漏れしてないか、_CrtSetDbgFlag で調べられる。。

[OpenSSL] 自己署名サーバ証明書をC言語で作る

証 明 書
HTTPS通信で使うSSLサーバ証明書(自己署名)を、OpenSSLを使ってプログラム(C言語)で自動生成したい。ググったらコマンドライン(opensslコマンド)のやり方はすぐ見つかったけど、プログラムコード例はあまり見つからなかったのでメモ。

環境:WinXP Home SP3 / VC2008 Express / OpenSSL 1.0.0j / C言語

参考サイト
http://stackoverflow.com/questions/16364522/how-do-i-create-a-self-signed-certificate-in-openssl-programatically-i-e-not
http://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl

あとOpenSSLのソースコードも参考に
https://github.com/luvit/openssl/blob/master/openssl/apps/genrsa.c
https://github.com/openssl/openssl/blob/master/apps/x509.c
https://github.com/openssl/openssl/blob/master/apps/apps.c
http://docs.huihoo.com/doxygen/openssl/1.0.1c/selfsign_8c_source.html

エラーチェックなし必要な関数のみコードはこんな感じ。
void SSLSelfCertificateCreate( void )
{
ASN1_INTEGER* serial = ASN1_INTEGER_new();
EVP_PKEY* pkey = EVP_PKEY_new();
BIGNUM* exp = BN_new();
BIGNUM* big = BN_new();
X509* x509 = X509_new();
RSA* rsa = RSA_new();
X509_NAME* name = NULL;

BN_set_word( exp, RSA_F4 );
RSA_generate_key_ex( rsa, 2048, exp, NULL ); // 鍵長2048bit
#define SERIAL_RAND_BITS 64
BN_pseudo_rand( big, SERIAL_RAND_BITS, 0, 0 );
BN_to_ASN1_INTEGER( big, serial );
X509_set_serialNumber( x509, serial );
EVP_PKEY_assign_RSA( pkey, rsa );
rsa = NULL;
X509_set_version( x509, 2 ); // 2 -> x509v3
X509_gmtime_adj( X509_get_notBefore(x509), 0 );
X509_gmtime_adj( X509_get_notAfter(x509), 60*60*24*365 ); // 有効期間365日
X509_set_pubkey( x509, pkey );
name = X509_get_subject_name( x509 );
X509_NAME_add_entry_by_txt(name,"C",MBSTRING_ASC,"JP",-1,-1,0); // Country
X509_NAME_add_entry_by_txt(name,"CN",MBSTRING_ASC,"OreOre",-1,-1,0); // Common Name
X509_set_issuer_name( x509, name );
X509_sign( x509, pkey, EVP_sha1() );
// x509 が証明書、pkey が秘密鍵
// ファイルに保存したり、HTTPS通信に使ったり

ASN1_INTEGER_free( serial );
EVP_PKEY_free( pkey );
X509_free( x509 );
RSA_free( rsa );
BN_free( exp );
BN_free( big );
}

ファイルに保存するには、PEM_write_XX 関数を使う。
FILE* fp = fopen( "ssl.crt", "wb" );
PEM_write_X509( fp, x509 );
fclose( fp );

fp = fopen( "ssl.key", "wb" );
PEM_write_PrivateKey( fp, pkey, NULL,NULL,0,NULL,NULL );
fclose( fp );

保存したファイルから読み込むには PEM_read_XX 関数を使う。
X509* x509;
EVP_PKEY* pkey;
FILE* fp;

fp = fopen( "ssl.crt", "rb" );
x509 = PEM_read_X509( fp, NULL,NULL,NULL );
fclose( fp );

fp = fopen( "ssl.key", "rb" );
pkey = PEM_read_PrivateKey( fp, NULL,NULL,NULL );
fclose( fp );

HTTPSのサーバ証明書とかに使うには例えば以下のように割り当てる。
SSL_CTX* ctx = SSL_CTX_new( SSLv23_method() );
SSL_CTX_use_certificate( ctx, x509 );
SSL_CTX_use_PrivateKey( ctx, pkey );

もっとも簡素なTCP/SSLサーバは以下のような感じ。
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libeay32.lib")
#pragma comment(lib,"ssleay32.lib")

#include <winsock2.h>
#include <ws2tcpip.h>
#include <openssl/ssl.h>

int main( void )
{
WSADATA wsaData;
ADDRINFOA hint;
ADDRINFOA* adr;
SOCKET server;
SOCKADDR_STORAGE addr;
int addrlen = sizeof(addr);
SOCKET client;
SSL_CTX* ctx;
SSL* ssl;

WSAStartup( MAKEWORD(2,2), &wsaData );
SSL_library_init();

memset( &hint, 0, sizeof(hint) );
hint.ai_socktype = SOCK_STREAM;
hint.ai_protocol = IPPROTO_TCP;
hint.ai_flags = AI_PASSIVE;

#define HOST "localhost"
#define PORT "10080"
GetAddrInfoA( HOST, PORT, &hint, &adr );
server = socket( adr->ai_family, adr->ai_socktype, adr->ai_protocol );
bind( server, adr->ai_addr, (int)adr->ai_addrlen );
#define BACKLOG 128
listen( server, BACKLOG );
client = accept( server, (SOCKADDR*)&addr, &addrlen );

ctx = SSL_CTX_new( SSLv23_method() );
ssl = SSL_new( ctx );
#define SSL_CRT "ssl.crt"
#define SSL_KEY "ssl.key"
SSL_use_certificate_file( ssl, SSL_CRT, SSL_FILETYPE_PEM );
SSL_use_PrivateKey_file( ssl, SSL_KEY, SSL_FILETYPE_PEM );
SSL_set_fd( ssl, client );
SSL_accept( ssl );
#define DATA "Hello, OpenSSL!\r\n"
SSL_write( ssl, DATA, strlen(DATA) );
SSL_shutdown( ssl );
SSL_free( ssl );
SSL_CTX_free( ctx );

shutdown( client, SD_BOTH );
closesocket( client );
shutdown( server, SD_BOTH );
closesocket( server );
FreeAddrInfoA( adr );
WSACleanup();
return 0;
}

TCP/SSLサーバの動作確認にはコマンド openssl s_client が便利。
>openssl.exe s_client -connect localhost:10080

タスクトレイ(通知領域)アイコンのバルーンは_WIN32_WINNTが0x500台なら出るけど0x600だと出ないのはなぜ?

どうして・・?環境はXP+VC2008Express。
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"shell32.lib")

#ifdef __BORLANDC__
#define _WIN32_IE 0x0502
#endif
#define _WIN32_WINNT 0x0502

#include

BOOL TrayIconNotify( HWND hwnd, UINT msg )
{
NOTIFYICONDATA ni;

memset( &ni, 0, sizeof(ni) );
ni.cbSize = sizeof(ni);
ni.hWnd = hwnd;

switch( msg ){
case NIM_ADD:
ni.uFlags = NIF_ICON |NIF_INFO;
ni.hIcon = LoadIcon( NULL, IDI_INFORMATION );
strcpy( ni.szInfoTitle, "バルーンタイトル" );
strcpy( ni.szInfo, "バルーンメッセージ" );
ni.dwInfoFlags = NIIF_INFO;
}
return Shell_NotifyIcon( msg, &ni );
}

LRESULT CALLBACK MainFormProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
{
switch( msg ){
case WM_CREATE:
TrayIconNotify( hwnd, NIM_ADD );
break;

case WM_DESTROY:
TrayIconNotify( hwnd, NIM_DELETE );
PostQuitMessage(0);
return 0;
}
return DefWindowProc( hwnd, msg, wp, lp );
}

int WINAPI WinMain( HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow )
{
WNDCLASS wc;
HWND hwnd;
MSG msg = {0};

memset( &wc, 0, sizeof(wc) );
wc.lpfnWndProc = MainFormProc;
wc.hInstance = hinst;
wc.hIcon = LoadIcon(NULL,IDI_INFORMATION);
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground= (HBRUSH)(COLOR_BTNFACE+1);
wc.lpszClassName= "MainForm";
RegisterClass(&wc);

hwnd = CreateWindow(
"MainForm", "MainForm", WS_OVERLAPPEDWINDOW
,CW_USEDEFAULT, CW_USEDEFAULT, 300, 200
,NULL, NULL, hinst, NULL
);
ShowWindow( hwnd, nCmdShow );
UpdateWindow( hwnd );

while( GetMessage( &msg, NULL, 0,0 ) >0 ){
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return (int)msg.wParam;
}

[Win32]ツールチップが動かない件

tooltip.png自作アプリにツールチップをつけようとしたら、ぜんぜん動かなくてハマった件。

環境:WinSP Home SP3, VC2008Express, C言語, Win32API

ググったところ、ツールチップ表示で使うTOOLINFOW構造体は、comctl32.dllのバージョン5と6でサイズが異なり、コンパイル時はver6のつもりでも実行時はver5(のcomctl32.dll)がロードされてしまい、構造体サイズが不正となる結果、ツールチップが動かないという事らしい。なぜかUNICODEコンパイル時のみ発生する。

Unicode環境で、ツールチップを利用するには?
http://hpcgi1.nifty.com/MADIA/Vcbbs/wwwlng.cgi?print+201008/10080005.txt
Adding tooltip controls in unicode fails
http://social.msdn.microsoft.com/Forums/en-US/vclanguage/thread/5cc9a772-5174-4180-a1ca-173dc81886d9
ツールチップ と コモンコントロール のバージョン差異による落とし穴
http://ameblo.jp/blueskyame/entry-10398978729.html

対策は「マニフェストファイルを作ってver6のcomctl32.dllをロードする」方式がまず見つかる。これはXPのLunaスタイルをアプリに適用する話と同じになるもよう。

自作のプログラムを Windows XP のビジュアルスタイルに対応させる
http://www.koutou-software.co.jp/junk/apply-winxp-visualstyle.html

むう・・そうしないとダメなの?別にXPのビジュアルスタイル対応がしたいわけじゃないんだが・・。ということで、comctl32.dllをロードしてバージョン確認し、TOOLINFOW構造体サイズを調節してみた。

Common Control Versions (Windows)
http://msdn.microsoft.com/en-us/library/windows/desktop/hh298349(v=vs.85).aspx
DllGetVersion function (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/desktop/bb776404(v=vs.85).aspx

TOOLINFOW構造体サイズ用のグローバル変数を定義。
size_t TOOLINFOWsize = sizeof(TOOLINFOW);
アプリ起動時に以下の処理を行い、構造体サイズを調節する。
#define PACKVERSION(major,minor) MAKELONG(minor,major)
DWORD dwVersion=0;
HINSTANCE dll = LoadLibraryW( L"comctl32.dll" );
if( dll )
{
    DLLGETVERSIONPROC GetVersion = (DLLGETVERSIONPROC)GetProcAddress(dll,"DllGetVersion");
    if( GetVersion ){
        DLLVERSIONINFO dvi;
        HRESULT hr;
        memset( &dvi, 0, sizeof(dvi) );
        dvi.cbSize = sizeof(dvi);
        hr = GetVersion( &dvi );
        if( SUCCEEDED(hr) ) dwVersion = PACKVERSION(dvi.dwMajorVersion, dvi.dwMinorVersion);
    }
    FreeLibrary( dll );
}
if( dwVersion<PACKVERSION(6,0) ) TOOLINFOWsize -= sizeof(void*);

C言語でDetectInputCodepage

IMultiLanguage2::DetectInputCodepage を、C++じゃなくてC言語で使う方法メモ。
環境:WinXP Home SP3、VC2008Express

★ヘッダファイル
#include <mlang.h>
★COM初期化
CoInitialize(NULL);
★IMultiLanguage2インスタンス生成
IMultiLanguage2 *mlang2;
HRESULT res;

res = CoCreateInstance(
        &CLSID_CMultiLanguage
        ,NULL
        ,CLSCTX_INPROC_SERVER
        ,&IID_IMultiLanguage2
        ,(void**)&mlang2
);
if( SUCCEEDED(res) ){
    /* 成功 */
}

★判定
BYTE* data;  /* 入力データ */
int dataBytes;  /* 入力データバイト数 */
int count = 1;
DetectEncodingInfo info;

res = mlang2->lpVtbl->DetectInputCodepage( mlang2
        ,MLDETECTCP_NONE
        ,0
        ,data
        ,&dataBytes
        ,&info
        ,&count
);
if( SUCCEEDED(res) ){
    /* 成功 */
    int CodePage = info.nCodePage;
}
入力データがHTMLの場合は、MLDETECTCP_NONE を MLDETECTCP_HTML に変更すると判定精度が上がるようだ。というか、MLDETECTCP_NONE で HTML の判定をすると正しくない結果が返ってきて使い物にならない感じ。

判定結果を複数もらう場合は以下のようにする。
BYTE* data;  /* 入力データ */
int dataBytes;  /* 入力データバイト数 */
int count = 3;
DetectEncodingInfo info[3];

res = mlang2->lpVtbl->DetectInputCodepage( mlang2
        ,MLDETECTCP_NONE
        ,0
        ,data
        ,&dataBytes
        ,&(info[0])
        ,&count
);
if( SUCCEEDED(res) ){
    /* 成功 */
    int CodePage[3];
    int i;
    for( i=0; i<count; i++ )
        CodePage[i] = info[i].nCodePage;
}
ちなみに、先の判定結果を1つだけもらう場合は、複数もらう場合の第一候補が返ってきてるとは限らない挙動不審さがあるもよう。

★インスタンスおわり
mlang2->lpVtbl->Release( mlang2 );
★COMおわり
CoUninitialize();

Operaプライベートブラウジングウィンドウ起動3

operaprivate.jpgOperaバージョン: 11.61, 11.64

Operaのバージョンが上がって空白ページのタイトルが変わったみたいでちょっと変更。

よく考えたらこのスクリプト、Operaの設定で起動時の動作=ホームページを表示する、ホームページ=about:blankが前提かも。

あと、スクリプトファイルをOpera.exeと同じフォルダに置く前提でパスを自動取得するようにしてみた。

わーいXPでWin7+IE9動いた(^^)/けど・・

2013年11月現在、modern.IE から無料ダウンロードできる仮想マシンイメージが種類も豊富で、対応する仮想化ソフトも選べて便利です。
Win7onXP.png
H/W: VAIO type F (VGN-FT91S)
OS: Windows XP Home Edition SP3
CPU: Core Duo T2300
Mem: 2GB

マイクロソフトが公開しているVirtualPC用の仮想マシンイメージを使う。こんなものがあったのね。

Internet Explorer Application Compatibility VPC Image
http://www.microsoft.com/download/en/details.aspx?id=11575

いろいろと難はあったが、WebサイトのIE9動作確認には使えそう。Vista,7マシンがない身分としてはありがたい。

[JScript]ActiveXObjectでつくったオブジェクトに独自プロパティを追加できない件

JScriptのActiveXObject( 'WScript.Shell' )でつくったオブジェクトに、独自プロパティを追加したいんだけど、できない。なんで?
var shell = new ActiveXObject( 'WScript.Shell' );
shell.myFunction = function(){};
エラーになってしまうよ。
JScriptError.gif

Operaプライベートブラウジングウィンドウ起動2

operaprivate.jpgOperaバージョン: 11.50

前回のスクリプトは、まったく使い物にならないゴミ同然の粗悪品であった。

Operaの起動に時間がかかる(1秒を超える)と、もう期待通り動かない。おまけにOSシャットダウンをしようとするような害悪まであるもよう。

というわけで、せめてウィンドウの起動完了を確認してから次の処理にすすむよう改善してみた。全体の待ち時間も短くなっていい感じになった。

Operaプライベートブラウジングウィンドウ起動

operaprivate.jpgOperaバージョン: 11.50

Operaでプライベートブラウジング起動オプションとして
  -newprivatetab
が存在する。が、これは新しいタブ1つがプライベートになるだけで、次に普通に新しいタブを作ったら通常(プライベートでない)タブになってしまうもよう。

IE,Chrome,Firefox のプライベート(シークレット)モードのような、「最初にプライベートモードにしたら、後はずっとプライベート」という動作にしたい。

Opera起動後、Ctrl+Shift+N で「プライベートブラウジングウィンドウ」という、タブがぜんぶプライベートになるウィンドウを作れる。最初からこのウィンドウで起動することはできないのか?