source: trunk/cgi-bin/ipdb-rpc.cgi@ 866

Last change on this file since 866 was 866, checked in by Kris Deugau, 8 years ago

/trunk

main.cgi:

  • Don't spew warnings and break the UI when retrieval of the zone list for a netblock fails

IPDB.pm, ipdb-rpc.cgi:

  • Fill out rpc_listPool stub in ipdb-rpc.cgi. The first likely consumer may not want the full UI dataset (with description and "deleteme" flag) so the core sub has been extended with an optional flag that defaults to on.

ipdb-rpc.cgi:

  • Correct argument validation in rpc_deleteBlock()
  • Property svn:executable set to *
  • Property svn:keywords set to Date Rev Author Id
File size: 13.3 KB
Line 
1#!/usr/bin/perl
2# XMLRPC interface to manipulate most DNS DB entities
3###
4# $Id: ipdb-rpc.cgi 866 2016-05-02 21:59:07Z kdeugau $
5###
6# Copyright (C) 2013,2014,2016 Kris Deugau <kdeugau@deepnet.cx>
7
8use strict;
9use warnings;
10
11use DBI;
12use CustIDCK;
13use NetAddr::IP;
14use FCGI;
15use Frontier::Responder;
16
17use Sys::Syslog;
18
19# don't remove! required for GNU/FHS-ish install from tarball
20##uselib##
21
22use MyIPDB;
23
24openlog "IPDB-rpc","pid","$IPDB::syslog_facility";
25
26##fixme: username source? can we leverage some other auth method?
27# we don't care except for logging here, and Frontier::Client needs
28# a patch that's not well-distributed to use HTTP AUTH.
29
30# Collect the username from HTTP auth. If undefined, we're in
31# a test environment, or called without a username.
32my $authuser;
33if (!defined($ENV{'REMOTE_USER'})) {
34 $authuser = '__temptest';
35} else {
36 $authuser = $ENV{'REMOTE_USER'};
37}
38
39# Why not a global DB handle? (And a global statement handle, as well...)
40# Use the connectDB function, otherwise we end up confusing ourselves
41my $ip_dbh;
42my $sth;
43my $errstr;
44($ip_dbh,$errstr) = connectDB_My;
45initIPDBGlobals($ip_dbh);
46
47my $methods = {
48 # Internal core IPDB subs; no value in exposing them since the DB handle can't be used by the caller
49 #'ipdb._rpc'
50 # This one could be exposed, but the globals aren't automatically
51 # inherited by the caller anyway, and we call it just above locally.
52 #'ipdb.initIPDBGlobals'
53 #'ipdb.connectDB'
54 #'ipdb.finish'
55 #'ipdb.checkDBSanity'
56 'ipdb.addMaster' => \&rpc_addMaster,
57 'ipdb.touchMaster' => \&rpc_touchMaster,
58 'ipdb.listSummary' => \&rpc_listSummary,
59 'ipdb.listSubs' => \&rpc_listSubs,
60 'ipdb.listContainers' => \&rpc_listContainers,
61 'ipdb.listAllocations' => \&rpc_listAllocations,
62 'ipdb.listFree' => \&rpc_listFree,
63 'ipdb.listPool' => \&rpc_listPool,
64 'ipdb.getMasterList' => \&rpc_getMasterList,
65 'ipdb.getTypeList' => \&rpc_getTypeList,
66 'ipdb.getPoolSelect' => \&rpc_getPoolSelect,
67 'ipdb.findAllocateFrom' => \&rpc_findAllocateFrom,
68 'ipdb.ipParent' => \&rpc_ipParent,
69 'ipdb.subParent' => \&rpc_subParent,
70 'ipdb.blockParent' => \&rpc_blockParent,
71 'ipdb.getRoutedCity' => \&rpc_getRoutedCity,
72 'ipdb.allocateBlock' => \&rpc_allocateBlock,
73 # another internal sub; mainly a sub to make allocateBlock() a lot smaller
74 #'ipdb.initPool' => \&rpc_initPool
75 'ipdb.updateBlock' => \&rpc_updateBlock,
76 'ipdb.deleteBlock' => \&rpc_deleteBlock,
77 'ipdb.getBlockData' => \&rpc_getBlockData,
78 'ipdb.getBlockRDNS' => \&rpc_getBlockRDNS,
79 'ipdb.getNodeList' => \&rpc_getNodeList,
80 'ipdb.getNodeName' => \&rpc_getNodeName,
81 'ipdb.getNodeInfo' => \&rpc_getNodeInfo,
82 'ipdb.mailNotify' => \&rpc_mailNotify,
83
84# Subs not part of the core IPDB
85 'ipdb.getDispAlloctypes' => \&rpc_getDispAlloctypes,
86 'ipdb.getListAlloctypes' => \&rpc_getListAlloctypes,
87 'ipdb.getDefCustIDs' => \&rpc_getDefCustIDs,
88 'ipdb.getCityList' => \&rpc_getCityList,
89 'ipdb.getAvailableStatics' => \&rpc_getAvailableStatics,
90 'ipdb.getBackupList' => \&rpc_getBackupList,
91};
92
93my $reqcnt = 0;
94
95# main FCGI loop.
96while (FCGI::accept >= 0) {
97 # done here to a) prevent $ENV{'REMOTE_ADDR'} from being empty and b) to collect
98 # the right user for the individual call (since we may be running with FCGI)
99 syslog "debug", "$authuser active, $ENV{'REMOTE_ADDR'}";
100
101 # don't *think* we need any of these...
102 # %disp_alloctypes, %def_custids, %list_alloctypes
103 # @citylist, @poplist
104 # @masterblocks, %allocated, %free, %bigfree, %routed (removed in /trunk)
105 # %IPDBacl
106 #initIPDBGlobals($ip_dbh);
107
108 my $res = Frontier::Responder->new(
109 methods => $methods
110 );
111
112 # "Can't do that" errors
113 if (!$ip_dbh) {
114 print "Content-type: text/xml\n\n".$res->{_decode}->encode_fault(5, $DBI::errstr);
115 } else {
116 print $res->answer;
117 }
118 last if $reqcnt++ > $IPDB::maxfcgi;
119} # while FCGI::accept
120
121exit 0;
122
123
124##
125## Private subs
126##
127
128# Check RPC ACL
129sub _aclcheck {
130 my $subsys = shift;
131 return 1 if grep /$ENV{REMOTE_ADDR}/, @{$IPDB::rpcacl{$subsys}};
132 warn "$subsys/$ENV{REMOTE_ADDR} not in ACL\n"; # a bit of logging
133 return 0;
134}
135
136sub _commoncheck {
137 my $argref = shift;
138 my $needslog = shift;
139
140 die "Missing remote system name\n" if !$argref->{rpcsystem};
141 die "Access denied\n" if !_aclcheck($argref->{rpcsystem});
142 if ($needslog) {
143 die "Missing remote username\n" if !$argref->{rpcuser};
144 }
145}
146
147# stripped-down copy from from main.cgi. should probably be moved to IPDB.pm
148sub _validateInput {
149 my $argref = shift;
150
151 if (!$argref->{block}) {
152 $argref->{block} = $argref->{cidr} if $argref->{cidr};
153 die "Block/IP is required\n" if !$argref->{block};
154 }
155
156 # Alloctype check.
157 chomp $argref->{type};
158
159 die "Invalid allocation type\n" if (!grep /$argref->{type}/, keys %disp_alloctypes);
160
161 # Arguably not quite correct, as the custID won't be checked for
162 # validity if there's a default on the type.
163 if ($def_custids{$argref->{type}} eq '') {
164 # Types without a default custID must have one passed in
165 die "Customer ID is required\n" if !$argref->{custid};
166 # Crosscheck with billing.
167 my $status = CustIDCK->custid_exist($argref->{custid});
168 die "Error verifying customer ID: $CustIDCK::ErrMsg\n" if $CustIDCK::Error;
169 die "Customer ID not valid\n" if !$status;
170 } else {
171 # Types that have a default will use it unless one is specified.
172 if ((!$argref->{custid}) || ($argref->{custid} ne 'STAFF')) {
173 $argref->{custid} = $def_custids{$argref->{type}};
174 }
175 }
176} # end validateInput()
177
178
179##
180## RPC method subs
181##
182
183# Core IPDB subs
184# Prefixed with rpc_ to not conflict with subs in IPDB.pm
185
186# These are deep internals and don't make much sense to expose, since RPC
187# by definition does not access the backing DB directly.
188#sub _rpc {}
189#sub initIPDBGlobals {}
190#sub connectDB {}
191#sub finish {}
192#sub checkDBSanity {}
193
194sub rpc_addMaster {}
195sub rpc_touchMaster {}
196sub rpc_listSummary {}
197sub rpc_listSubs {}
198sub rpc_listContainers {}
199sub rpc_listAllocations {}
200sub rpc_listFree {}
201
202
203sub rpc_listPool {
204 my %args = @_;
205
206 _commoncheck(\%args, 'y');
207
208 $args{include_desc} = 0 if !$args{include_desc};
209
210 # convert passed pool from CIDR to an ID, maybe
211 if ($args{pool} !~ /^\d+$/) {
212 die "Invalid pool argument" if $args{pool} !~ m{^\d+\.\d+\.\d+\.\d+/\d+$};
213 die "VRF is required\n" if !$args{vrf}; # VRF name may not be empty
214 ($pid) = $ip_dbh->selectrow_array("SELECT a.id FROM allocations a JOIN allocations m ON a.master_id=m.id".
215 " WHERE a.cidr = ? AND m.vrf = ?", undef, $args{pool}, $args{vrf});
216 }
217
218 return listPool($ip_dbh, $pid, $args{include_desc});
219} # rpc_listPool()
220
221
222sub rpc_getMasterList {}
223sub rpc_getTypeList {}
224sub rpc_getPoolSelect {}
225sub rpc_findAllocateFrom {}
226sub rpc_ipParent {}
227sub rpc_subParent {}
228sub rpc_blockParent {}
229sub rpc_getRoutedCity {}
230
231sub rpc_allocateBlock {
232 my %args = @_;
233
234 _commoncheck(\%args, 'y');
235
236 _validateInput(\%args);
237
238 # Not required for update, delete
239 die "City is required\n" if !$args{city};
240 die "Customer ID is required\n" if !$args{custid};
241 die "Allocation type is required\n" if !$args{type};
242
243 if ($args{type} =~ /^.i$/) {
244 die "Pool ID or VRF to allocate from is required\n" if !$args{parent} && !$args{vrf};
245 } else {
246 die "Free block to allocate from is required\n" if !$args{fbid};
247 }
248
249 my ($code,$msg) = allocateBlock($ip_dbh, %args);
250
251 if ($code eq 'OK' || $code eq 'WARN') {
252 if ($args{type} =~ /^.i$/) {
253 $msg =~ s|/32||;
254 mailNotify($ip_dbh, "a$args{type}", "ADDED: $disp_alloctypes{$args{type}} allocation",
255 "$disp_alloctypes{$args{type}} $msg allocated to customer $args{custid}\n".
256 "Description: $args{desc}\n\nAllocated by: $args{rpcsystem}/$args{rpcuser}\n");
257 } else {
258 my $netblock = new NetAddr::IP $args{block};
259 mailNotify($ip_dbh, "a$args{type}", "ADDED: $disp_alloctypes{$args{type}} allocation",
260 "$disp_alloctypes{$args{type}} $args{block} allocated to customer $args{custid}\n".
261 "Description: $args{desc}\n\nAllocated by: $args{rpcsystem}/$args{rpcuser}\n");
262 }
263 syslog "notice", "$args{rpcsystem}/$args{rpcuser} allocated '$args{block}' to '$args{custid}' as ".
264 "'$args{type}' ($msg)";
265 } else {
266 syslog "err", "Allocation of '$args{block}' to '$args{custid}' as ".
267 "'$args{type}' by $args{rpcsystem}/$args{rpcuser} failed: '$msg'";
268 die "$msg\n";
269 }
270 return $msg;
271} # rpc_allocateBlock()
272
273# another internal sub; mainly a sub to make allocateBlock() a lot smaller
274#sub rpc_initPool {}
275
276
277sub rpc_updateBlock {
278 my %args = @_;
279
280 _commoncheck(\%args, 'y');
281
282 _validateInput(\%args);
283
284 # Allow caller to send a CIDR instead of the block ID.
285 $args{origblock} = $args{block};
286 if ($args{block} =~ m{^(?:\d+\.){3}\d+(?:/32)?$}) {
287 # updating a static IP. retrieve the IP ID based on either the parent or VRF.
288 die "Pool ID or VRF is required\n" if !$args{parent} && !$args{vrf};
289 ($args{block}) = $ip_dbh->selectrow_array(
290 "SELECT id FROM poolips WHERE ip = ? AND ".($args{parent} ? "parent_id = ?" : "vrf = ?"),
291 undef, $args{block}, ($args{parent} ? $args{parent} : $args{vrf}) );
292 }
293
294 my $binfo = getBlockData($ip_dbh, $args{block}, $args{type});
295
296 # set assignIP_on_update to simplify some calling situations. better to use allocateBlock if possible though.
297 my ($code,$msg) = updateBlock($ip_dbh, %args, assignIP_on_update => 1);
298 if ($code eq 'FAIL') {
299 syslog "err", "$args{rpcsystem}/$args{rpcuser} could not update block/IP $args{block} ($binfo->{block}): '$msg'";
300 die "$msg\n";
301 }
302 syslog "notice", "$args{rpcsystem}/$args{rpcuser} updated $args{block} ($binfo->{block})";
303
304 return $msg;
305} # rpc_updateBlock()
306
307
308sub rpc_deleteBlock {
309 my %args = @_;
310
311 _commoncheck(\%args, 'y');
312
313 if (!$args{block}) {
314 $args{block} = $args{cidr} if $args{cidr};
315 die "Block/IP is required\n" if !$args{block};
316 }
317
318 if ($args{block} =~ m{^(?:\d+\.){3}\d+(?:/32)?$}) {
319 # deleting a static IP. retrieve the IP ID based on either the parent or VRF.
320 die "Pool ID or VRF is required\n" if !$args{parent} && !$args{vrf};
321 ($args{block}) = $ip_dbh->selectrow_array(
322 "SELECT id FROM poolips WHERE ip = ? AND ".($args{parent} ? "parent_id = ?" : "vrf = ?"),
323 undef, $args{block}, ($args{parent} ? $args{parent} : $args{vrf}) );
324 warn "passed block $args{block} guessed as $args{block}\n";
325 }
326
327 # snag block info for log
328 my $blockinfo = getBlockData($ip_dbh, $args{block}, $args{type});
329 my ($code,$msg) = deleteBlock($ip_dbh, $args{block}, $args{type}, $args{delfwd}, $args{rpcuser});
330
331 my $authuser = "$args{rpcsystem}/$args{rpcuser}";
332 if ($code eq 'OK' || $code =~ /^WARN/) {
333 syslog "notice", "$authuser deallocated '$blockinfo->{type}'-type netblock ID $args{block} ".
334 "($blockinfo->{block}), $blockinfo->{custid}, $blockinfo->{city}, desc='$blockinfo->{description}'";
335 mailNotify($ip_dbh, 'da', "REMOVED: $disp_alloctypes{$blockinfo->{type}} $blockinfo->{block}",
336# $args{block} useful? do we care about the block ID here?
337 "$disp_alloctypes{$blockinfo->{type}} $blockinfo->{block} deallocated by $authuser\n".
338 "CustID: $blockinfo->{custid}\nCity: $blockinfo->{city}\n".
339 "Description: $blockinfo->{description}\n");
340 } else {
341 if ($args{type} =~ /^.i$/) {
342 syslog "err", "$authuser could not deallocate static IP ID $args{block} ($blockinfo->{block}): '$msg'";
343 } else {
344 syslog "err", "$authuser could not deallocate netblock ID $args{block} ($blockinfo->{block}): '$msg'";
345 }
346 }
347
348 return $msg;
349} # rpc_deleteBlock()
350
351
352sub rpc_getBlockData {}
353sub rpc_getBlockRDNS {}
354sub rpc_getNodeList {}
355sub rpc_getNodeName {}
356sub rpc_getNodeInfo {}
357sub rpc_mailNotify {}
358
359
360##
361## Subs not part of the core IPDB
362##
363
364# Subs to send back IPDB globals
365
366#our %disp_alloctypes;
367sub rpc_getDispAlloctypes {
368 my %args = @_;
369 _commoncheck(\%args, 'n');
370 return \%disp_alloctypes;
371}
372
373#our %list_alloctypes;
374sub rpc_getListAlloctypes {
375 my %args = @_;
376 _commoncheck(\%args, 'n');
377 return \%list_alloctypes;
378}
379
380#our %def_custids;
381sub rpc_getDefCustIDs {
382 my %args = @_;
383 _commoncheck(\%args, 'n');
384 return \%def_custids;
385}
386
387#our @citylist;
388sub rpc_getCityList {
389 my %args = @_;
390 _commoncheck(\%args, 'n');
391 return \@citylist;
392}
393
394#our @poplist;
395sub rpc_getPOPList {
396 my %args = @_;
397 _commoncheck(\%args, 'n');
398 return \@poplist;
399}
400
401# not sure how useful it is to expose this on RPC
402#our %IPDBacl;
403sub rpc_getIPDBacl {
404 my %args = @_;
405 _commoncheck(\%args, 'n');
406 return \%IPDBacl;
407}
408
409# Operations not provided directly by the core IPDB
410
411# Get a list of available static IPs of the given type
412# Not a core IPDB sub since there's little use for this format.
413sub rpc_getAvailableStatics {
414 my %args = @_;
415
416 _commoncheck(\%args, 'n');
417
418 my ($base,undef) = split //, $args{type};
419 $base .= "_";
420 my @params = ($base);
421
422 my $sql = "SELECT poolips.id,poolips.ip,poolips.parent_id,poolips.pool ".
423 "FROM poolips JOIN allocations ON poolips.parent_id=allocations.id WHERE poolips.type LIKE ?";
424 if ($base ne 'd_' && $args{city}) {
425 $sql .= " AND allocations.city=?";
426 push @params, $args{city};
427 }
428 $sql .= " AND poolips.available='y'";
429
430 my $ret = $ip_dbh->selectall_arrayref($sql, { Slice => {} }, (@params) );
431 die $ip_dbh->errstr if !$ret;
432
433 return $ret;
434} # rpc_getAvailableStatics()
435
436
437sub rpc_getBackupList {
438 my %args = @_;
439
440 _commoncheck(\%args, 'n');
441
442 # grab the whole waffle.
443 my $sql = "SELECT backup_id, bkbrand, bkmodel, bktype, bkport, bksrc, bkuser, bkvpass, bkepass, ip FROM backuplist";
444 my $result = $ip_dbh->selectall_arrayref($sql, { Slice => {} });
445 die $ip_dbh->errstr if !$result;
446
447 return $result;
448} # rpc_getBackupList()
Note: See TracBrowser for help on using the repository browser.