Skip to content

Instantly share code, notes, and snippets.

@Elemecca
Created December 17, 2012 07:47
Show Gist options
  • Save Elemecca/4316489 to your computer and use it in GitHub Desktop.
Save Elemecca/4316489 to your computer and use it in GitHub Desktop.
Exports mailbox configuration from the Webmasters.com control panel.
#!/usr/bin/env perl
use strict;
use warnings;
use POSIX qw(strftime);
use LWP::UserAgent ();
use LWP::ConnCache ();
use HTML::TreeBuilder ();
use XML::Writer ();
$global::VERSION = "0.1";
our ($agent, $host, $user, $pass, $out);
########################################################################
# Subroutines #
########################################################################
sub logmsg {
print STDERR strftime( '%Y-%m-%d %H:%M:%S', localtime )
. " " . join( " ", @_ ) . "\n";
}
sub login () {
our ($agent, $host, $user, $pass);
my $res = $agent->post( "https://$host/4admin/", [
'Action' => 'None',
'mode' => 'Login',
'Type' => 'Hosted',
'encrypt' => 'Y',
'User' => $user,
'Pass' => $pass,
't_stamp' => time()
]);
if (!$res->is_success) {
logmsg( "login failed:", $res->status_line );
return 0;
}
return 1;
}
sub fetch ($%) {
my ($page, %post) = @_;
our ($agent, $host);
my $url = "https://$host/$page";
my $res;
if (%post) {
$res = $agent->post( $url, \%post );
} else {
$res = $agent->get( $url );
}
if ($res->is_redirect
&& '/simpleHTML/logout.php' eq $res->header( 'Location' )) {
logmsg( "redirected to login page, logging in" );
if (!login()) {
return undef;
}
if (%post) {
$res = $agent->post( $url, \%post );
} else {
$res = $agent->get( $url );
}
}
if (!$res->is_success) {
logmsg( "request for '$page' failed:", $res->status_line );
return undef;
}
return $res->content;
}
sub treeify ($) {
my ($content) = @_;
my $root = HTML::TreeBuilder->new_from_content( $content );
$root->elementify();
return $root;
}
sub read_mailbox ($$) {
my ($mailbox, $domain) = @_;
logmsg( "fetching mailbox $mailbox" );
my $edit_page;
if (defined $domain) {
$edit_page = fetch( '/simpleHTML/fw_mail.php',
'Domain' => $domain,
'Mailbox' => $mailbox,
'mode' => 'Edit',
) or return;
} else {
$edit_page = fetch( '/simpleHTML/mail_edit.php',
'Mailbox' => $mailbox
) or return;
}
my $edit_tree = treeify( $edit_page );
$out->startTag( 'mailbox', 'name' => $mailbox );
# optional text description
my $desc_elem = $edit_tree->look_down(
'_tag' => 'input', 'name' => 'Label' );
my $desc = $desc_elem->attr( 'value' );
$out->dataElement( 'description', $desc ) if ($desc);
# whether local delivery is enabled
my $local_elem = $edit_tree->look_down(
'_tag' => 'input', 'name' => 'Store', 'value' => 'Yes' );
if (defined $local_elem->attr( 'checked' )) {
my $config_elem = $edit_tree->look_down(
'_tag' => 'input', 'name' => 'Control', 'value' => 'Yes' );
my $config = defined $config_elem->attr( 'checked' );
$out->emptyTag( 'local',
'configurable' => ($config ? 'true' : 'false') );
}
# BCC outgoing mail
my $bcc_elem = $edit_tree->look_down(
'_tag' => 'input', 'name' => 'Monitor' );
if (defined $bcc_elem) {
my $bcc = $bcc_elem->attr( 'value' );
$out->emptyTag( 'bcc', 'to' => $bcc ) if ($bcc);
}
# forwarding addresses
my $forwards_elem = $edit_tree->look_down(
'_tag' => 'textarea', 'name' => 'Faddress' );
my @forwards = split /\s/, $forwards_elem->as_trimmed_text;
my $forward_elem = $edit_tree->look_down(
'_tag' => 'input', 'name' => 'Forward', 'value' => 'Yes' );
my $forward = defined $forward_elem->attr( 'checked' );
if ($forward || @forwards) {
$out->startTag( 'forwarding',
'enabled' => ($forward ? 'true' : 'false') );
for my $addr (@forwards) {
$out->emptyTag( 'forward', 'to' => $addr );
}
$out->endTag( 'forwarding' );
}
# autoresponder
my $responder_elem = $edit_tree->look_down(
'_tag' => 'input', 'name' => 'Responder', 'value' => 'Yes' );
my $responder = defined $responder_elem->attr( 'checked' );
my $message_elem = $edit_tree->look_down(
'_tag' => 'textarea', 'name' => 'Rtext' );
my $message = $message_elem->as_text;
if ($responder || $message) {
my $format_elem = $edit_tree->look_down(
'_tag' => 'input', 'name' => 'type',
'value' => 'text/plain' );
my $plain = defined $format_elem->attr( 'checked' );
$out->dataElement( 'responder', $message,
'enabled' => ($responder ? 'true' : 'false'),
'format' => ($plain ? 'plain' : 'html'),
);
}
$out->endTag( 'mailbox' );
}
sub read_domain ($) {
my ($domain) = @_;
logmsg( "fetching list of mailboxen for $domain" );
my $list_page = fetch( '/simpleHTML/fw_mail.php',
'Domain' => $domain,
) or return;
my $list_tree = treeify( $list_page );
if (defined $list_tree->look_down( '_tag' => 'input',
'class' => 'sbt', 'value' => 'Enable' )) {
logmsg( "email is disabled on $domain" );
return;
}
$out->startTag( 'domain', 'name' => $domain );
my $list_elem = $list_tree->look_down(
'_tag' => 'select', 'name' => 'Mailbox' );
unless (defined $list_elem) {
logmsg( "unable to find account list" );
return;
}
my @options = $list_elem->find_by_tag_name( 'option' );
for my $option (@options) {
my $mailbox = $option->attr( 'value' );
read_mailbox( $mailbox, $domain );
}
$out->endTag( 'domain' );
}
########################################################################
# Executable Body #
########################################################################
# TODO: parse command-line arguments
$host = "48.webmasters.com";
$user = "science_sam";
$pass = "SomethingSecrety";
my $out_path = "addresses.xml";
open( my $out_file, ">", $out_path )
or die "unable to open output file '$out_path': $!";
$out = XML::Writer->new(
OUTPUT => $out_file,
DATA_MODE => 1,
DATA_INDENT => 2,
ENCODING => 'utf-8',
);
$out->xmlDecl();
$out->comment( "created by 4admin-emaildump $global::VERSION" );
$out->startTag( 'account' );
$agent = LWP::UserAgent->new(
'agent' => "4admin-emaildump/$global::VERSION ",
'cookie_jar' => {}, # creates an empty cookie jar
'conn_cache' => LWP::ConnCache->new,
'protocols_allowed' => [ 'https' ],
'ssl_opts' => {
'verify_hostname' => 0,
'SSL_verify_mode' => 0
},
'requests_redirectable' => []
);
logmsg( 'fetching list of mailboxen for master account' );
my $list_page = fetch( '/simpleHTML/mail_menu.php' ) or die;
my $list_tree = treeify( $list_page );
# the domain of the master account
# Augh, why can't they use IDs? This is horrible.
my $domain_parent_elem = $list_tree->look_down(
'_tag' => 'b', sub ($) {
for my $node ($_[ 0 ]->content_list) {
return 1 if (!ref $node and $node eq 'Account: ');
}
return 0;
}
);
my $domain = $domain_parent_elem->look_down(
'_tag' => 'font' )->as_trimmed_text;
$out->startTag( 'domain', 'name' => $domain );
my $list_elem = $list_tree->look_down(
'_tag' => 'select', 'name' => 'Mailbox' );
die "unable to find account list" unless (defined $list_elem);
my @options = $list_elem->find_by_tag_name( 'option' );
for my $option (@options) {
my $mailbox = $option->attr( 'value' );
read_mailbox( $mailbox, undef );
}
$out->endTag( 'domain' );
logmsg( "fetching list of addon domains" );
my $addon_page = fetch( "/simpleHTML/fw_top.php?Master=$domain" );
my $addon_tree = treeify( $addon_page );
my $addon_elem = $addon_tree->look_down(
'_tag' => 'select', 'name' => 'dom' );
for my $addon ($addon_elem->find_by_tag_name( 'option' )) {
read_domain( $addon->attr( 'value' ) );
}
$out->endTag( 'account' );
$out->end();
close( $out_file );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment