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