CodeCommitsIssuesPull requestsActionsInsightsSecurity
ba441348a14258e3d8a4d55d082889474c3e25f3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Cpanel/CloudFlare.pm

692lines · modecode

1package Cpanel::CloudFlare;
2
3# cpanel - Cpanel/CloudFlare.pm Copyright(c) 2011 CloudFlare, Inc.
4# All rights Reserved.
5# copyright@cloudflare.com http://cloudflare.com
6# @author ian@cloudflare.com
7# This code is subject to the cPanel license. Unauthorized copying is prohibited
8
9use Cpanel::DnsUtils::UsercPanel ();
10use Cpanel::AdminBin ();
11use Cpanel::Locale ();
12use Cpanel::Logger ();
13use Cpanel::UrlTools ();
14use Cpanel::SocketIP ();
15use Cpanel::Encoder::URI ();
16use Cpanel::AcctUtils ();
17use Cpanel::DomainLookup ();
18use Cpanel::DataStore ();
19
20use Socket ();
21use JSON::Syck ();
22use Digest::MD5 qw(md5_hex);
23use strict;
24
25my $logger = Cpanel::Logger->new();
26my $locale;
27my $cf_config_file = "/usr/local/cpanel/etc/cloudflare.json";
28my $cf_data_file_name = ".cpanel/datastore/cloudflare_data.yaml";
29my $cf_old_data_file_name = "/usr/local/cpanel/etc/cloudflare_data.yaml";
30my $cf_data_file;
31my $cf_host_key;
32my $cf_host_name;
33my $cf_host_uri;
34my $cf_user_name;
35my $cf_user_uri;
36my $cf_host_port;
37my $cf_host_on_cloud_msg;
38my $cf_host_prefix;
39my $has_ssl;
40my $cf_debug_mode;
41my $hoster_name;
42my $cf_cp_version;
43my $cf_global_data = {};
44my $DEFAULT_HOSTER_NAME = "your web hosting provider";
45
46my %KEYMAP = ( 'line' => 'Line', 'ttl' => 'ttl', 'name' => 'name',
47 'class' => 'class', 'address' => 'address', 'type' => 'type',
48 'txtdata' => 'txtdata', 'preference' => 'preference', 'exchange' => 'exchange' );
49
50## Initialize vars here.
51sub CloudFlare_init {
52 my $data = JSON::Syck::LoadFile($cf_config_file);
53
54 $cf_host_key = $data->{"host_key"};
55 $cf_host_name = $data->{"host_name"};
56 $cf_host_uri = $data->{"host_uri"};
57 $cf_host_port = $data->{"host_port"};
58 $cf_host_prefix = $data->{"host_prefix"};
59 $cf_debug_mode = $data->{"debug"};
60 $cf_user_name = $data->{"user_name"};
61 $cf_user_uri = $data->{"user_uri"};
62 $cf_cp_version = $data->{"cp_version"};
63 $hoster_name = $data->{"host_formal_name"};
64 $cf_host_on_cloud_msg = ($data->{"cloudflare_on_message"})? $data->{"cloudflare_on_message"}: "";
65 if (!$hoster_name) {
66 $hoster_name = $DEFAULT_HOSTER_NAME;
67 }
68
69 eval { use Net::SSLeay qw(post_https make_headers make_form); $has_ssl = 1 };
70 if ( !$has_ssl ) {
71 $logger->warn("Failed to load Net::SSLeay: $@.\nDisabling functionality until fixed.");
72 }
73
74 ## Load the api key.
75 if (-x "/usr/local/cpanel/bin/apikeywrap") {
76 my $response=`/usr/local/cpanel/bin/apikeywrap $cf_host_key`;
77 chomp $response;
78 $cf_host_key = $response;
79 }
80}
81
82# Can only be called with json or xml api because it uses
83# a non-standard return
84sub api2_user_create {
85 my %OPTS = @_;
86
87 if (!$cf_host_key) {
88 $logger->info("Missing cf_host_key! Define this in $cf_config_file.");
89 return [];
90 }
91
92 __load_data_file($OPTS{"homedir"});
93 # Use a random string as a password.
94 my $password = ($OPTS{"password"})? $OPTS{"password"}: crypt(int(rand(10000000)), time);
95 $logger->info("Creating Cloudflare user for " . $OPTS{"email"} . " -- " . $password);
96 $cf_global_data->{"cf_user_tokens"}->{$OPTS{"user"}} = md5_hex($OPTS{"user"} . $cf_host_key);
97 $logger->info("Making user token: " . $cf_global_data->{"cf_user_tokens"}->{$OPTS{"user"}});
98
99 ## Otherwise, try to create this user.
100 my $args = {
101 "host" => $cf_host_name,
102 "uri" => $cf_host_uri,
103 "port" => $cf_host_port,
104 "query" => {
105 "act" => "user_create",
106 "host_key" => $cf_host_key,
107 "cloudflare_email" => $OPTS{"email"},
108 "cloudflare_pass" => $password,
109 "unique_id" => $cf_global_data->{"cf_user_tokens"}->{$OPTS{"user"}},
110 },
111 };
112
113 Cpanel::DataStore::store_ref($cf_data_file, $cf_global_data);
114 my $result = __https_post_req->($args);
115 return JSON::Syck::Load($result);
116}
117
118# Can only be called with json or xml api because it uses
119# a non-standard return
120sub api2_user_lookup {
121 my %OPTS = @_;
122
123 __load_data_file($OPTS{"homedir"}, $OPTS{"user"});
124 if (!$cf_host_key) {
125 $logger->info("Missing cf_host_key! Define this in $cf_config_file.");
126 return [];
127 }
128
129 if ( !$has_ssl ) {
130 $logger->info("No SSL Configured");
131 return [{"result"=>"error",
132 "msg" => "CloudFlare is disabled until Net::SSLeay is installed on this server."}];
133 }
134
135 if ($cf_global_data->{"cf_user_tokens"}->{$OPTS{"user"}}) {
136 if ($cf_debug_mode) {
137 $logger->info("Using user token");
138 }
139 my $login_args = {
140 "host" => $cf_host_name,
141 "uri" => $cf_host_uri,
142 "port" => $cf_host_port,
143 "query" => {
144 "act" => "user_lookup",
145 "host_key" => $cf_host_key,
146 "unique_id" => $cf_global_data->{"cf_user_tokens"}->{$OPTS{"user"}},
147 },
148 };
149
150 my $result = JSON::Syck::Load(__https_post_req->($login_args));
151 $result->{"on_cloud_message"} = $cf_host_on_cloud_msg;
152 return ($result);
153 } else {
154 if ($cf_debug_mode) {
155 $logger->info("Using user email");
156 }
157 my $login_args = {
158 "host" => $cf_host_name,
159 "uri" => $cf_host_uri,
160 "port" => $cf_host_port,
161 "query" => {
162 "act" => "user_lookup",
163 "host_key" => $cf_host_key,
164 "cloudflare_email" => $OPTS{"email"},
165 },
166 };
167
168 my $result = JSON::Syck::Load(__https_post_req->($login_args));
169 $result->{"on_cloud_message"} = $cf_host_on_cloud_msg;
170 return ($result);
171 }
172}
173
174## Pulls certain stats for the passed in zone.
175sub api2_get_stats {
176 my %OPTS = @_;
177
178 if (!$OPTS{"user_api_key"}) {
179 $logger->info("Missing user_api_key!");
180 return [];
181 }
182
183 if ( !$has_ssl ) {
184 $logger->info("No SSL Configured");
185 return [{"result"=>"error",
186 "msg" => "CloudFlare is disabled until Net::SSLeay is installed on this server."}];
187 }
188
189 ## Otherwise, pull this users stats.
190 my $stats_args = {
191 "host" => $cf_user_name,
192 "uri" => $cf_user_uri,
193 "port" => $cf_host_port,
194 "query" => {
195 "a" => "stats",
196 "z" => $OPTS{"zone_name"},
197 "tkn" => $OPTS{"user_api_key"},
198 "u" => $OPTS{"user_email"},
199 "interval" => 30, # 30 = last 7 days, 20 = last 30 days 40 = last 24 hours
200 },
201 };
202
203 my $result = __https_post_req->($stats_args);
204 return JSON::Syck::Load($result);
205}
206
207sub api2_edit_cf_setting {
208 my %OPTS = @_;
209
210 if (!$OPTS{"user_api_key"}) {
211 $logger->info("Missing user_api_key!");
212 return [];
213 }
214
215 if ( !$has_ssl ) {
216 $logger->info("No SSL Configured");
217 return [{"result"=>"error",
218 "msg" => "CloudFlare is disabled until Net::SSLeay is installed on this server."}];
219 }
220
221 ## Otherwise, pull this users stats.
222 my $stats_args = {
223 "host" => $cf_user_name,
224 "uri" => $cf_user_uri,
225 "port" => $cf_host_port,
226 "query" => {
227 "a" => $OPTS{"a"},
228 "z" => $OPTS{"zone_name"},
229 "tkn" => $OPTS{"user_api_key"},
230 "u" => $OPTS{"user_email"},
231 "v" => $OPTS{"v"},
232 },
233 };
234
235 my $result = __https_post_req->($stats_args);
236 return JSON::Syck::Load($result);
237}
238
239# Can only be called with json or xml api because it uses
240# a non-standard return
241sub api2_zone_set {
242 my %OPTS = @_;
243
244 if (!$cf_host_key || !$cf_host_prefix) {
245 $logger->info("Missing cf_host_key or $cf_host_prefix! Define these in $cf_config_file.");
246 return [];
247 }
248
249 __load_data_file($OPTS{"homedir"});
250 my $domain = ".".$OPTS{"zone_name"}.".";
251 my $subs = $OPTS{"subdomains"};
252 $subs =~ s/${domain}//g;
253
254 ## Unpack the mapping from recs to lines (ugg, this is SOOO BAAD)
255 my $recs2lines = JSON::Syck::Load($OPTS{"cf_recs"});
256
257 ## Set up the zone_set args.
258 my $login_args = {
259 "host" => $cf_host_name,
260 "uri" => $cf_host_uri,
261 "port" => $cf_host_port,
262 "query" => {
263 "act" => "zone_set",
264 "host_key" => $cf_host_key,
265 "user_key" => $OPTS{"user_key"},
266 "zone_name" => $OPTS{"zone_name"},
267 "resolve_to" => $cf_host_prefix . "." . $OPTS{"zone_name"},
268 "subdomains" => $subs
269 },
270 };
271
272 if (!$subs) {
273 $login_args->{"query"}->{"act"} = "zone_delete";
274 $cf_global_data->{"cf_zones"}->{$OPTS{"zone_name"}} = 0
275 }
276
277 my $result = JSON::Syck::Load(__https_post_req->($login_args));
278
279 ## Args for updating local DNS.
280 my %zone_args = ("domain" => $OPTS{"zone_name"},
281 "class" => "IN",
282 "type" => "CNAME",
283 "name" => $cf_host_prefix,
284 "ttl" => 1400,
285 "cname" => $OPTS{"zone_name"},
286 );
287
288 my $is_cf = 0;
289
290 ## If we get an error, do nothing and return the error to the user.
291 if ($result->{"result"} eq "error") {
292 $logger->info("CloudFlare Error: " . $result->{"msg"});
293 } else {
294 ## Otherwise, update the dns for this zone.
295 my $dom = ".".$OPTS{"zone_name"};
296
297 ## First Make sure that the resolve to is set.
298 my $res = Cpanel::AdminBin::adminfetchnocache( 'zone', '', 'ADD', 'storable', $OPTS{"zone_name"},
299 __serialize_request( \%zone_args ) );
300
301 ## If there's a delete, remove this record from being CF.
302 if ($OPTS{"old_line"}) {
303 $zone_args{"line"} = $OPTS{"old_line"};
304 $zone_args{"name"} = $OPTS{"old_rec"};
305 $zone_args{"name"} =~ s/$domain//g;
306 $zone_args{"cname"} = $OPTS{"zone_name"};
307 $res = Cpanel::AdminBin::adminfetchnocache( 'zone', '', 'EDIT', 'storable', $OPTS{"zone_name"},
308 __serialize_request( \%zone_args ) );
309 }
310
311 ## Now, go ahead and update all of the CF enabled recs.
312 foreach my $ft (keys %{$result->{"response"}->{"forward_tos"}}) {
313 $zone_args{"line"} = $recs2lines->{$ft."."};
314 $zone_args{"name"} = $ft;
315 $zone_args{"name"} =~ s/$dom//g;
316 $zone_args{"cname"} = $result->{"response"}->{"forward_tos"}->{$ft};
317
318 $res = Cpanel::AdminBin::adminfetchnocache( 'zone', '', 'EDIT', 'storable', $OPTS{"zone_name"},
319 __serialize_request( \%zone_args ) );
320 if (!$res->{"status"}) {
321 ## Try again, bumping up the line by 1. -- no idea why this works.
322 $zone_args{"line"}++;
323 $res = Cpanel::AdminBin::adminfetchnocache( 'zone', '', 'EDIT', 'storable', $OPTS{"zone_name"},
324 __serialize_request( \%zone_args ) );
325 }
326
327 if (!$res->{"status"}) {
328 $logger->info("Failed to set DNS for CloudFlare record $ft!");
329 $logger->info(JSON::Syck::Dump($res));
330 $result->{"result"} = "error";
331 $result->{"msg"} = $res->{"statusmsg"};
332 }
333
334 ## Note that if at least one rec is on, this zone is on CF.
335 $cf_global_data->{"cf_zones"}->{$OPTS{"zone_name"}} = 1;
336 $is_cf = 1;
337 }
338 }
339
340 if (!$is_cf) {
341 $cf_global_data->{"cf_zones"}->{$OPTS{"zone_name"}} = 0;
342 }
343
344 ## Save the updated global data arg.
345 Cpanel::DataStore::store_ref($cf_data_file, $cf_global_data);
346
347 return $result;
348}
349
350sub api2_zone_delete {
351 my %OPTS = @_;
352
353 if (!$cf_host_key || !$cf_host_prefix) {
354 $logger->info("Missing cf_host_key or $cf_host_prefix! Define these in $cf_config_file.");
355 return [];
356 }
357
358 __load_data_file($OPTS{"homedir"});
359 my $domain = ".".$OPTS{"zone_name"}.".";
360
361 ## Unpack the mapping from recs to lines (ugg, this is SOOO BAAD)
362 my $recs2lines = JSON::Syck::Load($OPTS{"cf_recs"});
363
364 ## Set up the zone_set args.
365 my $login_args = {
366 "host" => $cf_host_name,
367 "uri" => $cf_host_uri,
368 "port" => $cf_host_port,
369 "query" => {
370 "act" => "zone_delete",
371 "host_key" => $cf_host_key,
372 "user_key" => $OPTS{"user_key"},
373 "zone_name" => $OPTS{"zone_name"}
374 },
375 };
376
377 $cf_global_data->{"cf_zones"}->{$OPTS{"zone_name"}} = 0;
378
379 my $result = JSON::Syck::Load(__https_post_req->($login_args));
380
381 ## Args for updating local DNS.
382 my %zone_args = ("domain" => $OPTS{"zone_name"},
383 "class" => "IN",
384 "type" => "CNAME",
385 "name" => $cf_host_prefix,
386 "ttl" => 1400,
387 "cname" => $OPTS{"zone_name"},
388 );
389
390 ## If we get an error, do nothing and return the error to the user.
391 if ($result->{"result"} eq "error") {
392 $logger->info("CloudFlare Error: " . $result->{"msg"});
393 } else {
394 ## Otherwise, update the dns for this zone.
395 my $dom = ".".$OPTS{"zone_name"};
396
397 ## Loop over list of subs, removing from CF.
398 my $res;
399 foreach my $linecom (split(/,/, $OPTS{"subdomains"})) {
400 my @line = split(':', $linecom);
401 $zone_args{"line"} = $line[1];
402 $zone_args{"name"} = $line[0];
403 $zone_args{"name"} =~ s/$domain//g;
404 $zone_args{"cname"} = $OPTS{"zone_name"};
405 $res = Cpanel::AdminBin::adminfetchnocache( 'zone', '', 'EDIT', 'storable', $OPTS{"zone_name"},
406 __serialize_request( \%zone_args ) );
407 }
408 }
409
410 ## Save the updated global data arg.
411 Cpanel::DataStore::store_ref($cf_data_file, $cf_global_data);
412
413 return $result;
414}
415
416sub api2_fetchzone {
417 my $raw = __fetchzone(@_);
418 my $results = [];
419 my %OPTS = @_;
420 my $domain = $OPTS{'domain'}.".";
421
422 foreach my $res (@{$raw->{"record"}}) {
423 if ((($res->{"type"} eq "CNAME") || ($res->{"type"} eq "A")) &&
424 ($res->{"name"} !~ /(^direct|^ssh|^ftp|ssl|mx|^ns[^.]*|^imap[^.]*|^pop[^.]*|smtp[^.]*|^mail[^.]*|^mx[^.]*|^exchange[^.]*|^smtp[^.]*|google[^.]*|^secure|^sftp|^svn|^git|^irc|^email|^mobilemail|^pda|^webmail|^e\.|^video|^vid|^vids|^sites|^calendar|^svn|^cvs|^git|^cpanel|^panel|^repo|^webstats|^local|localhost|$cf_host_prefix)/) &&
425 ($res->{"name"} ne $domain) &&
426 ($res->{"cname"} !~ /google.com/)){
427 if ($res->{"cname"} =~ /cdn.cloudflare.net$/) {
428 $res->{"cloudflare"} = 1;
429 } else {
430 $res->{"cloudflare"} = 0;
431 }
432 push @$results, $res;
433 }
434 }
435 return $results;
436}
437
438sub api2_getbasedomains {
439 my %OPTS = @_;
440 __load_data_file($OPTS{"homedir"}, $OPTS{"user"});
441 my $res = Cpanel::DomainLookup::api2_getbasedomains(@_);
442 my $has_cf = 0;
443 foreach my $dom (@$res) {
444 if ($cf_global_data->{"cf_zones"}->{$dom->{"domain"}}) {
445 $dom->{"cloudflare"} = 1;
446 $has_cf = 1;
447 } else {
448 $dom->{"cloudflare"} = 0;
449 }
450 }
451 return {"has_cf" => $has_cf, "res" => $res, "hoster" => $hoster_name};
452}
453
454sub api2 {
455 my $func = shift;
456
457 my %API;
458
459 $API{'user_create'}{'func'} = 'api2_user_create';
460 $API{'user_create'}{'engine'} = 'hasharray';
461 $API{'user_lookup'}{'func'} = 'api2_user_lookup';
462 $API{'user_lookup'}{'engine'} = 'hasharray';
463 $API{'zone_set'}{'func'} = 'api2_zone_set';
464 $API{'zone_set'}{'engine'} = 'hasharray';
465 $API{'zone_delete'}{'func'} = 'api2_zone_delete';
466 $API{'zone_delete'}{'engine'} = 'hasharray';
467 $API{'fetchzone'}{'func'} = 'api2_fetchzone';
468 $API{'fetchzone'}{'engine'} = 'hasharray';
469 $API{'getbasedomains'}{'func'} = 'api2_getbasedomains';
470 $API{'getbasedomains'}{'engine'} = 'hasharray';
471 $API{'zone_get_stats'}{'func'} = 'api2_get_stats';
472 $API{'zone_get_stats'}{'engine'} = 'hasharray';
473 $API{'zone_edit_cf_setting'}{'func'} = 'api2_edit_cf_setting';
474 $API{'zone_edit_cf_setting'}{'engine'} = 'hasharray';
475
476 return ( \%{ $API{$func} } );
477}
478
479## Run the auto-update here
480sub check_auto_update {
481
482 print "Checking CloudFlare for latest version\n";
483 CloudFlare_init();
484 print "Current Version: $cf_cp_version\n";
485
486 my $check_args = {
487 "host" => $cf_host_name,
488 "uri" => $cf_host_uri,
489 "port" => $cf_host_port,
490 "query" => {
491 "act" => "cpanel_info",
492 "host_key" => $cf_host_key,
493 },
494 };
495
496 my $result = JSON::Syck::Load(__https_post_req->($check_args));
497 print "Latest Version: " . $result->{"response"}{"cpanel_latest"} . "\n";
498 print "Latest SHA1: " . $result->{"response"}{"cpanel_sha1"} . "\n";
499
500 if ($result->{"response"}{"cpanel_latest"} > $cf_cp_version) {
501 print "Downloading the latest version.\n";
502 `curl -L https://github.com/cloudflare/CloudFlare-CPanel/tarball/master > /tmp/cloudflare.tar.gz`;
503 if (`sha1sum /tmp/cloudflare.tar.gz | grep $result->{"response"}{"cpanel_sha1"}`) {
504 print "Valid Checksum\n";
505 } else {
506 print "Checksum failed, aborting upgrade\n";
507 unlink("/tmp/cloudflare.tar.gz");
508 }
509 } else {
510 print "You already have the latest version.\n";
511 }
512}
513
514## Returns a list of all users and zones on CF.
515sub list_active_zones {
516 if ($>) {
517 die("must be root to run.");
518 }
519
520 CloudFlare_init();
521 my %zones;
522 foreach my $user (`ls /home`) {
523 chomp $user;
524 if ( -e "/home/$user/$cf_data_file_name") {
525 __load_data_file("/home/$user", $user);
526 foreach my $zone (keys %{$cf_global_data->{"cf_zones"}}) {
527 if ($cf_global_data->{"cf_zones"}{$zone}) {
528 $zones{$zone} = 1;
529 }
530 }
531 }
532 }
533
534 print "The following zones are currently active on CloudFlare:\n";
535 foreach my $zone (keys %zones) {
536 print "$zone\n";
537 }
538}
539
540########## Internal Functions Defined Below #########
541
542sub __load_data_file {
543 my $home_dir = shift;
544 my $user = shift;
545 $cf_data_file = $home_dir . "/" . $cf_data_file_name;
546 if( Cpanel::DataStore::load_ref($cf_data_file, $cf_global_data ) ) {
547 if ($cf_debug_mode) {
548 $logger->info("Successfully loaded cf data -- $cf_data_file");
549 }
550 } else {
551 ## Try to load the data from the old default data file (if it exists)
552 if (-e $cf_old_data_file_name) {
553 $logger->info( "Failed to load cf data -- Trying to copy from $cf_old_data_file_name for $user");
554 my $tmp_data = {};
555 Cpanel::DataStore::load_ref($cf_old_data_file_name, $tmp_data);
556 $cf_global_data->{"cf_user_tokens"}->{$user} = $tmp_data->{"cf_user_tokens"}->{$user};
557 $cf_global_data->{"cf_zones"} = $tmp_data->{"cf_zones"};
558
559 } else {
560 $cf_global_data = {"cf_zones" => {}};
561 $logger->info( "Failed to load cf data -- storing blank data at $cf_data_file");
562 }
563 Cpanel::DataStore::store_ref($cf_data_file, $cf_global_data);
564 chmod 0600, $cf_data_file;
565 }
566}
567
568sub __fetchzone {
569 my %OPTS = @_;
570 my $domain = $OPTS{'domain'};
571 my $results = Cpanel::AdminBin::adminfetchnocache( 'zone', '', 'FETCH', 'storable', $domain, ($OPTS{'customonly'} ? 1 : 0) );
572
573 if ( ref $results->{'record'} eq 'ARRAY' ) {
574 for(0 .. $#{ $results->{'record'} }) {
575 $results->{'record'}->[$_]->{'record'} = ($results->{'record'}->[$_]->{'address'} || $results->{'record'}->[$_]->{'cname'} || $results->{'record'}->[$_]->{'txtdata'});
576 $results->{'record'}->[$_]->{'line'} = ($results->{'record'}->[$_]->{'Line'});
577 }
578 foreach my $key ( keys %KEYMAP ) {
579 if ( exists $OPTS{$key} && defined $OPTS{$key} ) {
580 my %MULTITYPES = map { $_ => undef } split(/[\|\,]/, $OPTS{$key});
581 @{ $results->{'record'} } = grep { exists $MULTITYPES{$_->{ $KEYMAP{$key} }} } @{ $results->{'record'} };
582 }
583 }
584 }
585
586 return $results;
587}
588
589sub __https_post_req {
590 my ( $args_hr ) = @_;
591 if ($args_hr->{'port'} ne "443") {
592 ## Downgrade to http
593 return __http_post_req($args_hr);
594 } else {
595 my ( $args_hr ) = @_;
596 my ($page, $response, %reply_headers)
597 = post_https($args_hr->{'host'}, $args_hr->{'port'}, $args_hr->{'uri'}, '',
598 make_form(%{$args_hr->{'query'}}));
599 if ($cf_debug_mode) {
600 $logger->info($response);
601 $logger->info($page);
602 }
603 return $page;
604 }
605}
606
607sub __http_post_req {
608 my ( $args_hr ) = @_;
609 my $query = $args_hr->{'query'};
610 if ( ref $args_hr->{'query'} eq 'HASH' ) {
611 $query = '';
612 foreach my $key ( keys %{ $args_hr->{'query'} } ) {
613 if ( ref $args_hr->{'query'}{$key} eq 'ARRAY' ) {
614 for my $val ( @{ $args_hr->{'query'}{$key} } ) {
615 $query .=
616 $query
617 ? "&$key=" . Cpanel::Encoder::URI::uri_encode_str($val)
618 : "$key=" . Cpanel::Encoder::URI::uri_encode_str($val);
619 }
620 }
621 else {
622 $query .=
623 $query
624 ? "&$key=" . Cpanel::Encoder::URI::uri_encode_str( $args_hr->{'query'}{$key} )
625 : "$key=" . Cpanel::Encoder::URI::uri_encode_str( $args_hr->{'query'}{$key} );
626 }
627 }
628 }
629
630 my $postdata_len = length($query);
631
632 if ($cf_debug_mode) {
633 $logger->info($query);
634 }
635
636 my $proto = getprotobyname('tcp');
637 return unless defined $proto;
638
639 socket( my $socket_fh, &Socket::AF_INET, &Socket::SOCK_STREAM, $proto );
640 return unless $socket_fh;
641
642 my $iaddr = gethostbyname($args_hr->{'host'});
643 my $port = $args_hr->{'port'};
644
645 return unless ( defined $iaddr && defined $port );
646
647 my $sin = Socket::sockaddr_in( $port, $iaddr );
648 return unless defined $sin;
649
650 my $result = "";
651 if ( connect( $socket_fh, $sin ) ) {
652 send $socket_fh, "POST /$args_hr->{'uri'} HTTP/1.0\nContent-Length: $postdata_len\nContent-Type: application/x-www-form-urlencoded\nHost: $args_hr->{'host'}\n\n$query", 0;
653
654 my $in_header = 1;
655 while (<$socket_fh>) {
656 if ( /^\n$/ || /^\r\n$/ || /^$/ ) {
657 $in_header = 0;
658 next;
659 }
660
661 if (!$in_header) {
662 $result .= $_;
663 }
664 }
665 }
666
667 close $socket_fh;
668
669 if ($cf_debug_mode) {
670 $logger->info($result);
671 }
672
673 return $result;
674}
675
676sub __serialize_request {
677 my $opt_ref = shift;
678 my @KEYLIST;
679 foreach my $opt ( keys %$opt_ref ) {
680 push @KEYLIST, Cpanel::Encoder::URI::uri_encode_str($opt) . '=' . Cpanel::Encoder::URI::uri_encode_str( $opt_ref->{$opt} );
681 }
682 return join( '&', @KEYLIST );
683}
684
685## Are we running from the command line?
686if ($ARGV[0] eq "check") {
687 check_auto_update();
688} elsif ($ARGV[0] eq "list") {
689 list_active_zones();
690}
691
6921; # Ah, perl.