source: trunk/cgi-bin/search-rpc.cgi@ 905

Last change on this file since 905 was 905, checked in by Kris Deugau, 7 years ago

/trunk

Extend RPC search to cover CIDR cases

  • Property svn:executable set to *
File size: 6.4 KB
Line 
1#!/usr/bin/perl
2# XMLRPC interface to IPDB search
3# Copyright (C) 2017 Kris Deugau <kdeugau@deepnet.cx>
4
5use strict;
6use warnings;
7
8use DBI;
9use CustIDCK;
10use NetAddr::IP;
11use FCGI;
12use Frontier::Responder;
13
14use Sys::Syslog;
15
16# don't remove! required for GNU/FHS-ish install from tarball
17##uselib##
18
19use MyIPDB;
20
21openlog "IPDB-search-rpc","pid","$IPDB::syslog_facility";
22
23##fixme: username source? can we leverage some other auth method?
24# we don't care except for logging here, and Frontier::Client needs
25# a patch that's not well-distributed to use HTTP AUTH.
26
27# Collect the username from HTTP auth. If undefined, we're in
28# a test environment, or called without a username.
29my $authuser;
30if (!defined($ENV{'REMOTE_USER'})) {
31 $authuser = '__temptest';
32} else {
33 $authuser = $ENV{'REMOTE_USER'};
34}
35
36# Why not a global DB handle? (And a global statement handle, as well...)
37# Use the connectDB function, otherwise we end up confusing ourselves
38my $ip_dbh;
39my $sth;
40my $errstr;
41($ip_dbh,$errstr) = connectDB_My;
42initIPDBGlobals($ip_dbh);
43
44my $methods = {
45 'ipdb.search' => \&rpc_search,
46};
47
48my $reqcnt = 0;
49
50my $req = FCGI::Request();
51
52# main FCGI loop.
53while ($req->Accept() >= 0) {
54 # done here to a) prevent $ENV{'REMOTE_ADDR'} from being empty and b) to collect
55 # the right user for the individual call (since we may be running with FCGI)
56 syslog "debug", "$authuser active, $ENV{'REMOTE_ADDR'}";
57
58 # don't *think* we need any of these...
59 # %disp_alloctypes, %def_custids, %list_alloctypes
60 # @citylist, @poplist
61 # @masterblocks, %allocated, %free, %bigfree, %routed (removed in /trunk)
62 # %IPDBacl
63 #initIPDBGlobals($ip_dbh);
64
65 my $res = Frontier::Responder->new(
66 methods => $methods
67 );
68
69 # "Can't do that" errors
70 if (!$ip_dbh) {
71 print "Content-type: text/xml\n\n".$res->{_decode}->encode_fault(5, $DBI::errstr);
72 } else {
73 print $res->answer;
74 }
75 last if $reqcnt++ > $IPDB::maxfcgi;
76} # while FCGI::accept
77
78exit 0;
79
80
81##
82## Private subs
83##
84
85# Check RPC ACL
86sub _aclcheck {
87 my $subsys = shift;
88 return 1 if grep /$ENV{REMOTE_ADDR}/, @{$IPDB::rpcacl{$subsys}};
89 warn "$subsys/$ENV{REMOTE_ADDR} not in ACL\n"; # a bit of logging
90 return 0;
91}
92
93sub _commoncheck {
94 my $argref = shift;
95 my $needslog = shift;
96
97 die "Missing remote system name\n" if !$argref->{rpcsystem};
98 die "Access denied\n" if !_aclcheck($argref->{rpcsystem});
99 if ($needslog) {
100 die "Missing remote username\n" if !$argref->{rpcuser};
101 }
102}
103
104# stripped-down copy from from main.cgi. should probably be moved to IPDB.pm
105sub _validateInput {
106 my $argref = shift;
107
108 if (!$argref->{block}) {
109 $argref->{block} = $argref->{cidr} if $argref->{cidr};
110 die "Block/IP is required\n" if !$argref->{block};
111 }
112
113 # Alloctype check.
114 chomp $argref->{type};
115
116 die "Invalid allocation type\n" if (!grep /$argref->{type}/, keys %disp_alloctypes);
117
118 # Arguably not quite correct, as the custID won't be checked for
119 # validity if there's a default on the type.
120 if ($def_custids{$argref->{type}} eq '') {
121 # Types without a default custID must have one passed in
122 die "Customer ID is required\n" if !$argref->{custid};
123 # Crosscheck with billing.
124 my $status = CustIDCK->custid_exist($argref->{custid});
125 die "Error verifying customer ID: $CustIDCK::ErrMsg\n" if $CustIDCK::Error;
126 die "Customer ID not valid\n" if !$status;
127 } else {
128 # Types that have a default will use it unless one is specified.
129 if ((!$argref->{custid}) || ($argref->{custid} ne 'STAFF')) {
130 $argref->{custid} = $def_custids{$argref->{type}};
131 }
132 }
133} # end validateInput()
134
135
136##
137## RPC method subs
138##
139
140sub rpc_search {
141 my %args = @_;
142
143 _commoncheck(\%args, 'n');
144
145 my @fields;
146 my @vals;
147 my @matchtypes;
148
149 my %mt = (EXACT => '=',
150 EQUAL => '=',
151 NOT => '!~',
152 # CIDR options
153 MASK => 'MASK',
154 WITHIN => '<<=',
155 CONTAINS => '>>=',
156 );
157
158 if ($args{type}) {
159 push @fields, 's.type';
160 push @vals, $args{type};
161 push @matchtypes, '=';
162 }
163
164 ## CIDR query options.
165 if ($args{cidr}) {
166 $args{cidr} =~ s/^\s*(.+)\s*$/$1/g;
167 # strip matching type substring, if any - only applies to full-CIDR
168 my ($mnote) = $args{cidr} =~ /^(\w+):/;
169 $args{cidr} =~ s/^$mnote:// if $mnote;
170
171 if ($args{cidr} eq '') { # We has a blank CIDR. Ignore it.
172 } elsif ($args{cidr} =~ /\//) {
173 my ($net,$maskbits) = split /\//, $args{cidr};
174 if ($args{cidr} =~ /^(\d{1,3}\.){3}\d{1,3}\/\d{2}$/) {
175 # Full CIDR match.
176 push @fields, 's.cidr';
177 push @vals, $args{cidr};
178 if ($mnote =~ /(EQUAL|EXACT|CONTAINS|WITHIN)/) {
179 push @matchtypes, $mt{$1};
180 } else { # default to exact match
181 push @matchtypes, '=';
182 }
183 } elsif ($args{cidr} =~ /^(\d{1,3}\.){2}\d{1,3}\/\d{2}$/) {
184 # Partial match; beginning of subnet and maskbits are provided
185 # Show any blocks with the leading octet(s) and that masklength
186 # eg 192.168.179/26 should show all /26 subnets in 192.168.179
187 # Need some more magic for bare /nn searches:
188 push @fields, 's.cidr','masklen(s.cidr)';
189 push @vals, "$net.0/24", $maskbits;
190 push @matchtypes, '<<=','=';
191 }
192 } elsif ($args{cidr} =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
193 # Specific IP address match. Will show the parent chain down to the final allocation.
194 push @fields, 's.cidr';
195 push @vals, $args{cidr};
196 push @matchtypes, '>>=';
197 } elsif ($args{cidr} =~ /^\d{1,3}(\.(\d{1,3}(\.(\d{1,3}\.?)?)?)?)?$/) {
198 # 1, 2, or 3 leading octets in CIDR
199 push @fields, 'text(s.cidr)';
200 push @vals, "$args{cidr}\%";
201 push @matchtypes, 'LIKE'; # hmm
202 } else {
203 # do nothing.
204 ##fixme we'll ignore this to clear out the references to legacy code.
205 } # done with CIDR query options.
206
207 } # args{cidr}
208
209 foreach (qw(custid description notes city) ) {
210 if ($args{$_}) {
211 push @fields, "s.$_";
212 if ($args{$_} =~ /^(EXACT|NOT):/) {
213 push @matchtypes, $mt{$1};
214 $args{$_} =~ s/^$1://;
215 } else {
216 push @matchtypes, '~*';
217 }
218 push @vals, $args{$_};
219 }
220 }
221
222 my $sql = q(SELECT s.cidr,s.custid,s.type,s.description,s.city,a.dispname FROM searchme s JOIN alloctypes a ON s.type = a.type);
223 my @sqlcriteria;
224 for (my $i = 0; $i <= $#fields; $i++) {
225 push @sqlcriteria, "$fields[$i] $matchtypes[$i] ?";
226 }
227 $sql .= " WHERE ".join(' AND ', @sqlcriteria) if @sqlcriteria;
228
229 my $result = $ip_dbh->selectall_arrayref($sql, {Slice=>{}}, @vals);
230 die $ip_dbh->errstr if !$result;
231
232 return $result;
233} # rpc_search()
Note: See TracBrowser for help on using the repository browser.