rdm-mailhandler.rb

mail handler used in configuration - Russell Rose, 2013-09-13 08:19

Download (7.42 KB)

 
1
#!/usr/bin/ruby
2

    
3
require 'net/http'
4
require 'net/https'
5
require 'uri'
6
require 'optparse'
7

    
8
module Net
9
  class HTTPS < HTTP
10
    def self.post_form(url, params, headers, options={})
11
      request = Post.new(url.path)
12
      request.form_data = params
13
      request.initialize_http_header(headers)
14
      request.basic_auth url.user, url.password if url.user
15
      http = new(url.host, url.port)
16
      http.use_ssl = (url.scheme == 'https')
17
      if options[:no_check_certificate]
18
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
19
      end
20
      http.start {|h| h.request(request) }
21
    end
22
  end
23
end
24

    
25
class RedmineMailHandler
26
  VERSION = '0.2.3'
27

    
28
  attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check,
29
    :url, :key, :no_check_certificate, :no_account_notice, :no_notification
30

    
31
  def initialize
32
    self.issue_attributes = {}
33

    
34
    optparse = OptionParser.new do |opts|
35
      opts.banner = "Usage: rdm-mailhandler.rb [options] --url=<Redmine URL> --key=<API key>"
36
      opts.separator("")
37
      opts.separator("Reads an email from standard input and forward it to a Redmine server through a HTTP request.")
38
      opts.separator("")
39
      opts.separator("Required arguments:")
40
      opts.on("-u", "--url URL",              "URL of the Redmine server") {|v| self.url = v}
41
      opts.on("-k", "--key KEY",              "Redmine API key") {|v| self.key = v}
42
      opts.separator("")
43
      opts.separator("General options:")
44
      opts.on("--no-permission-check",        "disable permission checking when receiving",
45
                                              "the email") {self.no_permission_check = '1'}
46
      opts.on("--key-file FILE",              "path to a file that contains the Redmine",
47
                                              "API key (use this option instead of --key",
48
                                              "if you don't the key to appear in the",
49
                                              "command line)") {|v| read_key_from_file(v)}
50
      opts.on("--no-check-certificate",       "do not check server certificate") {self.no_check_certificate = true}
51
      opts.on("-h", "--help",                 "show this help") {puts opts; exit 1}
52
      opts.on("-v", "--verbose",              "show extra information") {self.verbose = true}
53
      opts.on("-V", "--version",              "show version information and exit") {puts VERSION; exit}
54
      opts.separator("")
55
      opts.separator("User creation options:")
56
      opts.on("--unknown-user ACTION",        "how to handle emails from an unknown user",
57
                                              "ACTION can be one of the following values:",
58
                                              "* ignore: email is ignored (default)",
59
                                              "* accept: accept as anonymous user",
60
                                              "* create: create a user account") {|v| self.unknown_user = v}
61
      opts.on("--default-group GROUP",        "add created user to GROUP (none by default)",
62
                                              "GROUP can be a comma separated list of groups") { |v| self.default_group = v}
63
      opts.on("--no-account-notice",          "don't send account information to the newly",
64
                                              "created user") { |v| self.no_account_notice = '1'}
65
      opts.on("--no-notification",            "disable email notifications for the created",
66
                                              "user") { |v| self.no_notification = '1'}
67
      opts.separator("")
68
      opts.separator("Issue attributes control options:")
69
      opts.on("-p", "--project PROJECT",      "identifier of the target project") {|v| self.issue_attributes['project'] = v}
70
      opts.on("-s", "--status STATUS",        "name of the target status") {|v| self.issue_attributes['status'] = v}
71
      opts.on("-t", "--tracker TRACKER",      "name of the target tracker") {|v| self.issue_attributes['tracker'] = v}
72
      opts.on(      "--category CATEGORY",    "name of the target category") {|v| self.issue_attributes['category'] = v}
73
      opts.on(      "--priority PRIORITY",    "name of the target priority") {|v| self.issue_attributes['priority'] = v}
74
      opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes",
75
                                              "specified by previous options",
76
                                              "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v}
77
      opts.separator("")
78
      opts.separator("Examples:")
79
      opts.separator("No project specified. Emails MUST contain the 'Project' keyword:")
80
      opts.separator("  rdm-mailhandler.rb --url http://redmine.domain.foo --key secret")
81
      opts.separator("")
82
      opts.separator("Fixed project and default tracker specified, but emails can override")
83
      opts.separator("both tracker and priority attributes using keywords:")
84
      opts.separator("  rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\")
85
      opts.separator("    --project foo \\")
86
      opts.separator("    --tracker bug \\")
87
      opts.separator("    --allow-override tracker,priority")
88

    
89
      opts.summary_width = 27
90
    end
91
    optparse.parse!
92

    
93
    unless url && key
94
      puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help."
95
      exit 1
96
    end
97
  end
98

    
99
  def submit(email)
100
    uri = url.gsub(%r{/*$}, '') + '/mail_handler'
101

    
102
    headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
103

    
104
    data = { 'key' => key, 'email' => email,
105
                           'allow_override' => allow_override,
106
                           'unknown_user' => unknown_user,
107
                           'default_group' => default_group,
108
                           'no_account_notice' => no_account_notice,
109
                           'no_notification' => no_notification,
110
                           'no_permission_check' => no_permission_check}
111
    issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
112

    
113
    debug "Posting to #{uri}..."
114
    begin
115
      response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate)
116
    rescue SystemCallError => e # connection refused, etc.
117
      warn "An error occured while contacting your Redmine server: #{e.message}"
118
      return 75 # temporary failure
119
    end
120
    debug "Response received: #{response.code}"
121

    
122
    case response.code.to_i
123
      when 403
124
        warn "Request was denied by your Redmine server. " +
125
             "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
126
        return 77
127
      when 422
128
        warn "Request was denied by your Redmine server. " +
129
             "Possible reasons: email is sent from an invalid email address or is missing some information."
130
        return 77
131
      when 400..499
132
        warn "Request was denied by your Redmine server (#{response.code})."
133
        return 77
134
      when 500..599
135
        warn "Failed to contact your Redmine server (#{response.code})."
136
        return 75
137
      when 201
138
        debug "Proccessed successfully"
139
        return 0
140
      else
141
        return 1
142
    end
143
  end
144

    
145
  private
146

    
147
  def debug(msg)
148
    puts msg if verbose
149
  end
150

    
151
  def read_key_from_file(filename)
152
    begin
153
      self.key = File.read(filename).strip
154
    rescue Exception => e
155
      $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}"
156
      exit 1
157
    end
158
  end
159
end
160

    
161
handler = RedmineMailHandler.new
162
exit(handler.submit(STDIN.read))