##// END OF EJS Templates
rdm-mailhandler.rb should catch EOFError (#18922)....
Jean-Philippe Lang -
r13579:d1ad182192af
parent child
Show More
@@ -1,178 +1,178
1 #!/usr/bin/env ruby
1 #!/usr/bin/env ruby
2 # Redmine - project management software
2 # Redmine - project management software
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 #
4 #
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
8 # of the License, or (at your option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
19 require 'net/http'
19 require 'net/http'
20 require 'net/https'
20 require 'net/https'
21 require 'uri'
21 require 'uri'
22 require 'optparse'
22 require 'optparse'
23
23
24 module Net
24 module Net
25 class HTTPS < HTTP
25 class HTTPS < HTTP
26 def self.post_form(url, params, headers, options={})
26 def self.post_form(url, params, headers, options={})
27 request = Post.new(url.path)
27 request = Post.new(url.path)
28 request.form_data = params
28 request.form_data = params
29 request.initialize_http_header(headers)
29 request.initialize_http_header(headers)
30 request.basic_auth url.user, url.password if url.user
30 request.basic_auth url.user, url.password if url.user
31 http = new(url.host, url.port)
31 http = new(url.host, url.port)
32 http.use_ssl = (url.scheme == 'https')
32 http.use_ssl = (url.scheme == 'https')
33 if options[:no_check_certificate]
33 if options[:no_check_certificate]
34 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
34 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
35 end
35 end
36 http.start {|h| h.request(request) }
36 http.start {|h| h.request(request) }
37 end
37 end
38 end
38 end
39 end
39 end
40
40
41 class RedmineMailHandler
41 class RedmineMailHandler
42 VERSION = '0.2.3'
42 VERSION = '0.2.3'
43
43
44 attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check,
44 attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check,
45 :url, :key, :no_check_certificate, :no_account_notice, :no_notification
45 :url, :key, :no_check_certificate, :no_account_notice, :no_notification
46
46
47 def initialize
47 def initialize
48 self.issue_attributes = {}
48 self.issue_attributes = {}
49
49
50 optparse = OptionParser.new do |opts|
50 optparse = OptionParser.new do |opts|
51 opts.banner = "Usage: rdm-mailhandler.rb [options] --url=<Redmine URL> --key=<API key>"
51 opts.banner = "Usage: rdm-mailhandler.rb [options] --url=<Redmine URL> --key=<API key>"
52 opts.separator("")
52 opts.separator("")
53 opts.separator("Reads an email from standard input and forwards it to a Redmine server through a HTTP request.")
53 opts.separator("Reads an email from standard input and forwards it to a Redmine server through a HTTP request.")
54 opts.separator("")
54 opts.separator("")
55 opts.separator("Required arguments:")
55 opts.separator("Required arguments:")
56 opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v}
56 opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v}
57 opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v}
57 opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v}
58 opts.separator("")
58 opts.separator("")
59 opts.separator("General options:")
59 opts.separator("General options:")
60 opts.on("--no-permission-check", "disable permission checking when receiving",
60 opts.on("--no-permission-check", "disable permission checking when receiving",
61 "the email") {self.no_permission_check = '1'}
61 "the email") {self.no_permission_check = '1'}
62 opts.on("--key-file FILE", "full path to a file that contains your Redmine",
62 opts.on("--key-file FILE", "full path to a file that contains your Redmine",
63 "API key (use this option instead of --key if",
63 "API key (use this option instead of --key if",
64 "you don't want the key to appear in the command",
64 "you don't want the key to appear in the command",
65 "line)") {|v| read_key_from_file(v)}
65 "line)") {|v| read_key_from_file(v)}
66 opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true}
66 opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true}
67 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
67 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
68 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
68 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
69 opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit}
69 opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit}
70 opts.separator("")
70 opts.separator("")
71 opts.separator("User creation options:")
71 opts.separator("User creation options:")
72 opts.on("--unknown-user ACTION", "how to handle emails from an unknown user",
72 opts.on("--unknown-user ACTION", "how to handle emails from an unknown user",
73 "ACTION can be one of the following values:",
73 "ACTION can be one of the following values:",
74 "* ignore: email is ignored (default)",
74 "* ignore: email is ignored (default)",
75 "* accept: accept as anonymous user",
75 "* accept: accept as anonymous user",
76 "* create: create a user account") {|v| self.unknown_user = v}
76 "* create: create a user account") {|v| self.unknown_user = v}
77 opts.on("--default-group GROUP", "add created user to GROUP (none by default)",
77 opts.on("--default-group GROUP", "add created user to GROUP (none by default)",
78 "GROUP can be a comma separated list of groups") { |v| self.default_group = v}
78 "GROUP can be a comma separated list of groups") { |v| self.default_group = v}
79 opts.on("--no-account-notice", "don't send account information to the newly",
79 opts.on("--no-account-notice", "don't send account information to the newly",
80 "created user") { |v| self.no_account_notice = '1'}
80 "created user") { |v| self.no_account_notice = '1'}
81 opts.on("--no-notification", "disable email notifications for the created",
81 opts.on("--no-notification", "disable email notifications for the created",
82 "user") { |v| self.no_notification = '1'}
82 "user") { |v| self.no_notification = '1'}
83 opts.separator("")
83 opts.separator("")
84 opts.separator("Issue attributes control options:")
84 opts.separator("Issue attributes control options:")
85 opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v}
85 opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v}
86 opts.on("-s", "--status STATUS", "name of the target status") {|v| self.issue_attributes['status'] = v}
86 opts.on("-s", "--status STATUS", "name of the target status") {|v| self.issue_attributes['status'] = v}
87 opts.on("-t", "--tracker TRACKER", "name of the target tracker") {|v| self.issue_attributes['tracker'] = v}
87 opts.on("-t", "--tracker TRACKER", "name of the target tracker") {|v| self.issue_attributes['tracker'] = v}
88 opts.on( "--category CATEGORY", "name of the target category") {|v| self.issue_attributes['category'] = v}
88 opts.on( "--category CATEGORY", "name of the target category") {|v| self.issue_attributes['category'] = v}
89 opts.on( "--priority PRIORITY", "name of the target priority") {|v| self.issue_attributes['priority'] = v}
89 opts.on( "--priority PRIORITY", "name of the target priority") {|v| self.issue_attributes['priority'] = v}
90 opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes",
90 opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes",
91 "specified by previous options",
91 "specified by previous options",
92 "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
92 "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
93 opts.separator("")
93 opts.separator("")
94 opts.separator("Examples:")
94 opts.separator("Examples:")
95 opts.separator("No project specified, emails MUST contain the 'Project' keyword:")
95 opts.separator("No project specified, emails MUST contain the 'Project' keyword:")
96 opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
96 opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
97 opts.separator("")
97 opts.separator("")
98 opts.separator("Fixed project and default tracker specified, but emails can override")
98 opts.separator("Fixed project and default tracker specified, but emails can override")
99 opts.separator("both tracker and priority attributes using keywords:")
99 opts.separator("both tracker and priority attributes using keywords:")
100 opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
100 opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
101 opts.separator(" --project foo \\")
101 opts.separator(" --project foo \\")
102 opts.separator(" --tracker bug \\")
102 opts.separator(" --tracker bug \\")
103 opts.separator(" --allow-override tracker,priority")
103 opts.separator(" --allow-override tracker,priority")
104
104
105 opts.summary_width = 27
105 opts.summary_width = 27
106 end
106 end
107 optparse.parse!
107 optparse.parse!
108
108
109 unless url && key
109 unless url && key
110 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
110 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
111 exit 1
111 exit 1
112 end
112 end
113 end
113 end
114
114
115 def submit(email)
115 def submit(email)
116 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
116 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
117
117
118 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
118 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
119
119
120 data = { 'key' => key, 'email' => email,
120 data = { 'key' => key, 'email' => email,
121 'allow_override' => allow_override,
121 'allow_override' => allow_override,
122 'unknown_user' => unknown_user,
122 'unknown_user' => unknown_user,
123 'default_group' => default_group,
123 'default_group' => default_group,
124 'no_account_notice' => no_account_notice,
124 'no_account_notice' => no_account_notice,
125 'no_notification' => no_notification,
125 'no_notification' => no_notification,
126 'no_permission_check' => no_permission_check}
126 'no_permission_check' => no_permission_check}
127 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
127 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
128
128
129 debug "Posting to #{uri}..."
129 debug "Posting to #{uri}..."
130 begin
130 begin
131 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate)
131 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate)
132 rescue SystemCallError => e # connection refused, etc.
132 rescue SystemCallError, IOError => e # connection refused, etc.
133 warn "An error occured while contacting your Redmine server: #{e.message}"
133 warn "An error occured while contacting your Redmine server: #{e.message}"
134 return 75 # temporary failure
134 return 75 # temporary failure
135 end
135 end
136 debug "Response received: #{response.code}"
136 debug "Response received: #{response.code}"
137
137
138 case response.code.to_i
138 case response.code.to_i
139 when 403
139 when 403
140 warn "Request was denied by your Redmine server. " +
140 warn "Request was denied by your Redmine server. " +
141 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
141 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
142 return 77
142 return 77
143 when 422
143 when 422
144 warn "Request was denied by your Redmine server. " +
144 warn "Request was denied by your Redmine server. " +
145 "Possible reasons: email is sent from an invalid email address or is missing some information."
145 "Possible reasons: email is sent from an invalid email address or is missing some information."
146 return 77
146 return 77
147 when 400..499
147 when 400..499
148 warn "Request was denied by your Redmine server (#{response.code})."
148 warn "Request was denied by your Redmine server (#{response.code})."
149 return 77
149 return 77
150 when 500..599
150 when 500..599
151 warn "Failed to contact your Redmine server (#{response.code})."
151 warn "Failed to contact your Redmine server (#{response.code})."
152 return 75
152 return 75
153 when 201
153 when 201
154 debug "Proccessed successfully"
154 debug "Proccessed successfully"
155 return 0
155 return 0
156 else
156 else
157 return 1
157 return 1
158 end
158 end
159 end
159 end
160
160
161 private
161 private
162
162
163 def debug(msg)
163 def debug(msg)
164 puts msg if verbose
164 puts msg if verbose
165 end
165 end
166
166
167 def read_key_from_file(filename)
167 def read_key_from_file(filename)
168 begin
168 begin
169 self.key = File.read(filename).strip
169 self.key = File.read(filename).strip
170 rescue Exception => e
170 rescue Exception => e
171 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
171 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
172 exit 1
172 exit 1
173 end
173 end
174 end
174 end
175 end
175 end
176
176
177 handler = RedmineMailHandler.new
177 handler = RedmineMailHandler.new
178 exit(handler.submit(STDIN.read))
178 exit(handler.submit(STDIN.read))
General Comments 0
You need to be logged in to leave comments. Login now