Last active
August 24, 2019 20:39
-
-
Save zealot128/a016c74fa55e0d061ed65b3493367a02 to your computer and use it in GitHub Desktop.
Mailinabox Ruby API - Zonefile Generation. Downloads all DNS record from your MAIB instance and write valid zonefiles for easy C&P and Version controlling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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