人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

プロセスのオーナ情報をTCPオプションヘッダに書き込むに至った背景とアプローチの補足

hb.matsumoto-r.jp

上記のリンクの昨日書いた記事のスコープや前提、及び、ユースケースがわかりにくかったので、以下にそれらをもう少し詳細に書こうと思います。コメントやアドバイスをすでに頂いた方はありがとうございます。

まず、この手法にいたった課題について説明してきます。 これまでWebホスティングサービス(レンタルサーバ)のように、WordPressのようなWebアプリケーションを配置するための領域(一般ユーザで利用するテナント)を貸し出すようなプラットフォームサービスにおいて、低価格化を実現するために単一のサーバにどれだけ高集積にテナントを収容するかという検討がなされてきました。

そんな中、テナント単位でプロセスを用意したり、IPアドレスをはじめとした個別リソースの紐付けを極力行わずに、共有のデータベースミドルウェアを使い、できるだけリソースを共有するような方式、例えばApacheのVirtualHostなどが採用されてきました。これによって、メモリ32GBぐらいのサーバに数万から10万ホストぐらいまで収容できるようになりました。プライベートIPアドレスや静的な情報を各テナントに紐付けると、10万ホスト収容の場合はそれ相当の数の静的な設定が必要になり、例えばApacheのVirtualHostの場合だと、それだけで4GBぐらいのworkerプレセスになります。そうなると、clone()に1秒ぐらいかかったりと大幅にCGIなどの性能低下やリソース逼迫が生じることとなり、そこも動的な設定にするなどの提案がなされてきました。

一方、そのようにリソースを共有するような方式をとると、プロセスの権限をテナントへのリクエストに応じていかに適切かつ動的に分離しつつ、Webアプリケーションの実行性能を落とさないかという点が課題になります。そこで、スレッド単位で権限を分離する方式などいくつか提案されてきました。しかし、そのような状況でも、Webサーバが持つ脆弱性やシステムにおける権限分離の穴などによって、特定のテナントから別のテナントのファイルを読み込めたりする問題がこれまでに何度も繰り返されてきました。

例えば、数年前にあったのは、とあるテナントのWebアプリが脆弱性なのによってのっとられたとします。その上で、そのテナントからシンボリックリンクを読みたい別のテナントのスクリプトファイルに対して.htmlなどに張り替え、Webサーバからは単に静的ファイルとして表示させるようにし、Webサーバのシンボリックリンクチェックがシステムコールレベルでアトミックに行われないこととマルチプロセス(スレッド)であることを利用して、特定テナントから何度か試行することでファイルを覗き見るような所謂TOCTOUな手法によってファイルを覗き見されるようなことがありました。また、ホスティング環境で提供しているライブラリやApacheにロードしているモジュールの脆弱性などによって、意図せず同一OS上でread権限を持つ複数のファイルの閲覧ができる問題などがありました。そして、その漏れた情報がどこかのサイトで共有され、とあるタイミングで特定のテナントからデータを抜かれるといった事象もありました。現在ではそれらを解決するための権限分離手法を沢山提案・実装してきたので、随分と強固になってきたと思いますが、依然として日々様々なインシデントが生じています。

各テナントはサービス利用者に一般ユーサで利用できる領域を貸し出しており、そこで公開されていWebアプリケーションをプラットフォームサービス事業者としてメンテするわけにはいかず、各テナントには脆弱性があり、そこが踏み台にされる前提で、そうなってもセキュリティ上の問題が起きないようなプラットフォーム基盤を構築する必要があります。また、WordPressをはじめとした、スクリプトファイルにかかれているID/PASSを抜こうという攻撃が特に多く、踏み台となったテナントから各テナントのID/PASSの認証情報が仮に漏れたとしても、それぞれのテナントを乗っ取らない限りにおいてはその認証情報を使えないようにしたいという多層防御の仕組みを検討していました。

そこで要件をまとめると、

  • テナントが収容されるサーバがフロントにあり、テナントから利用する共有データベースが後ろのプライベートネットワークで繋がっているようなホスティングシステム構成において
  • 接続元のIPアドレスが共有され、各テナントのWebアプリケーションが利用するデータベースミドルウェアを共有し、OSの各種リソースを共有するような低価格で提供される高集積マルチテナント環境でも
  • 仮に各テナントの各種スクリプトが利用するデータベースのID/PASSのような認証情報がシステムの脆弱性によって漏れたとしても
  • 収容サーバの特定のプロセスからのデータベースアクセスでない限りID/PASS認証を通さず
  • 収容サーバの接続元プロセスがユーザランドで正しい接続元プロセスかのように詐称できない(自身を証明する識別子を扱わせない)
  • IPアドレスベースよりもより粒度の細かいプロセスベースでの接続元プロセスの検証方式

を考えた結果、昨日に書いた記事のアプローチに繋がりました。

また、実装についてもeBPFを検討していたのですが、TCPオプションヘッダをいじるようなeBPFの使い方が以下の論文ぐらいで、これをためそうとしたところ論文に記載されているフックポイントなどはまだ見当たらず、論文がその提案であるということろで止まっており、今の所公開されているLinuxでは実現ができなさそう?に思ったのでカーネルモジュールで実装しています。

もしここについても、eBPFでこんなふうにできるよ、というのがあればご教示いただけると幸いです。

新たにカーネルでTCPオプションヘッダに書き込んだ情報をTCPセッション確立時にユーザランドでどう取得すべきか

追記:2020-06-05

このエントリの背景が雑だったので以下に補足記事を書きました。先にこちらに目を通していただいた方が良いかもしれません。

https://hb.matsumoto-r.jp/entry/2020/06/05/110709


speakerdeck.com

今tcprivというソフトウェアを開発しているのだが、細かい内容については上記のスライドを見てもらうとして、やりたいことは、TCPセッションを確立するプロセスのオーナ情報を接続先のプロセスで透過的に検証するという処理である。

github.com

以下ではその実装の概要を紹介しつつ、今検討していることについてお話したい。

接続元プロセスは一般ユーザを想定しており、脆弱性などによって悪意のあるユーザにのっとられることもありうるし、とあるプロセスが利用する認証情報も漏れることがあることを想定している。 しかし、情報が漏れたとしても、適切なオーナからとそのオーナに紐付いた認証情報を利用しないと、リモート先のプロセスで認証を拒否出来るようなしくみを考えている。 いわゆる多要素認証のサーバ間通信版と考えてもよいのかもしれない。

例えば、とあるマルチテナントシステム間連携において、とあるプロセスが利用しているDBのID/PASS、あるいは、トークンが漏れた時に、それらの認証情報を使って、同システム内の別のオーナのプロセスがDBに接続を試みた場合、オーナが違うのでID/PASSやトークンが一致していても認証を通さない。 マルチテナント型のマネージドシステム内で、プロセスレベルで隔離はされているがOS上に共存しているプロセスや、同じIPアドレスが割り当てられているコンテナなどを接続元として想像すると良いかもしれない。 あとはレンタルサーバの例えばWordPress的なWebアプリケーションとか。 オーナや権限で分離されている沢山のWordPressにおいて、たとえはとあるWordPressのDB接続のためのID/PASSが漏れたとしても、そのID/PASSを使うべきプロセスオーナからのみしかそのID/PASSの認証を接続先のDBで認証しない、というようなケースである。

また、接続時にオーナの情報を詐称できないように、その一連のオーナに関する情報をカーネル側で透過的に実現している。 すなわち、認証情報がもれた当該プロセスからのみのアクセスや脆弱性で乗ったられたプロセスからのみしか、認証情報を利用して接続できなくなるので、被害範囲を局所化できる。

オーナ情報の付与を透過的にカーネルで実現するために、カーネル内のNetfilterのフックポイントを利用して、カーネルモジュールによりカーネルのTCPスタック前後でTCPオプションヘッダにオーナ情報を書き込む領域を定義し、書き込んでいる。

static int __init tcpriv_init(void)
{
  printk(KERN_INFO TCPRIV_INFO "open\n");
  printk(KERN_INFO TCPRIV_INFO "An Access Control Architecture Separating Privilege Transparently via TCP Connection "
                               "Based on Process Information\n");

  nfho_in.hook = hook_local_in_func;
  nfho_in.hooknum = NF_INET_LOCAL_IN;
  nfho_in.pf = PF_INET;
  nfho_in.priority = NF_IP_PRI_FIRST;

  nf_register_net_hook(&init_net, &nfho_in);

  nfho_out.hook = hook_local_out_func;
  nfho_out.hooknum = NF_INET_LOCAL_OUT;
  nfho_out.pf = PF_INET;
  nfho_out.priority = NF_IP_PRI_FIRST;

  nf_register_net_hook(&init_net, &nfho_out);

  return 0;
}

こんな感じで、ローカルに入ってきたパケットと出ていくパケットをフックしている。

出ていくフェーズ、すなわち、リモートサーバにTCPで接続しにいくような状況では、synパケット送出時にオプションのチェックとtcprivオプションのセット(tcpriv_tcp_syn_options)、さらに、定義した実験的TCPオプションフィールドにオーナ情報を書き込んでいる(tcpriv_tcp_options_write)。

static unsigned int hook_local_out_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
  struct iphdr *iphdr = ip_hdr(skb);
  struct tcphdr *tcphdr = tcp_hdr(skb);

  if (iphdr->version == 4) {
    if (iphdr->protocol == IPPROTO_TCP && tcphdr->syn) {
      struct tcp_out_options opts;
      struct sock *sk;
      struct tcp_md5sig_key *md5;

      printk(KERN_INFO TCPRIV_INFO "found local out TCP syn packet from %pI4.\n", &iphdr->saddr);

      sk = state->sk;
      memset(&opts, 0, sizeof(opts));
      tcpriv_tcp_syn_options(sk, skb, &opts, &md5);
      tcpriv_tcp_options_write((__be32 *)(tcphdr + 1), NULL, &opts);
    }
  }

  return NF_ACCEPT;
}

現在、IANAが規定しているTCPオプションヘッダは複数存在し,同時に,HOST_IDやLinuxカーネルバージョン5系で実装されているShared Memory communications over RMDA protocol(以降SMC-R)*1といった実験的なTCPオプションも存在する。 例えばTCP Fast Open Coolieは2014年に標準化され、IANAから正式なTCPオプションとしてkindナンバーを付与されている。 一方で、比較的新しいTCPオプションであるため、依然としてLinuxカーネルのバージョンによっては、実験的なkindナンバーを共有して他の実験的なTCPオプションと利用する実装になっている*2。 そのため、実装においては、TCP Fast Open Cookieが固有のkindナンバーを保つ場合と共有の実験的なkindナンバーを持つ場合を想定して実装を行っている。

また、Linuxカーネルバージョン5系においては、共有の実験的なkindナンバーにSMC-Rを利用しているため、IANAからSMC-Rに割り当てられているTCP Experimental Option Experiment Identifiers(以降TCP ExIDs)*3とは別のTCP ExIDsを暫定で割り振って区別する。

特にパケットのTCPヘッダオプションフィールドの解析を行う際に、既存のTCPオプションはそのままに、提案手法用の実験的オプションの共有kindナンバー、データレングス、TCP ExIDs、キー情報を書き込む。 実験的オプションの仕様を忘れていると、ExIDsの定義を忘れちゃうので注意。

書き込み処理は以下のような感じ。

static void tcpriv_options_write(__be32 *ptr, u16 *options)
{
  if (unlikely(OPTION_TCPRIV & *options)) {
    kuid_t uid = current_uid();
    kgid_t gid = current_gid();

    *ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) | (TCPOPT_EXP << 8) | (TCPOLEN_EXP_TCPRIV_BASE));
    *ptr++ = htonl(TCPOPT_TCPRIV_MAGIC);

    /* TODO; write tcpriv information: allocate 32bit (unsinged int) for owner/uid area */
    *ptr++ = htonl(uid.val);
    *ptr++ = htonl(gid.val);
  }
}

static void tcpriv_tcp_options_write(__be32 *ptr, struct tcp_sock *tp, struct tcp_out_options *opts)
{
  u16 options = opts->options; /* mungable copy */

  if (unlikely(OPTION_MD5 & options)) {
    *ptr++;
    ptr += 4;
  }

  if (unlikely(opts->mss)) {
    *ptr++;
  }

  if (likely(OPTION_TS & options)) {
    if (unlikely(OPTION_SACK_ADVERTISE & options)) {
      *ptr++;
      options &= ~OPTION_SACK_ADVERTISE;
    } else {
      *ptr++;
    }
    *ptr++;
    *ptr++;
  }

  if (unlikely(OPTION_SACK_ADVERTISE & options)) {
    *ptr++;
  }

  if (unlikely(OPTION_WSCALE & options)) {
    *ptr++;
  }

  if (unlikely(opts->num_sack_blocks)) {
    int this_sack;

    *ptr++;

    for (this_sack = 0; this_sack < opts->num_sack_blocks; ++this_sack) {
      *ptr++;
      *ptr++;
    }
  }

  if (unlikely(OPTION_FAST_OPEN_COOKIE & options)) {
    struct tcp_fastopen_cookie *foc = opts->fastopen_cookie;
    u8 *p = (u8 *)ptr;
    u32 len; /* Fast Open option length */

    if (foc->exp) {
      len = TCPOLEN_EXP_FASTOPEN_BASE + foc->len;
      p += TCPOLEN_EXP_FASTOPEN_BASE;
    } else {
      len = TCPOLEN_FASTOPEN_BASE + foc->len;
      *p++;
      *p++ = len;
    }

    ptr += (len + 3) >> 2;
  }

  tcpriv_options_write(ptr, &options);
}

続いて、入ってくるフェーズでは、syncパケット受信時にTCPオプションヘッダフィールドをparseし、オプションが定義されていれば当該フィールドからオーナ情報(uid32bit+gid32bit)を取得するようにしている(tcpriv_tcp_parse_options)。

static unsigned int hook_local_in_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
  struct iphdr *iphdr = ip_hdr(skb);
  struct tcphdr *tcphdr = tcp_hdr(skb);
  struct tcp_options_received tmp_opt;

  if (iphdr->version == 4) {
    if (iphdr->protocol == IPPROTO_TCP && tcphdr->syn) {
      printk(KERN_INFO TCPRIV_INFO "found local in TCP syn packet from %pI4.\n", &iphdr->saddr);

      /* parse tcp options and store tmp_opt buffer */
      memset(&tmp_opt, 0, sizeof(tmp_opt));
      tcpriv_tcp_clear_options(&tmp_opt);
      tcpriv_tcp_parse_options(&init_net, skb, &tmp_opt, 0, NULL);
    }
  }

  return NF_ACCEPT;
}

parseについては、カーネルのコードを存分に参考にしながら、ひとつずつオプションフィールドのフラグとレングスのチェックを行って、tcprivオプションがああればその情報を取得するようにしている。

/* TCP parse tcpriv option functions */
static void tcpriv_parse_options(const struct tcphdr *th, struct tcp_options_received *opt_rx, const unsigned char *ptr,
                                 int opsize)
{
  if (th->syn && !(opsize & 1) && opsize >= TCPOLEN_EXP_TCPRIV_BASE && get_unaligned_be32(ptr) == TCPOPT_TCPRIV_MAGIC) {
    /* TODO: check tcpriv information */
    u32 uid, gid;
    uid = get_unaligned_be32(ptr + 4);
    gid = get_unaligned_be32(ptr + 8);
    printk(KERN_INFO TCPRIV_INFO "found client process info: uid=%u gid=%u\n", uid, gid);
  }
}

/* ref: https://elixir.bootlin.com/linux/latest/source/net/ipv4/tcp_input.c#L3839 */
void tcpriv_tcp_parse_options(const struct net *net, const struct sk_buff *skb, struct tcp_options_received *opt_rx,
                              int estab, struct tcp_fastopen_cookie *foc)
{
  const unsigned char *ptr;
  const struct tcphdr *th = tcp_hdr(skb);
  int length = (th->doff * 4) - sizeof(struct tcphdr);

  ptr = (const unsigned char *)(th + 1);
  opt_rx->saw_tstamp = 0;

  while (length > 0) {
    int opcode = *ptr++;
    int opsize;

    switch (opcode) {
    case TCPOPT_EOL:
      return;
    case TCPOPT_NOP: /* Ref: RFC 793 section 3.1 */
      length--;
      continue;
    default:
      if (length < 2)
        return;
      opsize = *ptr++;
      if (opsize < 2) /* "silly options" */
        return;
      if (opsize > length)
        return; /* don't parse partial options */
      switch (opcode) {

      case TCPOPT_EXP:
        /* Fast Open or SMC option shares code 254 using a 16 bits magic number. */
        if (opsize >= TCPOLEN_EXP_FASTOPEN_BASE && get_unaligned_be16(ptr) == TCPOPT_FASTOPEN_MAGIC) {
          // do nothing
        } else if (th->syn && !(opsize & 1) && opsize >= TCPOLEN_EXP_SMC_BASE &&
                   get_unaligned_be16(ptr) == TCPOPT_SMC_MAGIC) {
          // do nothing
        } else {
          tcpriv_parse_options(th, opt_rx, ptr, opsize);
        }

        break;
      }
      ptr += opsize - 2;
      length -= opsize;
    }
  }
}

これでめでたしめでたし、リモート先のプロセス側で接続元プロセスのオーナ情報を取得できるわけであるが、さてここからユーザランドで動作するミドルウェア等でこのデータをどのように取得するかを考える必要がある。

現状では、以下の図にあるように、socket APIを使うあるいは同様のAPIを実装することによって、tcprivオプションが有効化どうかをチェックする関数を用意する。

f:id:matsumoto_r:20200604144054p:plain

それを用いて、ミドルウェア、あるいは、その前端においたミドルウェア対応のプロキシで、セッションを確立する際にその関数でtcprivオプションをチェックする。 その上で、tcprivオプションがenabledであれば/procファイルシステムの指定の場所に、tcpriv/ipaddress+src-portみたいなファイルを見ると、接続元のオーナ情報が取得できるようにしておき、それを行える関数を用意する。 そのためにも、tcprivを実装しているカーネルモジュールの中で、オーナ情報をparseした後に、その情報を/proc以下に書き込むようにしておく。 すると、TCPセッション確立時に接続元プロセスのオーナ情報を取得できるので、その段階、あるいはミドルウェアとしての認証を行う段階で、そのオーナ情報を従来のID/PASSやトークンを突き合わせることで認証行う。


とまあ、ここまでが頭の中で大体できそうだと考えている設計なのだが、もっとシンプルにオーナ情報をユーザランドでセッション確立時に取得できる方法はないかと最近考えている。 scoket APIを作ったり拡張したりすることはできるが、もう少し、うまく/procにtcprivに関する情報を配置するだけで、セッション確立時にあわよくば関数を使わなくてもreadとかだけで扱えるような設計はないか検討している。

もしアイデアがありましたら、ご教示いただけると幸いでございます。

*1:RFC-7609 IBM's Shared Memory Communications over RDMA (SMC-R) Protocol, https://tools.ietf.org/html/rfc7609

*2:RFC-6994 Shared Use of Experimental TCP Options, https://tools.ietf.org/html/rfc6994

*3:TCP Experimental Option Experiment Identifiers (TCP ExIDs), https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml#tcp-exids

ngx_mruby v2のHTTPクライアントをv1よりも最大90倍高速にした

写真のような感じでRubyKaigi2018で登壇し、RubyKaigiを経て、ようやくngx_mrubyのv2をリリースしました。基本的にv1と互換性がありますので、今後はv2を開発していくことになります。

github.com

ngx_mruby v2の目玉機能としては、Rubyスクリプトからノンブロッキングのsleepとhttp[s]クライアントを使えるようになったことです。実装的には、nginxのsub requestという機能をうまく使って、ノンブロッキングのhttp[s]クライアントを汎用的なsub_requestメソッドとして実現しています。

では、本エントリではそのノンブロッキングhttpクライアントがどの程度高速処理可能になったかを実験してみましょう。また最後には、RubyKaigi2018の感想も述べます。

  • 実験
    • proxyサーバのblockingとnon-blockingのhttpクライアントの設定
    • apiサーバの設定
  • 実験結果
    • レスポンスタイムを変化させたベンチマーク
    • 同時接続数を変化させたベンチマーク
  • まとめとRubyKaigi2018の感想
続きを読む

OSレイヤでWebサーバが起動時に実行するシステムコールを監視し起動完了直前のプロセスをイメージ化する

今回は、Webサーバの実装に依存することなく、OSレイヤでWebサーバソフトウェアが起動時に実行するであろうシステムコールを監視して、そのタイミングでプロセスをイメージ化する方法(PoC)について紹介します。

続きを読む

プログラミングにおける不安と学びのプロセス

僕の場合、実現したいことをコードで書けない時には、ひたすら似たコードを読んで理解して写して…を繰り返す。そのうちに手元に大量の自分のサンプルが溜まっていく。その繰り返しがパターンの細分化を促し、書けるコードの幅を広げていく。書けるコードを気持ちよく書き続けてるだけでは新しいコードは書けないからだ....と、向き合えるようになるには時間がかかった。

書き慣れたコードの延長で書いていると、自分でコードを書けている実感があって、リファレンスなど何も見ずに自分の力でプログラミングできている感があるのだが、ある時これはただ「慣れ」の感覚を高めているように思えた。素早く書けること自体は、それはそれで一種のスキルで素晴らしいのだけど、実現したいことをコードで書けるようになる、という観点で振り返ったときに、どうしても成長を感じなかったのだ。それ以来、まずいと思い、実現したいことを思い描き、それを実現するために自分が書けないコードを探しては読み、写しては学ぶようにした。しかしそこには常に不安がつきまとう。

書けないコードを読んで真似をするという行為を繰り返している状態は、自分でコードを書けている実感がとても弱くなる。だからこそ、その漠然とした不安を払拭するためにそのプロセスを続けるのだけど、ある時振り返ると、出来ないことをできるようになっていた、という結果とその成長を実感できる。とはいえ不安はまだまだあるし、満たされることもないので、継続的に取り組み続ける。実はこれは満足感と払拭されない不安がうまく両立されている状態なのだ。

自信や実感の無さにいかに向き合ってそれをエネルギーに変えるか、その際に周りとの相対比較による無駄な雑念をどれだけ取り除けるか、というのはとても大事で、そのような状態でプログラミングに向き合えたら、自分のために、実現したいことのためにただ書けないコードを書けるようにと学び続けられる。遅くたって構わない。ただひたすらに継続してやっていくだけだ。

業務でもプログラミングでもそうだけど、専門性という意味合いのない、ある特定の狭い領域にのめり込むと割と簡単に全能感を得られてしまう。その全能感は気持ちよくて癖になってなかなかやめられないのだけど、その全能感故にその狭い領域や今出来ることから抜けられない。いかに簡単にその全能感を捨てられるかがポイントに思う。

周りよりも速く全能感を得られたのなら、次は自分のできないことに取り組みながら、俺が俺がとやってきたことを適切に周りに頼り任せていく。そうすると、自分ができないことができるようになる間に、周りのできることが自分も少しずつできなくなっていくし、それどころか、忘れてすらいく。でも実は、そのように生じる役割分担とその連鎖は、実現したいことの限界を突破していくという意味では、集合知として専門性を高める作業にほかならないのだ。

このテーマで何が言いたかったというと、そうやってコードを読んで理解して真似してる時はコードを書けてない実感が強く不安だから、それを避けて出来る方や慣れる方を優先しがちだけど、実はその不安は正しい学びのプロセスであって成長の角度が比較的大きくなるので、その不安と向き合ってしまうと良いと思うのであった。

そしてこれは、プログラミングに限らずあらゆることに適用できそうな学びのプロセスであるかもな、とも思う。

HTTPリクエスト単位でmrubyのバイトコードをProcとFiberで包みなおして実行した場合の性能とv2について

ngx_mrubyのv2の4月リリースに向けて、HTTPリクエスト単位で実行されるRubyのコードを、FiberとProcで包んだオブジェクト経由で実行する実行方式に実装しなおしています。これまでのngx_mrubyのv1系は、Rubyのコードをnginx起動時にstruct RPocにコンパイルしておき、リクエスト毎にそのバイトコードを実行していました。

一方v2では、nginx起動時にコンパイルされたstruct RProcを、HTTPリクエスト時にprocオブジェクトに変換した上で、そのprocオブジェクトをcallする処理をfiberで包み、そのfiberオブジェクトをresumeする処理をさらにprocで包んで、procをcallで実行するようにしました。それをC側とRubyのコードを行き来しながらうまいことnginxとmruby間のイベントループの上に乗るようにします。今のところはCとRubyの世界のコンテキストを現状のmrubyでうまく行き来するために、こういった複雑な方式にしています。その理由については一旦省略します。

github.com

続きを読む

IMAPサーバのdovecotをmrubyでハックする

hb.matsumoto-r.jp

上記のエントリで言及していたメールの受信サーバdovecotをmrubyで制御するpluginが概ね完成しましたので紹介します。というのも、一月前ぐらいにはできていたのですがバタバタしておりブログにできていませんでした。

github.com

dovecot-mruby-pluginはメール受信のIMAPサーバとして動くdovecotをmrubyで色々制御することができます。今日はその制御の例を幾つか紹介します。

続きを読む