##// END OF EJS Templates
Mail Handler: option for specifying a custom CA bundle (#19182)....
Jean-Philippe Lang -
r13907:9e39ad471994
parent child
Show More
@@ -1,179 +1,183
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[:certificate_bundle]
34 http.ca_file = options[:certificate_bundle]
35 end
33 if options[:no_check_certificate]
36 if options[:no_check_certificate]
34 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
37 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
35 end
38 end
36 http.start {|h| h.request(request) }
39 http.start {|h| h.request(request) }
37 end
40 end
38 end
41 end
39 end
42 end
40
43
41 class RedmineMailHandler
44 class RedmineMailHandler
42 VERSION = '0.2.3'
45 VERSION = '0.2.3'
43
46
44 attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check,
47 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
48 :url, :key, :no_check_certificate, :certificate_bundle, :no_account_notice, :no_notification
46
49
47 def initialize
50 def initialize
48 self.issue_attributes = {}
51 self.issue_attributes = {}
49
52
50 optparse = OptionParser.new do |opts|
53 optparse = OptionParser.new do |opts|
51 opts.banner = "Usage: rdm-mailhandler.rb [options] --url=<Redmine URL> --key=<API key>"
54 opts.banner = "Usage: rdm-mailhandler.rb [options] --url=<Redmine URL> --key=<API key>"
52 opts.separator("")
55 opts.separator("")
53 opts.separator("Reads an email from standard input and forwards it to a Redmine server through a HTTP request.")
56 opts.separator("Reads an email from standard input and forwards it to a Redmine server through a HTTP request.")
54 opts.separator("")
57 opts.separator("")
55 opts.separator("Required arguments:")
58 opts.separator("Required arguments:")
56 opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v}
59 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}
60 opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v}
58 opts.separator("")
61 opts.separator("")
59 opts.separator("General options:")
62 opts.separator("General options:")
60 opts.on("--no-permission-check", "disable permission checking when receiving",
63 opts.on("--no-permission-check", "disable permission checking when receiving",
61 "the email") {self.no_permission_check = '1'}
64 "the email") {self.no_permission_check = '1'}
62 opts.on("--key-file FILE", "full path to a file that contains your Redmine",
65 opts.on("--key-file FILE", "full path to a file that contains your Redmine",
63 "API key (use this option instead of --key if",
66 "API key (use this option instead of --key if",
64 "you don't want the key to appear in the command",
67 "you don't want the key to appear in the command",
65 "line)") {|v| read_key_from_file(v)}
68 "line)") {|v| read_key_from_file(v)}
66 opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true}
69 opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true}
70 opts.on("--certificate-bundle FILE", "certificate bundle to use") {|v| self.certificate_bundle = v}
67 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
71 opts.on("-h", "--help", "show this help") {puts opts; exit 1}
68 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
72 opts.on("-v", "--verbose", "show extra information") {self.verbose = true}
69 opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit}
73 opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit}
70 opts.separator("")
74 opts.separator("")
71 opts.separator("User creation options:")
75 opts.separator("User creation options:")
72 opts.on("--unknown-user ACTION", "how to handle emails from an unknown user",
76 opts.on("--unknown-user ACTION", "how to handle emails from an unknown user",
73 "ACTION can be one of the following values:",
77 "ACTION can be one of the following values:",
74 "* ignore: email is ignored (default)",
78 "* ignore: email is ignored (default)",
75 "* accept: accept as anonymous user",
79 "* accept: accept as anonymous user",
76 "* create: create a user account") {|v| self.unknown_user = v}
80 "* create: create a user account") {|v| self.unknown_user = v}
77 opts.on("--default-group GROUP", "add created user to GROUP (none by default)",
81 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}
82 "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",
83 opts.on("--no-account-notice", "don't send account information to the newly",
80 "created user") { |v| self.no_account_notice = '1'}
84 "created user") { |v| self.no_account_notice = '1'}
81 opts.on("--no-notification", "disable email notifications for the created",
85 opts.on("--no-notification", "disable email notifications for the created",
82 "user") { |v| self.no_notification = '1'}
86 "user") { |v| self.no_notification = '1'}
83 opts.separator("")
87 opts.separator("")
84 opts.separator("Issue attributes control options:")
88 opts.separator("Issue attributes control options:")
85 opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v}
89 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}
90 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}
91 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}
92 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}
93 opts.on( "--priority PRIORITY", "name of the target priority") {|v| self.issue_attributes['priority'] = v}
90 opts.on( "--private", "create new issues as private") {|v| self.issue_attributes['is_private'] = '1'}
94 opts.on( "--private", "create new issues as private") {|v| self.issue_attributes['is_private'] = '1'}
91 opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes",
95 opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes",
92 "specified by previous options",
96 "specified by previous options",
93 "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
97 "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
94 opts.separator("")
98 opts.separator("")
95 opts.separator("Examples:")
99 opts.separator("Examples:")
96 opts.separator("No project specified, emails MUST contain the 'Project' keyword:")
100 opts.separator("No project specified, emails MUST contain the 'Project' keyword:")
97 opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
101 opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
98 opts.separator("")
102 opts.separator("")
99 opts.separator("Fixed project and default tracker specified, but emails can override")
103 opts.separator("Fixed project and default tracker specified, but emails can override")
100 opts.separator("both tracker and priority attributes using keywords:")
104 opts.separator("both tracker and priority attributes using keywords:")
101 opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
105 opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
102 opts.separator(" --project foo \\")
106 opts.separator(" --project foo \\")
103 opts.separator(" --tracker bug \\")
107 opts.separator(" --tracker bug \\")
104 opts.separator(" --allow-override tracker,priority")
108 opts.separator(" --allow-override tracker,priority")
105
109
106 opts.summary_width = 27
110 opts.summary_width = 27
107 end
111 end
108 optparse.parse!
112 optparse.parse!
109
113
110 unless url && key
114 unless url && key
111 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
115 puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
112 exit 1
116 exit 1
113 end
117 end
114 end
118 end
115
119
116 def submit(email)
120 def submit(email)
117 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
121 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
118
122
119 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
123 headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
120
124
121 data = { 'key' => key, 'email' => email,
125 data = { 'key' => key, 'email' => email,
122 'allow_override' => allow_override,
126 'allow_override' => allow_override,
123 'unknown_user' => unknown_user,
127 'unknown_user' => unknown_user,
124 'default_group' => default_group,
128 'default_group' => default_group,
125 'no_account_notice' => no_account_notice,
129 'no_account_notice' => no_account_notice,
126 'no_notification' => no_notification,
130 'no_notification' => no_notification,
127 'no_permission_check' => no_permission_check}
131 'no_permission_check' => no_permission_check}
128 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
132 issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
129
133
130 debug "Posting to #{uri}..."
134 debug "Posting to #{uri}..."
131 begin
135 begin
132 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate)
136 response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate, :certificate_bundle => certificate_bundle)
133 rescue SystemCallError, IOError => e # connection refused, etc.
137 rescue SystemCallError, IOError => e # connection refused, etc.
134 warn "An error occured while contacting your Redmine server: #{e.message}"
138 warn "An error occured while contacting your Redmine server: #{e.message}"
135 return 75 # temporary failure
139 return 75 # temporary failure
136 end
140 end
137 debug "Response received: #{response.code}"
141 debug "Response received: #{response.code}"
138
142
139 case response.code.to_i
143 case response.code.to_i
140 when 403
144 when 403
141 warn "Request was denied by your Redmine server. " +
145 warn "Request was denied by your Redmine server. " +
142 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
146 "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
143 return 77
147 return 77
144 when 422
148 when 422
145 warn "Request was denied by your Redmine server. " +
149 warn "Request was denied by your Redmine server. " +
146 "Possible reasons: email is sent from an invalid email address or is missing some information."
150 "Possible reasons: email is sent from an invalid email address or is missing some information."
147 return 77
151 return 77
148 when 400..499
152 when 400..499
149 warn "Request was denied by your Redmine server (#{response.code})."
153 warn "Request was denied by your Redmine server (#{response.code})."
150 return 77
154 return 77
151 when 500..599
155 when 500..599
152 warn "Failed to contact your Redmine server (#{response.code})."
156 warn "Failed to contact your Redmine server (#{response.code})."
153 return 75
157 return 75
154 when 201
158 when 201
155 debug "Proccessed successfully"
159 debug "Proccessed successfully"
156 return 0
160 return 0
157 else
161 else
158 return 1
162 return 1
159 end
163 end
160 end
164 end
161
165
162 private
166 private
163
167
164 def debug(msg)
168 def debug(msg)
165 puts msg if verbose
169 puts msg if verbose
166 end
170 end
167
171
168 def read_key_from_file(filename)
172 def read_key_from_file(filename)
169 begin
173 begin
170 self.key = File.read(filename).strip
174 self.key = File.read(filename).strip
171 rescue Exception => e
175 rescue Exception => e
172 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
176 $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
173 exit 1
177 exit 1
174 end
178 end
175 end
179 end
176 end
180 end
177
181
178 handler = RedmineMailHandler.new
182 handler = RedmineMailHandler.new
179 exit(handler.submit(STDIN.read))
183 exit(handler.submit(STDIN.read))
General Comments 0
You need to be logged in to leave comments. Login now