1#!/usr/bin/perl
2
3use strict;
4use warnings;
5
6use Getopt::Long;
7
8use IO::Socket::INET;
9
10use GSSAPI;
11use MIME::Base64;
12
13my %opt;
14
15unless(GetOptions(\%opt, qw(prodid=s hostname=s port=s mutual))) {
16  print "$0 needs arguments, provide at least -prodid and -hostname, optionally -port (defauly 10000) or -mutual (for two sided authentication)\n";
17  exit(1);
18}
19
20if(! $opt{hostname}) {
21  die "$0: must specify -hostname\n";
22}
23
24if(! $opt{prodid}) {
25  die "$0: must specify -prodid\n";
26}
27
28if(! $opt{port}) {
29  warn "$0: -port not specified, defaulting to 10000\n";
30  $opt{port} = 10000;
31}
32
33if(! $opt{prodid}) {
34  $opt{prodid} = "host";
35}
36
37warn "$0: using [$opt{prodid}\@$opt{hostname}:$opt{port}]\n";
38
39
40#
41# GSSAPI::Name->import produces $gss_server_name
42# which is then passed in to GSSAPI::Context::init
43# $gss_server_name represents the principcal name
44# of the app server to which we are authenticating
45#
46
47my $server_name = "$opt{prodid}\@$opt{hostname}";
48my $status = GSSAPI::Name->import(my $gss_server_name, $server_name, gss_nt_service_name);
49$status || gss_exit("CLIENT::Unable to import server name: $server_name", $status);
50
51$status = $gss_server_name->display(my $display_name, my $type);
52print "CLIENT::principal [$server_name] means going to communicate with server name [$display_name]\n";
53
54my $gss_input_token = q{};
55
56my $socket = IO::Socket::INET->new
57  (
58   PeerAddr                 => $opt{hostname},
59   PeerPort                 => $opt{port},
60   Proto                    => 'tcp',
61   Type                     => SOCK_STREAM,
62  );
63
64die "socket/connect: $!\n" unless ($socket);
65
66#
67# The main purpose of GSSAPI::Context::init is to produce
68# an authentication token ($gss_output_token) which will
69# be sent by the app client to app server. Note the output
70# is binary data.
71#
72
73my $gss_auth_flags;
74if($opt{mutual}) {
75  $gss_auth_flags = GSS_C_MUTUAL_FLAG;
76} else {
77  $gss_auth_flags = 0;
78}
79
80my $client_context;
81
82my $counter = 0;
83my $error = 0;
84
85do {
86    $counter++;
87
88    $status = GSSAPI::Context::init($client_context,           # output context
89				    GSS_C_NO_CREDENTIAL,
90				    $gss_server_name,          # authenticate to this name
91				    GSS_C_NO_OID,              # use default mechanism (krb5)
92				    $gss_auth_flags,           # input flags
93				    0,                         # input time
94				    GSS_C_NO_CHANNEL_BINDINGS, # no channel binding
95				    $gss_input_token,          # input token
96				    my $out_mech,
97				    my $gss_output_token,
98				    my $out_flags,
99				    my $out_time);
100
101    $status || gss_exit("CLIENT::Unable to initialize security context", $status);
102
103    print "CLIENT::gss_init_sec_context success\n";
104
105    # The GSS protocol can do mutual authentication. If this is requested, the token
106    # that we generate in the first pass will indicate this to the server. The major
107    # status will have the GSS_S_CONTINUE_NEEDED bit set to indicate that we are
108    # expecting a reply with a server identity token. This loop will continue until
109    # that bit is no longer set. It should go through only once (non-mutual) or twice
110    # (mutual).
111
112    if ($counter == 1) {
113	print "CLIENT::going to identify client to server\n";
114    } elsif ($counter == 2) {
115	print "CLIENT::confirmed server identity from mutual token\n";
116        my $server_name;
117	$status = $gss_server_name->display($server_name);
118        $status || gss_exit("CLIENT::Unable to display server name", $status);
119        print "CLIENT::authenticated server name is $server_name\n" if $server_name;
120
121    } else {
122	print "CLIENT::iteration [$counter] successful, but should not be here\n";
123    }
124
125    if($gss_output_token) {
126	print "CLIENT::have token to send ...\n";
127	print "CLIENT::GSS token length is " . length($gss_output_token) . "\n";
128
129	#
130	# $gss_output_token is binary data
131	#
132
133	print $socket encode_base64($gss_output_token, '') . "\n";
134	print "CLIENT::sent token to server\n";
135    }
136
137    if ($status->major & GSS_S_CONTINUE_NEEDED) {
138	print "CLIENT::Mutual auth requested ...\n";
139	$gss_input_token = <$socket>;
140	if ($gss_input_token) {
141	    print "CLIENT::got mutual auth token from server\n";
142	    $gss_input_token = decode_base64($gss_input_token);
143	    print "CLIENT::mutual auth token length is " . length($gss_input_token) . "\n";
144	} else {
145	    print "CLIENT::server did not send needed continue token back\n";
146	    $error = 1;
147	}
148    }
149} while (!$error and $status->major & GSS_S_CONTINUE_NEEDED);
150
151$socket->shutdown(2);
152
153exit(0);
154
155################################################################################
156
157sub gss_exit {
158  my $errmsg = shift;
159  my $status = shift;
160
161  my @major_errors = $status->generic_message();
162  my @minor_errors = $status->specific_message();
163
164  print STDERR "$errmsg:\n";
165  foreach my $s (@major_errors) {
166    print STDERR "  MAJOR::$s\n";
167  }
168  foreach my $s (@minor_errors) {
169    print STDERR "  MINOR::$s\n";
170  }
171  exit(1);
172}
173
174