client.pm 8.62 KB
Newer Older
1
# Radius client
2
# Copyright (C) 2007-2019, AllWorldIT
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.



package smradius::client;


use strict;
use warnings;

26
27
28
use base qw(AWITPT::Object);

use Getopt::Long qw( GetOptionsFromArray );
29
30
31
32
use IO::Select;
use IO::Socket;

use smradius::version;
Nigel Kukard's avatar
Nigel Kukard committed
33
use smradius::Radius::Packet;
34
35
36
37
38
39
40
41
42

# Check Config::IniFiles is instaslled
if (!eval {require Config::IniFiles; 1;}) {
	print STDERR "You're missing Config::IniFiles, try 'apt-get install libconfig-inifiles-perl'\n";
	exit 1;
}



43
# Run the client
44
45
sub run
{
46
	my ($self,@methodArgs) = @_;
47
48


49
50
	# Instantiate if we're not already instantiated
	$self = $self->new() if (!ref($self));
51

52
53
	# The hash we're going to return
	my $ret = { };
54

55
	print(STDERR "SMRadClient v".VERSION." - Copyright (c) 2007-2019, AllWorldIT\n");
56

57
58
	print(STDERR "\n");

59
60
61
62
	# Set defaults
	my $cfg;
	$cfg->{'config_file'} = "/etc/smradiusd.conf";

63
64
	# Grab runtime arguments
	my @runArgs = @methodArgs ? @methodArgs : @ARGV;
65
66
67
68

	# Parse command line params
	my $cmdline;
	%{$cmdline} = ();
69
70
	if (!GetOptionsFromArray(
		\@runArgs,
71
72
73
		\%{$cmdline},
		"config:s",
		"raddb:s",
74
		"listen:s",
75
		"help",
76
	)) {
Nigel Kukard's avatar
Nigel Kukard committed
77
78
		print(STDERR "ERROR: Error parsing commandline arguments");
		return 1;
79
	}
80
81
82
83

	# Check for some args
	if ($cmdline->{'help'}) {
		displayHelp();
84
		return 0;
85
86
87
	}

	# Make sure we only have 2 additional args
88
89
	if (@runArgs < 3) {
		print(STDERR "ERROR: Invalid number of arguments\n");
90
		displayHelp();
91
		return 1;
92
93
94
	}

	if (!defined($cmdline->{'raddb'}) || $cmdline->{'raddb'} eq "") {
95
		print(STDERR "ERROR: No raddb directory specified!\n");
96
		displayHelp();
97
		return 1;
98
99
100
	}

	# Get variables we need
101
102
103
	my $server = shift(@runArgs);
	my $type = shift(@runArgs);
	$self->{'secret'} = shift(@runArgs);
104
105

	# Validate type
106
107
	if (!defined($type) || ( $type ne "acct" && $type ne "auth" && $type ne "disconnect")) {
		print(STDERR "ERROR: Invalid packet type specified!\n");
108
		displayHelp();
109
		return 1;
110
111
112
113
114
115
116
117
	}


	print(STDERR "\n");


	# Time to start loading the dictionary
	print(STDERR "Loading dictionaries...");
Nigel Kukard's avatar
Nigel Kukard committed
118
	my $raddb = smradius::Radius::Dictionary->new();
119
120

	# Look for files in the dir
121
122
123
124
125
	my $DIR;
	if (!opendir($DIR, $cmdline->{'raddb'})) {
		print(STDERR "ERROR: Cannot open '".$cmdline->{'raddb'}."': $!");
		return 1;
	}
126
127
128
129
	my @raddb_files = readdir($DIR);

	# And load the dictionary
	foreach my $df (@raddb_files) {
130
131
132
133
134
135
		my $df_fn = $cmdline->{'raddb'}."/$df";
		# Load dictionary
		if (!$raddb->readfile($df_fn)) {
			print(STDERR "Failed to load dictionary '$df_fn': $!");
		}
		print(STDERR ".");
136
137
138
139
140
141
142
	}
	print(STDERR "\n");

	# Decide what type of packet this is
	my $port;
	my $pkt_code;
	if ($type eq "acct") {
143
144
		$port = 1813;
		$pkt_code = "Accounting-Request";
145
	} elsif ($type eq "auth") {
146
147
		$port = 1812;
		$pkt_code = "Access-Request";
148
	} elsif ($type eq "disconnect") {
149
150
		$port = 1813;
		$pkt_code = "Disconnect-Request";
151
152
153
154
	}


	print(STDERR "\nRequest:\n");
155
	printf(STDERR " > Secret => '%s'\n",$self->{'secret'});
156
	# Build packet
157
158
	$self->{'packet'} = smradius::Radius::Packet->new($raddb);
	$self->{'packet'}->set_code($pkt_code);
159
160
	# Generate identifier
	my $ident = int(rand(32768));
161
	$self->{'packet'}->set_identifier($ident);
162
163
164
	print(STDERR " > Identifier: $ident\n");
	# Generate authenticator number
	my $authen = int(rand(32768));
165
	$self->{'packet'}->set_authenticator($authen);
166
167
	print(STDERR " > Authenticator: $ident\n");

168
169
170
171
172
	# Pull in attributes from STDIN if we're not being called as a function
	if (!@runArgs) {
		while (my $line = <STDIN>) {
			$self->addAttributesFromString($line);
		}
173
	}
174
175
176
177

	# Pull in attributes from commandline
	while (my $line = shift(@runArgs)) {
		$self->addAttributesFromString($line);
178
179
180
	}

	# Create UDP packet
181
	my $udp_packet = $self->{'packet'}->pack();
182
183
184
185
186
187
188
189

	# Create socket to send packet out on
	my $sockTimeout = "10";  # 10 second timeout
	my $sock = IO::Socket::INET->new(
		PeerAddr => $server,
		PeerPort => $port,
		Type => SOCK_DGRAM,
		Proto => 'udp',
190
		Timeout => $sockTimeout,
191
192
193
	);

	if (!$sock) {
194
195
		print(STDERR "ERROR: Failed to create socket\n");
		return 1;
196
197
	}

198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
	my $sock2;
	# Check if we must listen on another IP/port
	if (defined($cmdline->{'listen'}) && $cmdline->{'listen'} ne "") {
		print(STDERR "Creating second socket\n");

		# Check the details we were provided
		my ($localAddr,$localPort) = split(/:/,$cmdline->{'listen'});
		if (!defined($localPort)) {
			print(STDERR "ERROR: The format for --listen is IP:Port\n");
			return 1;
		}

		$sock2 = IO::Socket::INET->new(
			LocalAddr => $localAddr,
			LocalPort => $localPort,
			Type => SOCK_DGRAM,
			Proto => 'udp',
			Timeout => $sockTimeout,
		);

		if (!$sock2) {
			print(STDERR "ERROR: Failed to create second socket\n");
			return 1;
		}
	}

224
225
	# Check if we sent the packet...
	if (!$sock->send($udp_packet)) {
226
227
		print(STDERR "ERROR: Failed to send data on socket\n");
		return 1;
228
229
230
231
232
233
234
235
	}

	# And time for the response
	print(STDERR "\nResponse:\n");

	# Once sent, we need to get a response back
	my $rsock = IO::Select->new($sock);
	if (!$rsock) {
236
237
		print(STDERR "ERROR: Failed to select response data on socket\n");
		return 1;
238
239
240
241
	}

	# Check if we can read a response after the select()
	if (!$rsock->can_read($sockTimeout)) {
242
243
		print(STDERR "ERROR: Failed to receive response data on socket\n");
		return 1;
244
245
246
247
248
	}

	# Read packet
	$sock->recv($udp_packet, 65536);
	if (!$udp_packet) {
249
		print(STDERR "ERROR: Receive response data failed on socket: $!\n");
250
		return 1;
251
252
253
	}

	# Parse packet
254
255
	my $pkt = smradius::Radius::Packet->new($raddb,$udp_packet);
	print(STDERR " > Authenticated: ". (defined(auth_req_verify($udp_packet,$self->{'secret'},$authen)) ? "yes" : "no") ."\n");
256
257
	print(STDERR $pkt->str_dump());

258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
	# Setup response
	$ret->{'request'} = $self->hashedPacket($self->{'packet'});
	$ret->{'response'} = $self->hashedPacket($pkt);


	my $udp_packet2;
	if (defined($sock2)) {
		my $rsock2 = IO::Select->new($sock2);
		if (!$rsock2) {
			print(STDERR "ERROR: Failed to select response data on socket2\n");
			return 1;
		}

		# Check if we can read a response after the select()
		if (!$rsock2->can_read($sockTimeout)) {
			print(STDERR "ERROR: Failed to receive response data on socket2\n");
			return 1;
		}

		# Read packet
		my $udp_packet2;
		$sock2->recv($udp_packet2, 65536);
		if (!$udp_packet2) {
			print(STDERR "ERROR: Receive response data failed on socket2: $!\n");
			return 1;
		}

		my $pkt2 = smradius::Radius::Packet->new($raddb,$udp_packet2);
		print(STDERR $pkt2->str_dump());

		# Save the packet we got
		$ret->{'listen'}->{'response'} = $self->hashedPacket($pkt2);
	}

292

293
	# If we were called as a function, return hashed version of the response packet
294
	if (@methodArgs) {
295
		return $ret;
296
297
298
299
300
301
302
303
	}

	return 0;
}




304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# Return a hashed version of the packet
sub hashedPacket
{
	my ($self,$pkt) = @_;


	my $res = {};


	$res->{'code'} = $pkt->code();
	$res->{'identifier'} = $pkt->identifier();

	foreach my $attrName (sort $pkt->attributes()) {
		my $attrVal = $pkt->rawattr($attrName);
		$res->{'attributes'}->{$attrName} = $attrVal;
	}

	foreach my $attrVendor ($pkt->vendors()) {
		foreach my $attrName ($pkt->vsattributes($attrVendor)) {
			$res->{'vattributes'}->{$attrVendor}->{$attrName} = $pkt->vsattr($attrVendor,$attrName);
		}
Nigel Kukard's avatar
Nigel Kukard committed
325
	}
326
327
328
329
330
331

	return $res;
}



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# Allow adding attribute from a string
sub addAttributesFromString
{
	my ($self,$line) = @_;


	# Remove EOL
	chomp($line);
	# Split on , and newline
	my @rawAttributes = split(/[,\n]+/,$line);
	foreach my $attr (@rawAttributes) {
		# Pull off attribute name & value
		my ($name,$value) = ($attr =~ /\s*(\S+)\s*=\s?(.+)/);
		$self->addAttribute($name,$value);
	}

	return;
}



# Add attribute to packet
sub addAttribute
{
	my ($self,$name,$value) = @_;


	# Add to packet
	print(STDERR " > Adding '$name' => '$value'\n");
	if ($name eq "User-Password") {
362
		$self->{'packet'}->set_password($value,$self->{'secret'});
363
	} else {
364
		$self->{'packet'}->set_attr($name,$value);
365
366
	}

367
368
369
370
371
372
373
374
375
	return;
}



# Display help
sub displayHelp {
	print(STDERR<<EOF);

376
377
Usage: $0 [args] <server> <acct|auth|disconnect> <secret> [ATTR=VALUE,...]
    --raddb=<DIR>          Directory where the radius dictionary files are
378
379
380
381
382
383
384
385
386

EOF

	return;
}


1;
# vim: ts=4