matsumotory / dovecot-mruby-plugin

Programmable IMAP Server Scripting with mruby

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

dovecot-mruby-plugin Build Status

dovecot included dovecot-mruby-plugin is a programmable IMAP/POP server scripting with mruby like ngx_mruby for nginx, mod_mruby for apache httpd and trusterd for HTTP/2 mruby Web server. Also pmilter is a programmable Milter server scripting with mruby for SMTP server.

Quick Build and Install

make setup
make
cp -p dovecot/target/lib/dovecot/lib95_imap_mruby_plugin.so{,1} /path/to/plugin-dir/.

Test

  • dovecot download and install, then test dovecot-mruby-plugin in current directory
make setup
make test

IMAP command register using mruby

  • /path/to/command_register.rb
# Register new commands
%w(

matsumotory
test

).each do |cmd|
  Dovecot::IMAP.command_register(cmd) do |args|
    Dovecot::IMAP.send_line "Hi, #{Dovecot::IMAP.username}"
    if cmd == Dovecot::IMAP.username
      Dovecot::IMAP.send_line "You are me."
    else
      Dovecot::IMAP.send_line "I am #{cmd}.  Not you with #{args}"
    end
  end
end

# Register CAP command which run CAPABILITY command
Dovecot::IMAP.alias_command_register("CAP") do
  Dovecot::IMAP.send_line "execute CAPABILITY commands"
  Dovecot::IMAP.capability
end

Dovecot::IMAP.alias_command_register("LIST_ALIAS") do
  Dovecot::IMAP.send_line "alias LIST commands"
  Dovecot::IMAP.list
end

start dovecot with DOVECOT_MRUBY_INIT_PATH env

  • dovecot.conf
import_environment = DOVECOT_MRUBY_INIT_PATH
  • start dovecot
DOVECOT_MRUBY_INIT_PATH=/path/to/command_register.rb ./dovecot/target/sbin/dovecot \
	-c ./dovecot/configuration/dovecot.conf
  • telnet example
$ telnet 127.0.0.1 6070
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
* OK [CAPABILITY ...] Dovecot ready.

1 login test testPassword
1 OK [CAPABILITY ...] Logged in

1 matsumotory hoge 1
* matsumotory Hi, test
* matsumotory I am matsumotory.  Not you with ["hoge", "1"]
1 OK matsumotory completed (0.001 + 0.000 secs).

1 test 
* test Hi, test
* test You are me.
1 OK test completed (0.001 + 0.000 secs).

1 matsumotory 
* matsumotory Hi, test
* matsumotory I am matsumotory.  Not you with []
1 OK matsumotory completed (0.001 + 0.000 secs).

1 cap
* cap execute CAPABILITY commands
* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR ...
1 OK Capability completed (0.001 + 0.000 secs).

1 list "" *
* LIST (\HasNoChildren) "/" INBOX
1 OK List completed (0.001 + 0.000 secs).

1 list_alias "" *
* list_alias alias LIST commands
* LIST (\HasNoChildren) "/" INBOX
1 OK List completed (0.001 + 0.000 secs).

1 logout
* BYE Logging out
1 OK Logout completed (0.001 + 0.000 secs).

Connection closed by foreign host.

Pre or Post command hook using mruby

  • conf.d/95-mruby.conf

callback Ruby scripts on pre and post each IMAP commands.

protocol imap {
  mail_plugins = $mail_plugins imap_mruby
}

plugin {
  #
  # mruby_pre_${command name} = '${Ruby code}'
  # mruby_post_${command name} = '${Ruby code}'
  # mruby_pre_${command name}_path = /path/to/pre_code.rb
  # mruby_post_${command name}_path = /path/to/post_code.rb
  #
  # support the following commands
  #
  # select examine create delete rename subscribe unsubscribe list rlist lsub rlsub
  # status check close append expunge search fetch store copy uid noop capability idle
  # namespace getquota setquota getquotaroot

  mruby_pre_capability = '"pre #{Dovecot::IMAP.command_name}"'
  mruby_post_capability = '"post #{Dovecot::IMAP.command_name}"'

  # mruby_pre_capability_path = /tmp/a.rb
  # mruby_post_capability_path = /tmp/a.rb
}

dovcot start and watching log

  • telnet
$ telnet 127.0.0.1 6070
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
* OK [CAPABILITY ...] Dovecot ready.

1 login test testPassword
1 OK [CAPABILITY ...] Logged in

1 capability
* CAPABILITY IMAP4rev1 ...
1 OK Capability completed (0.002 + 0.000 + 0.001 secs).
  • logging
Aug 15 10:14:39 imap-login: Info: Login: user=<test>, method=PLAIN, rip=127.0.0.1, lip=127.0.0.1, mpid=29724, secured, session=<JZILDMhW1L5/AAAB>
Aug 15 10:14:39 imap(test): Debug: Loading modules from directory: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/target/lib/dovecot
Aug 15 10:14:39 imap(test): Debug: Module loaded: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/target/lib/dovecot/lib10_quota_plugin.so
Aug 15 10:14:39 imap(test): Debug: Module loaded: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/target/lib/dovecot/lib15_notify_plugin.so
Aug 15 10:14:39 imap(test): Debug: Module loaded: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/target/lib/dovecot/lib20_fts_plugin.so
Aug 15 10:14:39 imap(test): Debug: Module loaded: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/target/lib/dovecot/lib20_mail_log_plugin.so
Aug 15 10:14:39 imap(test): Debug: Module loaded: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/target/lib/dovecot/lib20_zlib_plugin.so
Aug 15 10:14:39 imap(test): Debug: Module loaded: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/target/lib/dovecot/lib95_imap_mruby_plugin.so
Aug 15 10:14:39 imap(test): Debug: Added userdb setting: plugin/scrambler_plain_password=<hidden>
Aug 15 10:14:39 imap(test): Info: code-path: /home/ubuntu/DEV/dovecot-mruby-plugin/dovecot/configuration/mruby_handler/command_register.rb
Aug 15 10:14:39 imap(test): Info: run mruby file at mruby_init, return value: ["matsumotory", "test"]
Aug 15 10:14:39 imap(test): Debug: Effective uid=1000, gid=1000, home=/home/ubuntu/src/dovecot-2.2.31/build/modules/dovecot/home/test
Aug 15 10:14:39 imap(test): Debug: quota: No quota setting - plugin disabled
Aug 15 10:14:39 imap(test): Debug: Namespace inbox: type=private, prefix=, sep=/, inbox=yes, hidden=no, list=yes, subscriptions=yes location=mdbox:~/mail
Aug 15 10:14:39 imap(test): Debug: fs: root=/home/ubuntu/src/dovecot-2.2.31/build/modules/dovecot/home/test/mail, index=, indexpvt=, control=, inbox=, alt=
Aug 15 10:14:39 imap(test): Debug: fts: No fts setting - plugin disabled
Aug 15 10:14:41 imap(test): Info: mruby_pre_capability inline-code: "pre #{Dovecot::IMAP.command_name} #{Dovecot::IMAP.username}"
Aug 15 10:14:41 imap(test): Info: run mruby at mruby_pre_capability, return value: "pre capability test"
Aug 15 10:14:41 imap(test): Info: mruby mruby_pre_capability_path hook declined
Aug 15 10:14:41 imap(test): Info: mruby_post_capability inline-code: "post #{Dovecot::IMAP.command_name} #{Dovecot::IMAP.username}"
Aug 15 10:14:41 imap(test): Info: run mruby at mruby_post_capability, return value: "post capability test"
Aug 15 10:14:41 imap(test): Info: mruby mruby_post_capability_path hook declined
Aug 15 10:14:43 imap(test): Info: Logged out in=22 out=778

User Case

Control cpu usage of each command with any user.

limit cpu 30% using mruby-cgroup

  • pre.rb
rate = Cgroup::CPU.new "test"

# limit cpu 30% usage
rate.cfs_quota_us = 30000

rate.create

if Dovecot::IMAP.username == "test" && Dovecot::IMAP.command_name == "LIST"
  rate.attach
end
  • post.rb
rate = Cgroup::CPU.new "test"

if Dovecot::IMAP.username == "test" && Dovecot::IMAP.command_name == "LIST"
  rate.detach
end
  • 95-mruby.conf
protocol imap {
  mail_plugins = $mail_plugins imap_mruby
}

plugin {
  mruby_pre_list_path = /path/to/pre.rb
  mruby_post_list_path = /path/to/post.rb
}
  • connect dovecot and run LIST with CPU 100% to 30% limit
29906 ubuntu    20   0   87640   9508   7532 R  29.9  0.1   0:04.46 imap 

build and test system: special thanks: https://github.com/posteo/scrambler-plugin

About

Programmable IMAP Server Scripting with mruby

License:GNU Affero General Public License v3.0


Languages

Language:C 57.0%Language:Ruby 31.4%Language:Yacc 7.9%Language:C++ 2.5%Language:HTML 0.9%Language:Makefile 0.2%Language:Shell 0.1%Language:Batchfile 0.0%