1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

メールをフックしてgitをさわりつつ返信

Last updated at Posted at 2017-03-28
シリーズ:さくらサーバーで遊ぶ
設定等のメモ http://qiita.com/cielavenir/items/67a7c631713b4c816b6d
SSL化 http://qiita.com/cielavenir/items/aad29b2348fc4d3f9155
(さくらサーバー導入の主目的)
メールをフックする
http://qiita.com/cielavenir/items/5ce4568fc405329d421a
Linuxbrewを導入する(旧) http://qiita.com/cielavenir/items/741921fcecb281555f77
Homebrewを導入する https://qiita.com/cielavenir/items/67ce0ec9cd8d43ed00f1
さくらサーバーでgccへの依存が発生しない理由
(HOMEBREW_BUILD_FROM_SOURCE=1は不要)
https://qiita.com/cielavenir/items/410ded536d9b520989c4

用途が非常にspecificですが、汎用的に使える部分もあるのではないかと。
確認用に自分のtwitterにDMする機能も。

メールフィルタ

  • qmailの場合
    • USER-SUFFIX@DOMAIN
    • ~/.qmail-SUFFIX
|/usr/bin/spamfilter
|$HOME/mailhook.rb
  • maildropの場合(さくらサーバー)
    • MAIL_ACCOUNT@DOMAIN
    • ~/MailBox/MAIL_ACCOUNT/.mailfilter
    • 実行時ディレクトリが.mailfilterと同じディレクトリになるようなので注意。
xfilter "/usr/local/bin/spamc"
to "|/home/USER/mailhook.rb"

フックスクリプト

mail gemとtwitter gemが必要です。

mailhook.rb
#!/usr/bin/env /home/cielavenir/.rbenv/shims/ruby
#coding:utf-8
#shebangが効かない場合のみenvを経由(さくらでは効きませんでした)

#mailhookとwebhook(cgi)を別のサーバーに置く場合はコメントアウト
$stdout=File.open('mailhook.log','w')
$stderr=$stdout
def msg(str)
	$stdout.puts "Content-Type: text/plain\n\n"+str
end
#msg("Debug mode.\n"+RUBY_VERSION)

begin

Encoding.default_external='UTF-8'
require 'nkf'
require 'net/smtp'
require 'time'
require 'stringio'
require 'etc'

USERDIR=Etc.getpwuid.dir
#USERDIR='/home/USER'

#ENV['GEM_HOME']=USERDIR+'/.gem/ruby/2.2.0'
#変わったサーバーだとsshとcgiでUSERDIRの値が違うことがあるので確認
#system('stat '+USERDIR)

require 'mail'
#require 'git'
require 'twitter'

### config ###
#BLOB_NAME_FILE='https://github.com/USER/REPO/blob/__REVISION__/
#BLOB_NAME_FILE='https://gitlab.com/USER/REPO/blob/__REVISION__/
#BLOB_NAME_FILE='https://bitbucket.org/USER/REPO/src/__REVISION__/'
#BLOB_NAME_DIR='https://github.com/USER/REPO/tree/__REVISION__/
#BLOB_NAME_DIR='https://gitlab.com/USER/REPO/tree/__REVISION__/
#BLOB_NAME_DIR='https://bitbucket.org/USER/REPO/src/__REVISION__/'

REPO_LOCAL=USERDIR+'/git_dir'

MAIL_HOOKER=''
#Your primary email
SENDER_ADDR=''
SENDER_NAME=''
#Server info
SERVER_HOST='example.jp'
SERVER_PORT='587'
#nil/:enable_ssl/:enable_starttls
#enable_sslとenable_starttlsはどちらか片方しか対応していない場合がありますので注意して下さい。サーバーの設定を熟読。
SMTP_SSL_METHOD=:enable_starttls
#Envelope from (ドメインは基本的にSMTPサーバーと同じである必要があります)
SMTP_FROM=''
SMTP_PASSWORD=''
#OpenSSL::SSL::VERIFY_NONE/OpenSSL::SSL::VERIFY_PEER
SSL_VERIFY=OpenSSL::SSL::VERIFY_PEER
#CAファイル(問題がなければnilでも可)
SSL_CA='/usr/local/share/certs/ca-root-nss.crt'

HEADER=<<EOM
THIS_IS_HEADER
EOM

FOOTER=<<EOM
THIS_IS_FOOTER
EOM

MY_TWITTER='user'
client=Twitter::REST::Client.new{|config|
	config.consumer_key        = ""
	config.consumer_secret     = ""
	config.access_token        = ""
	config.access_token_secret = ""
}

def predicate(name,*args)
	#nameがargsに合致するか
	args.any?{|arg|name.include?(arg)}
end

def predicate_subject(subject)
	#なんでもかんでも転送すると問題があるので、特定の件名を持つメールにのみ反応する
	!subject.start_with?('Re:') && !subject.start_with?('RE:')
end

def make_arg(body)
	arg=[]
	body.each_line{|line|
		if line=~/(\d+)/
			arg<<$1
		end
	}
	arg
end

### end of config ###

#twitter gemはSSLContextを触れない
OpenSSL::SSL.module_eval{
	remove_const(:VERIFY_PEER)
	const_set( :VERIFY_PEER, SSL_VERIFY )
}
ENV['SSL_CERT_FILE']||=SSL_CA

def do_send(query)
	boundary="webhook_scripter_boundary"
	subject_mime=NKF.nkf("-wM",query[:subject])
	begin
		smtp=Net::SMTP.new(SERVER_HOST,SERVER_PORT)
		if SMTP_SSL_METHOD
			ctx = smtp.send(SMTP_SSL_METHOD)
			#ctx.verify_mode = SSL_VERIFY
			#ctx.ca_file = SSL_CA if SSL_CA
		end
		smtp.start('localhost',SMTP_FROM,SMTP_PASSWORD,:plain){
			body = <<EOM
From: #{NKF.nkf("-wM",SENDER_NAME)} <#{SENDER_ADDR}>
To: #{query[:to]}
CC: #{query[:cc]*', '}
Sender: #{SMTP_FROM}
Subject: #{subject_mime.chomp}
In-Reply-To: <#{query[:msgid]}>
Date: #{Time.now.rfc2822}
MIME-Version: 1.0
X-Mailer: webhook_scripter
Content-Type: multipart/mixed; boundary="#{boundary}"

--#{boundary}
Content-Type: text/plain; charset=UTF-8

#{query[:body]}
EOM

			smtp.send_mail body+"--#{boundary}--", SMTP_FROM, query[:to], *query[:cc]
			true
		}
	rescue => err
		puts err.inspect
		false
	end
end

def do_git(body)
	pwd=Dir.pwd
	Dir.chdir(REPO_LOCAL)

	arg=make_arg(body)
	#g=Git.open('.')
	system("git pull origin master")
	#g.pull('origin','master')
	revision=`git rev-parse HEAD`.strip
	#revision=g.object('HEAD').sha
	BLOB_NAME_FILE.sub!('__REVISION__',revision)
	BLOB_NAME_DIR.sub!('__REVISION__',revision)

	body=StringIO.new
	body.puts HEADER
	body.puts

	Dir.open('.'){|dir|
		#せっかくpredicateはargを複数受け取れるけど、ファイル名がargでソートされてほしいので
		arg.each{|ar|
			dir.each{|name|
				if predicate(name,ar)
					body.puts BLOB_NAME_FILE+name if FileTest.file?(name)
					body.puts BLOB_NAME_DIR+name if FileTest.directory?(name)
				end
			}
		}
	}

	body.puts FOOTER
	Dir.chdir(pwd)
	body
end

### main
mail_str=STDIN.read
File.write('mailhook.eml',mail_str)
mail=Mail.read_from_string(mail_str) # eml given by mailhook
mail_body=mail.body.decoded.encode('UTF-8',mail.charset)
subject=mail.subject||''
if !predicate_subject(subject)
	msg("email subject is not correct.")
	exit
end
body=do_git(mail_body)

mail_from=mail.from
query={
	:to => mail_from.shift,
	:cc => ( mail_from+mail.to+(mail.cc||[]) )-[MAIL_HOOKER,SENDER_ADDR]+[SENDER_ADDR],
	:subject => 'Re: '+(mail.subject||''),
	:msgid => mail.message_id,
	:body => body.string,
}
do_send(query)

client.create_direct_message(MY_TWITTER, "[mailhook performed]\n"+query[:body])

###
msg("Performed successfully.")

rescue => e
msg(e.to_s)
client.create_direct_message(MY_TWITTER, "[mailhook error]\n"+e.to_s)
end

メールフックサーバーを別の場所に

都合によりメールフックサーバーを別の場所に置く場合は、上のスクリプトをCGIとして設置した上で、以下のようなラッパーを用意します。

mailhook_wrapper.rb
#!/usr/bin/ruby
#coding:utf-8
require 'net/https'
require 'stringio'
http=Net::HTTP.new('SERVER',PORT)
http.use_ssl=true
#http.verify_mode=OpenSSL::SSL::VERIFY_NONE
s=StringIO.new;$stderr=s # stderrはフィルタの動作に邪魔なので潰す

DIR='/home/USER'

http.start{
	mail_str=STDIN.read
	File.open(DIR+'/webhook.eml','w'){|f|f.write mail_str}
	resp=http.post('/cgi-bin/webhook.cgi',mail_str)
	File.open(DIR+'/webhook.log','w'){|f|f.write resp.body}
}

最後に

メールフックの仕様上、認証を設けることは不可能です。いくら件名でフィルタを掛けているとは言え、使用の際は慎重を期して下さい。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?