|
|
|
#!/usr/local/bin/perl
|
|
|
|
# WANT_JSON
|
|
|
|
|
|
|
|
# yamd - client Yandex.Mail for Domain API
|
|
|
|
# Run as stand-alone application or as Ansible module
|
|
|
|
#
|
|
|
|
# Copyright 2018 Sergey Kiselev <root@digital-freak.ru>
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
|
|
# modification, are permitted provided that the following conditions are met:
|
|
|
|
#
|
|
|
|
# 1. Redistributions of source code must retain the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer.
|
|
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
|
|
# and/or other materials provided with the distribution.
|
|
|
|
# 3. Neither the name of the copyright holder nor the names of its
|
|
|
|
# contributors may be used to endorse or promote products derived from this
|
|
|
|
# software without specific prior written permission.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
our $VERSION = '0.1';
|
|
|
|
|
|
|
|
use File::Basename;
|
|
|
|
use Encode qw/encode/;
|
|
|
|
|
|
|
|
use YAML::XS qw/Load LoadFile/;
|
|
|
|
use JSON::XS;
|
|
|
|
use Types::Serialiser;
|
|
|
|
|
|
|
|
use LWP::UserAgent;
|
|
|
|
use HTTP::Request;
|
|
|
|
|
|
|
|
our $api_url = "https://pddimp.yandex.ru/api2/admin";
|
|
|
|
our $api_requests = Load(<<EOYAML
|
|
|
|
---
|
|
|
|
default_content_type: 'application/x-www-form-urlencoded'
|
|
|
|
deputy:
|
|
|
|
list:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
add:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, login]
|
|
|
|
delete:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, login]
|
|
|
|
|
|
|
|
dkim:
|
|
|
|
status:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
disable:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
enable:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
|
|
|
|
dns:
|
|
|
|
list:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
add:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, type, content]
|
|
|
|
del:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, record_id]
|
|
|
|
edit:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, record_id]
|
|
|
|
|
|
|
|
domain:
|
|
|
|
details:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
domains:
|
|
|
|
query_type: 'GET'
|
|
|
|
registration_status:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
delete:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
register:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
|
|
|
|
logo:
|
|
|
|
check:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
del:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
set:
|
|
|
|
content_type: 'multipart/form-data'
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, file]
|
|
|
|
|
|
|
|
settings:
|
|
|
|
set_country:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, country]
|
|
|
|
|
|
|
|
email:
|
|
|
|
counters:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain, login]
|
|
|
|
list:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
add:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, login, password]
|
|
|
|
del:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, login]
|
|
|
|
edit:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, login]
|
|
|
|
get_oauth_token:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, login]
|
|
|
|
|
|
|
|
ml:
|
|
|
|
list:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
subscribers:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain, maillist]
|
|
|
|
add:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, maillist]
|
|
|
|
del:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, maillist]
|
|
|
|
get_can_send_on_behalf:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, maillist, subscriber]
|
|
|
|
set_can_send_on_behalf:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, maillist, subscriber, can_send_on_behalf]
|
|
|
|
subscribe:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, maillist, subscriber]
|
|
|
|
unsubscribe:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, maillist, subscriber]
|
|
|
|
|
|
|
|
import:
|
|
|
|
check_imports:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
check_settings:
|
|
|
|
query_type: 'GET'
|
|
|
|
parameters:
|
|
|
|
required: [domain, server, port, method, ssl]
|
|
|
|
start_import_file:
|
|
|
|
content_type: 'multipart/form-data'
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, file, server, port, method, ssl]
|
|
|
|
start_one_import:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain, server, port, method, ssl, ext-login, ext-password]
|
|
|
|
stop_all_imports:
|
|
|
|
query_type: 'POST'
|
|
|
|
parameters:
|
|
|
|
required: [domain]
|
|
|
|
EOYAML
|
|
|
|
);
|
|
|
|
|
|
|
|
our $task_file = shift or die "Usage: " . basename($0) . "<file.yml\n";
|
|
|
|
our $task = undef;
|
|
|
|
|
|
|
|
{
|
|
|
|
my ($filename, $path, $suffix) = fileparse($task_file);
|
|
|
|
|
|
|
|
if ($path =~ 'ansible' && $filename == 'args') {
|
|
|
|
# Calling from Ansible with args in the JSON-file
|
|
|
|
open(my $fh, "<", $task_file);
|
|
|
|
$task = decode_json(<$fh>);
|
|
|
|
close $fh;
|
|
|
|
} else {
|
|
|
|
# Stand-alone running with args in the YAML-file
|
|
|
|
$task = %{LoadFile($task_file)}{yamd}
|
|
|
|
or die "Unable to open file '" . $task_file ."'\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Checking general mandatory parameters of the request
|
|
|
|
foreach my $check ("query", "service", "token") {
|
|
|
|
if (!defined $task->{$check}) {
|
|
|
|
die "'" . $check . "' is undefined in '" . $task_file . "'\n";
|
|
|
|
} else {
|
|
|
|
if (!ref($task->{$check})) {
|
|
|
|
if ($task->{$check} =~ /^-?\d+\.?\d*/) {
|
|
|
|
die "Incorrect value of '" . $check . "' in '" . $task_file . "'\n";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
die "Incorrect value of '" . $check . "' in '" . $task_file . "'\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my $request_url = "";
|
|
|
|
my $request_type = "";
|
|
|
|
my $request_content_type = "";
|
|
|
|
my $service = $task->{service};
|
|
|
|
my $sub_service = $task->{sub_service};
|
|
|
|
my $query = $task->{query};
|
|
|
|
my $options = $task->{options};
|
|
|
|
my $api_request = undef;
|
|
|
|
my @check_list;
|
|
|
|
|
|
|
|
# Checking mandatory parameters of the request
|
|
|
|
if (defined $sub_service) {
|
|
|
|
$api_request = $api_requests->{$service}->{$sub_service}->{$query};
|
|
|
|
} else {
|
|
|
|
$api_request = $api_requests->{$service}->{$query};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (defined $api_request->{parameters}->{required}) {
|
|
|
|
@check_list = @{$api_request->{parameters}->{required}};
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach my $check (@check_list) {
|
|
|
|
if (defined $task->{$check}) {
|
|
|
|
if (!ref($task->{$check})) {
|
|
|
|
if ($task->{$check} =~ /^-?\d+\.?\d*/) {
|
|
|
|
die "Incorrect value of '" . $check . "' in '" . $task_file . "'\n";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
die "Incorrect value of '" . $check . "' in '" . $task_file . "'\n";
|
|
|
|
}
|
|
|
|
} elsif (defined $options->{$check}) {
|
|
|
|
if (!ref($options->{$check})) {
|
|
|
|
if ($options->{$check} =~ /^-?\d+\.?\d*/) {
|
|
|
|
die "Incorrect value of '" . $check . "' in '" . $task_file . "'\n";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
die "Incorrect value of '" . $check . "' in '" . $task_file . "'\n";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
die "'" . $check . "' is undefined in '" . $task_file . "'\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$request_url = $api_url . "/" . $service;
|
|
|
|
$request_url .= (defined $sub_service) ? "/" . $sub_service : "";
|
|
|
|
$request_url .= "/" . $query;
|
|
|
|
|
|
|
|
if ( $service eq "import" && $query eq "start_import_file" ) {
|
|
|
|
$request_url .= "?domain=" . $task->{domain};
|
|
|
|
foreach my $k (keys %{$task->{options}}) {
|
|
|
|
$request_url .= "&" . $k . "=" . $task->{options}->{$k};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (defined $sub_service) {
|
|
|
|
$request_type = $api_request->{query_type};
|
|
|
|
$request_content_type = $api_request->{content_type};
|
|
|
|
} else {
|
|
|
|
$request_type = $api_request->{query_type};
|
|
|
|
$request_content_type = $api_request->{content_type};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!defined $request_content_type) {
|
|
|
|
$request_content_type = $api_requests->{default_content_type};
|
|
|
|
}
|
|
|
|
if (!defined $sub_service) {
|
|
|
|
$sub_service = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
my $ua = new LWP::UserAgent;
|
|
|
|
my $request = new HTTP::Request($request_type => $request_url);
|
|
|
|
|
|
|
|
if ( $service eq "domain" &&
|
|
|
|
$sub_service eq "logo" &&
|
|
|
|
$query eq "set" ) {
|
|
|
|
{
|
|
|
|
my $boundary = 'X';
|
|
|
|
my @rand = ('a'..'z', 'A'..'Z');
|
|
|
|
for (0..14) { $boundary .= $rand[rand(@rand)]; }
|
|
|
|
|
|
|
|
$request->header(
|
|
|
|
'PddToken' => $task->{token},
|
|
|
|
'Content-type' => $request_content_type . "; boundary=" . $boundary
|
|
|
|
);
|
|
|
|
|
|
|
|
my $field = new HTTP::Message([
|
|
|
|
'Content-Disposition' => 'form-data; name="domain"',
|
|
|
|
'Content-Type' => 'text/plain; charset=utf-8'
|
|
|
|
]);
|
|
|
|
$field->add_content_utf8($task->{domain});
|
|
|
|
$request->add_part($field);
|
|
|
|
|
|
|
|
open(my $fh, '<', $task->{options}->{file});
|
|
|
|
my $file_content = new HTTP::Message([
|
|
|
|
'Content-Disposition' => 'form-data; name="file"; filename="logo"',
|
|
|
|
'Content-Type' => 'application/octet-stream'
|
|
|
|
]);
|
|
|
|
$file_content->add_content($_) while <$fh>;
|
|
|
|
$request->add_part($file_content);
|
|
|
|
|
|
|
|
close $fh;
|
|
|
|
}
|
|
|
|
} elsif ( $task->{service} eq "import" &&
|
|
|
|
$task->{query} eq "start_import_file" ) {
|
|
|
|
{
|
|
|
|
my $boundary = 'X';
|
|
|
|
my @rand = ('a'..'z', 'A'..'Z');
|
|
|
|
for (0..14) { $boundary .= $rand[rand(@rand)]; }
|
|
|
|
|
|
|
|
$request->header(
|
|
|
|
'PddToken' => $task->{token},
|
|
|
|
'Content-type' => $request_content_type . "; boundary=" . $boundary
|
|
|
|
);
|
|
|
|
|
|
|
|
open(my $fh, '<', $task->{options}->{file});
|
|
|
|
my $file_content = new HTTP::Message([
|
|
|
|
'Content-Disposition' => 'form-data; name="import_list_file"; filename="import_list_file"',
|
|
|
|
'Content-Type' => 'text/plain',
|
|
|
|
]);
|
|
|
|
$file_content->add_content($_) while <$fh>;
|
|
|
|
$request->add_part($file_content);
|
|
|
|
|
|
|
|
close $fh;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
{
|
|
|
|
$request->header(
|
|
|
|
'PddToken' => $task->{token},
|
|
|
|
'Content-type' => $request_content_type
|
|
|
|
);
|
|
|
|
|
|
|
|
my $request_content = "";
|
|
|
|
if (defined $task->{domain}) {
|
|
|
|
$request_content = "domain=" . $task->{domain};
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach my $k (keys %{$task->{options}}) {
|
|
|
|
$request_content .= "&" . $k . "=";
|
|
|
|
$request_content .= encode("utf8",$task->{options}->{$k});
|
|
|
|
}
|
|
|
|
|
|
|
|
$request->content($request_content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my $response = $ua->request($request);
|
|
|
|
|
|
|
|
if ($response->is_success) {
|
|
|
|
print $response->content . "\n\n";
|
|
|
|
} else {
|
|
|
|
print $response->status_line . "\n\n";
|
|
|
|
}
|
|
|
|
|