2009/09/02


以前、Growl For Windows紹介記事gntp-sendという単純なCのプログラム(パスワードハッシュのみサポート)を書きました。その後、GNTPプロトコルの仕様に合わせパスワードハッシュ以外にもAES/DES/3DESな暗号化通信もサポートしたPerlモジュール、Growl::GNTPを書きました。

C/C++言語から暗号モジュールを扱う上でcryptライブラリはライセンス制約がありますが、public domainなCrypto++を使えばな制約もなくなります。
久々boostを触ってみようとリハビリがてらboost::asioとcrypto++を使って、暗号化通信をサポートしたGNTP Growlプログラムを書いてみた。

crypto++には、ハッシュアルゴリズムや暗号アルゴリズムがごった煮で含まれており、フィルタとして使ったりCBCモードで使ったりと、かなり便利になっています。
例えば乱数を得たいのであれば
CryptoPP::SecByteBlock salt(8);
CryptoPP::AutoSeededRandomPool rng;
rng.GenerateBlock(salt.begin(), salt.size());
こんな感じに、またMD5 Digestのhex文字列を取得したいのであれば
CryptoPP::SecByteBlock passtext(CryptoPP::Weak1::MD5::DIGESTSIZE);
CryptoPP::Weak1::MD5 hash;
hash.Update((byte*)password_.c_str(), password_.size());
hash.Update(salt.begin(), salt.size());
hash.Final(passtext);
CryptoPP::SecByteBlock digest(CryptoPP::Weak1::MD5::DIGESTSIZE);
hash.CalculateDigest(digest.begin(), passtext.begin(), passtext.size());
といった感じに。さらにCBCモードで文字列を暗号化したいならば
CryptoPP::CBC_Mode<CryptoPP::DES>::Encryption
  encryptor(passtext.begin(), iv.size(), iv.begin());

std::string cipher_text;
CryptoPP::StringSource(text, true,
  new CryptoPP::StreamTransformationFilter(encryptor,
  new CryptoPP::StringSink(cipher_text)
  ) // StreamTransformationFilter
); // StringSource
こんな風に書くことも出来る。これだけそろえば出来たも同前。
boost::asioの便利なiostreamを使って
asio::ip::tcp::iostream sock(hostname, port);
sock << "こんにちわこんにちわ!";
綺麗な書き方も出来る。ちなみにboost::asioを使えばWindowsやUNIX上での特有なコードが現れる事がないので、同じソースでWindowsやLinuxでもコンパイル出来てしまうので非常にありがたいですね。

以下、全体のソースです。ヘッダファイルになってます。
#ifndef gntp_h
#define gntp_h

#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include <sstream>
#include <iostream>
#include <string>
#include <cryptopp/osrng.h>
#include <cryptopp/files.h>
#include <cryptopp/hex.h>
#include <cryptopp/md5.h>
#include <cryptopp/des.h>
#include <cryptopp/aes.h>
#include <cryptopp/filters.h>
#include <cryptopp/modes.h>

#include <asio.hpp>

class gntp {
private:
  static inline std::string to_hex(CryptoPP::SecByteBlock& in) {
    std::string out;
    CryptoPP::HexEncoder hex( NULL, true, 2, "" );
    hex.Attach(new CryptoPP::StringSink(out));
    hex.PutMessageEnd(in.begin(), in.size());
    return out;
  }

  void send(const char* method, std::stringstream& stm) {
    asio::ip::tcp::iostream sock(hostname_, port_);
    if (!sock) return;

    // initialize salt and iv
    CryptoPP::SecByteBlock salt(8), iv(8);
    rng.GenerateBlock(salt.begin(), salt.size());
    rng.GenerateBlock(iv.begin(), iv.size());

    if (!password_.empty()) {
      // get digest of password+salt hex encoded
      CryptoPP::SecByteBlock passtext(CryptoPP::Weak1::MD5::DIGESTSIZE);
      CryptoPP::Weak1::MD5 hash;
      hash.Update((byte*)password_.c_str(), password_.size());
      hash.Update(salt.begin(), salt.size());
      hash.Final(passtext);
      CryptoPP::SecByteBlock digest(CryptoPP::Weak1::MD5::DIGESTSIZE);
      hash.CalculateDigest(digest.begin(), passtext.begin(), passtext.size());

      // initialize crypt
      CryptoPP::CBC_Mode<CryptoPP::DES>::Encryption
        encryptor(passtext.begin(), iv.size(), iv.begin());

      std::string cipher_text;
      CryptoPP::StringSource(stm.str(), true,
        new CryptoPP::StreamTransformationFilter(encryptor,
        new CryptoPP::StringSink(cipher_text)
        ) // StreamTransformationFilter
      ); // StringSource

      sock << "GNTP/1.0 "
        << method
        << " DES:" << to_hex(iv)
        << " MD5:" << to_hex(digest) << "." << to_hex(salt)
        << "\r\n"
        << cipher_text << "\r\n\r\n";
    } else {
      sock << "GNTP/1.0 "
        << method
        << " NONE\r\n"
        << stm.str() << "\r\n";
    }

    while (1) {
      std::string line;
      if (!std::getline(sock, line)) {
        break;
      }
      //std::cout << "[" << line << "]" << std::endl;
      if (line.find("GNTP/1.0 -ERROR") == 0)
        throw "failed to register notification";
      if (line == "\r") break;
    }
  }

  std::string application_;
  std::string hostname_;
  std::string port_;
  std::string password_;
  CryptoPP::AutoSeededRandomPool rng;
public:
  gntp(std::string application = "gntp-send", std::string password = "",
      std::string hostname = "localhost", std::string port = "23053") :
    application_(application),
    password_(password),
    hostname_(hostname),
    port_(port) { }

  void regist(const char* name) {
    std::stringstream stm;
    stm << "Application-Name: " << application_ << "\r\n";
    stm << "Notifications-Count: 1\r\n";
    stm << "\r\n";
    stm << "Notification-Name: " << name << "\r\n";
    stm << "Notification-Display-Name: " << name << "\r\n";
    stm << "Notification-Enabled: True\r\n";
    stm << "\r\n";
    send("REGISTER", stm);
  }

  void notify(const char* name, const char* title, const char* text, const char* icon = NULL) {
    std::stringstream stm;
    stm << "Application-Name: " << application_ << "\r\n";
    stm << "Notification-Name: " << name << "\r\n";
    if (icon) stm << "Notification-Icon: " << icon << "\r\n";
    stm << "Notification-Title: " << title << "\r\n";
    stm << "Notification-Text: " << text << "\r\n";
    stm << "\r\n";
    send("NOTIFY", stm);
  }
};

#endif
使い方は非常に簡単。
#include "gntp.h"

int main(void) {
  gntp client("my growl application", "my-password-is-secret");
  client.regist("my-event");
  client.notify("my-event", "タイトル", "本文", "http://mattn.kaoriya.net/images/logo.png");
  return 0;
}
これだけで暗号化通信をサポートしたGNTP Growl通信が出来てしまいます。ちなみにDESな部分をAESに変えればそのままAES暗号通信する事が出来ます。CBC素晴らしい。

本当はboost 1.40.0に含まれる新しい機能を触るつもりだったのですが、まったく触れてもいませんね...苦笑

追記 githubにソースあげておいた。
mattn's gntppp at master - GitHub

GNTP++: gntp client library writen in C++

http://github.com/mattn/gntppp/tree/master
Posted at by



2009/08/25


ネタ的にはZIGOROuさんかhasegawaさんのネタっぽいが...
@if(0)==(0) ECHO OFF
CScript.exe //NoLogo //E:JScript "%~f0" %*
GOTO :EOF
@end

function wsock_ConnectionRequest(reqId) {    
    if (socket.State != 0/* closed */) socket.Close();
    socket.Accept(reqId);
}

function wsock_DataArrival(bytesTotal) {    
    var data = script.Run('GetData', socket, bytesTotal);
    socket.SendData([
        "HTTP/1.1 200 OK",
        "Connection: closed",
        "Content-Type: text/html;",
        "",
        "Hello World! " + new Date(),
        ""
    ].join("\n"));
    // 相手が閉じてくれないので閉じたいけど待たないとレスポンスが無くなる
    WScript.Sleep(1000);
    socket.Close();
    socket.Listen();
}

var socket = WScript.CreateObject('MSWinsock.Winsock', 'wsock_');
var script = WScript.CreateObject('ScriptControl');
script.language = 'VBScript';
script.AddObject('WScript', WScript);
script.AddCode([
    'Function GetData(socket, bytesTotal):',
    '  Dim data:',
    '  socket.GetData data, vbString, bytesTotal:',
    '  GetData = data:',
    'End Function'
].join(''));

socket.Bind(8080);
socket.Listen();
while (socket.State != 9/* error */{ 
    WScript.Sleep(100);
} 

// vim:set ft=javascript:
GetDataがByRefなので、ScriptControlを使ってます。
Posted at by



2009/08/24


すぎゃーんさんのirssi全裸プラグインをベースに、kazuhoさんのpicojsonを使い、freenodeのwebchat機能をAPI的に使って、全裸発言リプライするIRC botを作ってみた。
コンパイルは以下の通り # g++ zenra-bot.cc -lmecab -lcurl
Windows(mingw32)でも動作します。

実行は第一引数にnick名、第二引数にチャネル名を指定します。
# ./zenra-bot zenra-bot #zenra-room 常駐している間は、誰かの発言をZENRIZE(全裸的変換)してつぶやきます。
例えば
会社に行く
と誰かが発言すれば
全裸で会社に行く
とつぶやいてくれます。
とても有用ですね!

よろしければどうぞ。

Posted at by