Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Mailinabox Ruby API - Zonefile Generation. Downloads all DNS record from your MAIB instance and write valid zonefiles for easy C&P and Version controlling
require 'bundler/inline'
IGNORE_RECORD_TYPES = ['SSHFP', 'TLSA']
BOX = 'box.yourserver.com'
ADMIN_EMAIL = 'admin@yourserver.com'
gemfile do
source 'https://rubygems.org'
gem 'http'
gem 'tty-prompt', require: 'tty/prompt'
gem 'tty-table', require: 'tty/table'
gem 'pry'
end
require_relative './mailinabox_api.rb'
def ask_creds
prompt = TTY::Prompt.new(output: STDERR)
$email_admin_name = prompt.ask("#{BOX} admin mail address?", default: ADMIN_EMAIL)
$email_admin_password = prompt.mask('Passwort', required: true)
end
def multiline_string(value)
groups = []
chars = value.chars;1
if value.include?('p=')
idx = value.index('p=') + 2
else
idx = 100
end
groups << chars.take(idx).join
chars = chars.drop(idx)
loop do
groups << chars.take(200).join
chars = chars.drop(200)
break if chars.empty?
end
%[(#{groups.join(%{"\n"})} )]
end
ask_creds
FileUtils.mkdir_p('zonefiles')
api = MailinaboxApi.new($email_admin_name, $email_admin_password, BOX, insecure: true)
dump = api.dns_dump
dump.each do |a|
d = a[0]
zf = api.zonefile(d, dump: dump).sort_by { |a, b, c| [b, a.length, a] }.reject { |a, b, c| IGNORE_RECORD_TYPES.include?(b) }
max_length = zf.map(&:first).max_by(&:length).length
out = zf.map { |i|
name, type, value = i
if value.length > 255
value = multiline_string(value)
end
sprintf("%-#{max_length + 2}s IN %-5s %s", name, type, value)
}
File.write("zonefiles/#{d}", out.join("\n") + "\n")
end
class MailinaboxApi
def initialize(email, password, domain, insecure: false)
@email = email
@password = password
@domain = domain
@insecure = insecure
end
def users
get '/admin/mail/users?format=json'
end
def user_exists?(email)
users.find { |i| i['domain'] == email.split('@').last }['users'].find { |i| i['email'] == email } != nil
end
def aliases
get '/admin/mail/aliases?format=json'
end
def add_to_alias(email, verteiler)
domain = verteiler.split('@').last
al = aliases.find { |i| i['domain'] == domain }['aliases'].find { |i| i['address'] == verteiler }
return false if al['forwards_to'].include?(email)
request do |http|
http.
headers('Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8').
post('/admin/mail/aliases/add', form: {
address: verteiler,
forwards_to: (al['forwards_to'] + [email]).join("\n"),
permitted_senders: '',
update_if_exists: '1'
})
end
end
def create_alias(from, to)
domain = from.split('@').last
al = aliases.find { |i| i['domain'] == domain }['aliases'].find { |i| i['address'] == from }
return false if al
request do |http|
http.
headers('Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8').
post('/admin/mail/aliases/add', form: {
address: from,
forwards_to: to,
permitted_senders: '',
})
end
end
def create_email(email, password)
request do |http|
http.
headers('Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8').
post('/admin/mail/users/add', form: { email: email, password: password })
end
end
def dns_dump
get("/admin/dns/dump")
end
def zonefile(domain, dump: dns_dump)
found = dump.find { |d, _| d == domain }
unless found
puts "No domain #{domain} found, existing: #{dns_dump.map(&:first).inspect}"
exit 1
end
records = found[1]
zonefile = []
records.each do |record|
val = record['value'].split.map { |j| zonefile_name(j, domain) }.join(" ")
if record['rtype'] == 'TXT'
val = %["#{val}"]
end
zonefile << [zonefile_name(record['qname'], domain), record['rtype'], val]
end
zonefile
end
def zonefile_name(name, domain_name)
if name == domain_name
return "@"
end
if name[/\.#{Regexp.escape domain_name}\.?$/]
return name.sub(/\.#{Regexp.escape domain_name}$/, '')
end
name
end
def get(url)
request do |request|
request.
accept(:json).
get(url)
end
end
def request(options = {}, &block)
HTTP.persistent("https://#{@domain}") do |http|
if @insecure
ctx = OpenSSL::SSL::SSLContext.new
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.default_options = http.default_options.with_ssl_context(ctx)
end
@request = http
.basic_auth(user: @email, pass: @password)
.timeout(connect: 15, read: 30)
@response = yield(@request)
end
unless @response.status.success?
Kernel.warn @response.body.to_s
raise StandardError.new("#{@response.status} Request failed to #{full_url}")
end
if @response.headers['Content-Type'] == 'application/json'
JSON.parse(@response.body.to_s)
else
@response.body.to_s
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.