Chris@0: #!/usr/bin/env ruby Chris@0: Chris@0: # == Synopsis Chris@0: # Chris@0: # Reads an email from standard input and forward it to a Redmine server Chris@0: # through a HTTP request. Chris@0: # Chris@0: # == Usage Chris@0: # Chris@0: # rdm-mailhandler [options] --url= --key= Chris@0: # Chris@0: # == Arguments Chris@0: # Chris@0: # -u, --url URL of the Redmine server Chris@0: # -k, --key Redmine API key Chris@0: # Chris@0: # General options: Chris@0: # --unknown-user=ACTION how to handle emails from an unknown user Chris@0: # ACTION can be one of the following values: Chris@0: # ignore: email is ignored (default) Chris@0: # accept: accept as anonymous user Chris@0: # create: create a user account Chris@0: # --no-permission-check disable permission checking when receiving Chris@0: # the email Chris@0: # -h, --help show this help Chris@0: # -v, --verbose show extra information Chris@0: # -V, --version show version information and exit Chris@0: # Chris@0: # Issue attributes control options: Chris@0: # -p, --project=PROJECT identifier of the target project Chris@0: # -s, --status=STATUS name of the target status Chris@0: # -t, --tracker=TRACKER name of the target tracker Chris@0: # --category=CATEGORY name of the target category Chris@0: # --priority=PRIORITY name of the target priority Chris@0: # -o, --allow-override=ATTRS allow email content to override attributes Chris@0: # specified by previous options Chris@0: # ATTRS is a comma separated list of attributes Chris@0: # Chris@0: # == Examples Chris@0: # No project specified. Emails MUST contain the 'Project' keyword: Chris@0: # Chris@0: # rdm-mailhandler --url http://redmine.domain.foo --key secret Chris@0: # Chris@0: # Fixed project and default tracker specified, but emails can override Chris@0: # both tracker and priority attributes using keywords: Chris@0: # Chris@0: # rdm-mailhandler --url https://domain.foo/redmine --key secret \\ Chris@0: # --project foo \\ Chris@0: # --tracker bug \\ Chris@0: # --allow-override tracker,priority Chris@0: Chris@0: require 'net/http' Chris@0: require 'net/https' Chris@0: require 'uri' Chris@0: require 'getoptlong' Chris@0: require 'rdoc/usage' Chris@0: Chris@0: module Net Chris@0: class HTTPS < HTTP Chris@0: def self.post_form(url, params) Chris@0: request = Post.new(url.path) Chris@0: request.form_data = params Chris@0: request.basic_auth url.user, url.password if url.user Chris@0: http = new(url.host, url.port) Chris@0: http.use_ssl = (url.scheme == 'https') Chris@0: http.start {|h| h.request(request) } Chris@0: end Chris@0: end Chris@0: end Chris@0: Chris@0: class RedmineMailHandler Chris@0: VERSION = '0.1' Chris@0: Chris@0: attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :no_permission_check, :url, :key Chris@0: Chris@0: def initialize Chris@0: self.issue_attributes = {} Chris@0: Chris@0: opts = GetoptLong.new( Chris@0: [ '--help', '-h', GetoptLong::NO_ARGUMENT ], Chris@0: [ '--version', '-V', GetoptLong::NO_ARGUMENT ], Chris@0: [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], Chris@0: [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ], Chris@0: [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT], Chris@0: [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], Chris@0: [ '--status', '-s', GetoptLong::REQUIRED_ARGUMENT ], Chris@0: [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT], Chris@0: [ '--category', GetoptLong::REQUIRED_ARGUMENT], Chris@0: [ '--priority', GetoptLong::REQUIRED_ARGUMENT], Chris@0: [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT], Chris@0: [ '--unknown-user', GetoptLong::REQUIRED_ARGUMENT], Chris@0: [ '--no-permission-check', GetoptLong::NO_ARGUMENT] Chris@0: ) Chris@0: Chris@0: opts.each do |opt, arg| Chris@0: case opt Chris@0: when '--url' Chris@0: self.url = arg.dup Chris@0: when '--key' Chris@0: self.key = arg.dup Chris@0: when '--help' Chris@0: usage Chris@0: when '--verbose' Chris@0: self.verbose = true Chris@0: when '--version' Chris@0: puts VERSION; exit Chris@0: when '--project', '--status', '--tracker', '--category', '--priority' Chris@0: self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup Chris@0: when '--allow-override' Chris@0: self.allow_override = arg.dup Chris@0: when '--unknown-user' Chris@0: self.unknown_user = arg.dup Chris@0: when '--no-permission-check' Chris@0: self.no_permission_check = '1' Chris@0: end Chris@0: end Chris@0: Chris@0: RDoc.usage if url.nil? Chris@0: end Chris@0: Chris@0: def submit(email) Chris@0: uri = url.gsub(%r{/*$}, '') + '/mail_handler' Chris@0: Chris@0: data = { 'key' => key, 'email' => email, Chris@0: 'allow_override' => allow_override, Chris@0: 'unknown_user' => unknown_user, Chris@0: 'no_permission_check' => no_permission_check} Chris@0: issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } Chris@0: Chris@0: debug "Posting to #{uri}..." Chris@0: response = Net::HTTPS.post_form(URI.parse(uri), data) Chris@0: debug "Response received: #{response.code}" Chris@0: Chris@0: case response.code.to_i Chris@0: when 403 Chris@0: warn "Request was denied by your Redmine server. " + Chris@0: "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key." Chris@0: return 77 Chris@0: when 422 Chris@0: warn "Request was denied by your Redmine server. " + Chris@0: "Possible reasons: email is sent from an invalid email address or is missing some information." Chris@0: return 77 Chris@0: when 400..499 Chris@0: warn "Request was denied by your Redmine server (#{response.code})." Chris@0: return 77 Chris@0: when 500..599 Chris@0: warn "Failed to contact your Redmine server (#{response.code})." Chris@0: return 75 Chris@0: when 201 Chris@0: debug "Proccessed successfully" Chris@0: return 0 Chris@0: else Chris@0: return 1 Chris@0: end Chris@0: end Chris@0: Chris@0: private Chris@0: Chris@0: def debug(msg) Chris@0: puts msg if verbose Chris@0: end Chris@0: end Chris@0: Chris@0: handler = RedmineMailHandler.new Chris@0: exit(handler.submit(STDIN.read))