line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Citrix::LaunchMesg; |
2
|
|
|
|
|
|
|
#use strict; |
3
|
|
|
|
|
|
|
#use warnings; |
4
|
|
|
|
|
|
|
|
5
|
2
|
|
|
2
|
|
95530
|
use Storable ('dclone'); |
|
2
|
|
|
|
|
7893
|
|
|
2
|
|
|
|
|
4744
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.25'; |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
# TODO: |
9
|
|
|
|
|
|
|
# - Create a more precise description of what (keys) is in the session config sections |
10
|
|
|
|
|
|
|
# DONE: |
11
|
|
|
|
|
|
|
# - Now use accessors on Farm |
12
|
|
|
|
|
|
|
# - Generate message into a string, no direct output. |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 NAME |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
Citrix::LaunchMesg - Generate Citrix session launch messages in format understood by Citrix Desktop Clients. |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 DESCRIPTION |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Citrix::LaunchMesg Has methods for both initiating a totally new session and reconnecting |
21
|
|
|
|
|
|
|
to an existing session. Depends on Net::DNS to resolve server hostname to IP Address |
22
|
|
|
|
|
|
|
(convention used in Citrix launch messages). |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
For now please look into the file session_template.pl within module distro to learn about |
25
|
|
|
|
|
|
|
launch message sections used for constructing the launch message (by Citrix::LaunchMesg::new()). |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Citrix::LaunchMesg; |
30
|
|
|
|
|
|
|
# Get "all farms" configuration (as indexed hash) |
31
|
|
|
|
|
|
|
my $fms = Citrix::getfarms('idx' => 1); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# Pick Farm to launch session on |
34
|
|
|
|
|
|
|
my $fc = $fms->{'istanbul'}; |
35
|
|
|
|
|
|
|
# (Perl hash) default-valued Templates for launch message sections |
36
|
|
|
|
|
|
|
my %sections = ('client' => $client, 'app' => $app, ); |
37
|
|
|
|
|
|
|
my $clm = Citrix::LaunchMesg->new($fc, %sections); |
38
|
|
|
|
|
|
|
# Launch a new session (by Domain, Username, CitrixApp) |
39
|
|
|
|
|
|
|
my $err = $clm->setbalanced('hypertechno', 'joecitrix', 'DESKTOP-UNIX'); |
40
|
|
|
|
|
|
|
# Send "launch.ica" to web browser to be processed by wfcmgr Citrix desktop client app. |
41
|
|
|
|
|
|
|
# When set via HTTP in a web application Need to add respective http headers |
42
|
|
|
|
|
|
|
# within application. Use 'application/x-ica' to launch Citrix client helper app. |
43
|
|
|
|
|
|
|
print $clm->output(); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# ... Connect to existing session (after Citrix::LaunchMesg->new(...)) |
47
|
|
|
|
|
|
|
# You should do app level checks that this session actually belongs to user launching it. |
48
|
|
|
|
|
|
|
# However the Citrix authentication phase still prevents abuse. |
49
|
|
|
|
|
|
|
$clm->sethostappsess("good-old-host-22:3567"); |
50
|
|
|
|
|
|
|
print $clm->output(); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head1 METHODS |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=cut |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
our ($foo, $bar); |
60
|
|
|
|
|
|
|
# Keyword param Attributes of constructor for templates |
61
|
|
|
|
|
|
|
our @tattr = ('client','app',); |
62
|
|
|
|
|
|
|
# Translations for section names from runtime names to INI-section labels used in message |
63
|
|
|
|
|
|
|
our %sectheads = ('client' => 'WFClient', 'app' => '', 'enc' => 'Encoding', '' => '', ); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head2 my $clm = Citrix::LaunchMesg->new($farmctx, %opt); |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
Constructor for launch Message by Farm Context $farmctx, templates for various sections of |
68
|
|
|
|
|
|
|
Citrix Launch message. This may later serve for launching a truly new session or connecting to |
69
|
|
|
|
|
|
|
existing one. Options (%opt) are: |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=over 4 |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=item client - Client Config section |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=item app - Application Config section |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=item inputenc - Input Encoding (optional, default: 'InputEncoding' => 'ISO8859_1') |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=back |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
For an example / quick reference on above section see file 'session_template.pl' in source distribution. |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=cut |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub new { |
86
|
0
|
|
|
0
|
1
|
|
my ($class, $fc, %c) = @_; |
87
|
0
|
|
|
|
|
|
my $lm = { |
88
|
|
|
|
|
|
|
'fc' => $fc, |
89
|
|
|
|
|
|
|
'enc' => {'InputEncoding' => 'ISO8859_1',}, |
90
|
|
|
|
|
|
|
'appx' => {}, |
91
|
|
|
|
|
|
|
'appserv' => {$c{'appserv'} => '',}, |
92
|
|
|
|
|
|
|
}; |
93
|
0
|
0
|
|
|
|
|
$fc || die("Farm Missing"); |
94
|
|
|
|
|
|
|
# Validate templates passid in %c to be hashes. Also test contents ? |
95
|
0
|
0
|
|
|
|
|
for (@tattr) {(ref($c{$_}) eq 'HASH') || die("No Template for $_ passed");} |
|
0
|
|
|
|
|
|
|
96
|
0
|
|
|
|
|
|
bless($lm, $class); |
97
|
|
|
|
|
|
|
# Grab copies of templates for instance specific tweaks |
98
|
0
|
|
|
|
|
|
@$lm{@tattr} = map({dclone($c{$_});} @tattr); |
|
0
|
|
|
|
|
|
|
99
|
0
|
0
|
|
|
|
|
if ($c{'inputenc'}) {$lm->{'enc'}->{'InputEncoding'} = $c{'inputenc'};} |
|
0
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
return($lm); |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
#sub ctxlaunch {my ($ctxt, $t_c, $t_as, $t_a, $capp) = @_;} |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=head2 $clm->setbalanced($dom, $uid, $capp); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Initialize message state for launching a new load-balanced session for user $uid in Citrix |
107
|
|
|
|
|
|
|
domain ($dom) by application name ($capp). |
108
|
|
|
|
|
|
|
Domain string usually looks like Windows domain name (e.g. company name without spaces). |
109
|
|
|
|
|
|
|
The launch message already contain Citrix Farm context (so does not need to be passed in here). |
110
|
|
|
|
|
|
|
Use output() later to generate the actual message. |
111
|
|
|
|
|
|
|
Returns 0 for success |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=cut |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub setbalanced { |
116
|
0
|
|
|
0
|
1
|
|
my ($lm, $dom, $uid, $capp) = @_; |
117
|
0
|
|
|
|
|
|
my $fc = $lm->{'fc'}; |
118
|
0
|
|
|
|
|
|
my $errstr; |
119
|
0
|
0
|
|
|
|
|
if (!$dom) {$errstr = "No Domain for new session"; goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
120
|
0
|
0
|
|
|
|
|
if (!$uid) {$errstr = "No Username for new session"; goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
121
|
0
|
0
|
|
|
|
|
if (!$capp) {$errstr = "No Application for new session"; goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
|
my $t_c = $lm->{'client'}; |
123
|
0
|
|
|
|
|
|
my $t_a = $lm->{'app'}; |
124
|
0
|
|
|
|
|
|
$t_c->{'ClientName'} = "$dom-$uid"; |
125
|
0
|
|
|
|
|
|
$t_a->{'Address'} = $capp; |
126
|
|
|
|
|
|
|
# Nest Browser info Into WFClient sect of launch message (looked up masterhost from Farminfo) |
127
|
0
|
|
|
|
|
|
my $mh = $fc->masterhost(); #OLD:{'mh'} |
128
|
0
|
|
|
|
|
|
my $ds = $fc->domainsuffix(); # #OLD: {'ds'} |
129
|
|
|
|
|
|
|
# Need to have masterhost address fully qualified for bulletproof function under all DNS / nameres. conditions |
130
|
|
|
|
|
|
|
# For now fix anything that contains dost to be combo of first (hostname) part and complete domain |
131
|
|
|
|
|
|
|
# This can be left in without hurting functionality |
132
|
0
|
0
|
|
|
|
|
if ($mh =~ /^([\w\-]+)\./) {$mh = "$1.$ds";} |
|
0
|
|
|
|
|
|
|
133
|
0
|
|
|
|
|
|
$t_c->{'TcpBrowserAddress'} = $mh; |
134
|
0
|
|
|
|
|
|
$t_c->{'HttpBrowserAddress'} = "$mh:8080"; |
135
|
|
|
|
|
|
|
# Fill in proper app (repeated) |
136
|
0
|
|
|
|
|
|
$t_a->{'InitialProgram'} = "#$capp"; |
137
|
0
|
|
|
|
|
|
$lm->{'appid'} = $capp; |
138
|
0
|
|
|
|
|
|
return(0); |
139
|
0
|
|
|
|
|
|
ERROR: |
140
|
|
|
|
|
|
|
$lm->{'errstr'} = $errstr; |
141
|
0
|
|
|
|
|
|
return(1); |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
# # 'cdom'... 'appid' ... 'userid' |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head2 $clm->sethostappsess($hostsess); |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
Initialize message state for connecting to an existing session by passing host / session ID info |
148
|
|
|
|
|
|
|
in $hostsess. $hostsess should be given in Citrix native notation "$host:$sessid". |
149
|
|
|
|
|
|
|
Queries Citrix Application ID live from current farm, since this is required in message. |
150
|
|
|
|
|
|
|
Use output() to generate the actual message |
151
|
|
|
|
|
|
|
Returns 0 for success |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=cut |
154
|
|
|
|
|
|
|
sub sethostappsess { |
155
|
0
|
|
|
0
|
1
|
|
my ($lm, $hostsess) = @_; # NONEED: , $app |
156
|
0
|
|
|
|
|
|
my $errstr; |
157
|
0
|
|
|
|
|
|
my $t_c = $lm->{'client'}; |
158
|
0
|
|
|
|
|
|
my $t_a = $lm->{'app'}; |
159
|
0
|
|
|
|
|
|
my $fc = $lm->{'fc'}; |
160
|
0
|
|
|
|
|
|
my ($host, $sid) = split(/:/, $hostsess); |
161
|
0
|
0
|
0
|
|
|
|
if (!$host || !$sid) {$errstr = "No Host or session passed"; goto ERROR;} # .Dumper($cgi) |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# We need Farm Context here to resolve host with full domain |
163
|
0
|
|
|
|
|
|
my $abshost = "$host.".$fc->domainsuffix(); # OLD:{'ds'} |
164
|
0
|
|
|
|
|
|
my @addr = dnsresolve($abshost); # SUPEROLD: $fc->{'ds'} |
165
|
0
|
0
|
|
|
|
|
if (!@addr) {$errstr = "No Result for host ($abshost) search";goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
166
|
0
|
0
|
|
|
|
|
if (@addr > 1) {$errstr = "Host '$abshost' Resolved to multiple addresses";goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
167
|
0
|
0
|
|
|
|
|
if (!$addr[0]) {$errstr = "Not even single IP found for '$abshost'";goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
168
|
0
|
|
|
|
|
|
$t_a->{'Address'} = $addr[0]; |
169
|
|
|
|
|
|
|
# Need to fill in InitialProgram ? |
170
|
0
|
|
|
|
|
|
my $ss = Citrix::SessionSet->new($fc); |
171
|
0
|
|
|
|
|
|
$ss->gethostsess($host); |
172
|
0
|
|
|
|
|
|
my $sess = $ss->getsessbyid($hostsess); |
173
|
0
|
0
|
|
|
|
|
if (!$sess) {$errstr = "No Session by '$hostsess'";goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# NOW Resolve $capp by session |
175
|
0
|
|
|
|
|
|
my $capp = $sess->{'APPID'}; |
176
|
0
|
0
|
|
|
|
|
if (!$capp) {$errstr = "No Application Found for $hostsess";goto ERROR;} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
#OLD:$t_as = {$capp, ''}; |
178
|
0
|
|
|
|
|
|
$lm->{'appserv'} = {$capp, ''}; |
179
|
0
|
|
|
|
|
|
$t_a->{'InitialProgram'} = "#$capp"; |
180
|
0
|
|
|
|
|
|
$lm->{'appid'} = $capp; |
181
|
0
|
|
|
|
|
|
return(0); |
182
|
0
|
|
|
|
|
|
ERROR: |
183
|
|
|
|
|
|
|
$lm->{'errstr'} = $errstr; |
184
|
0
|
|
|
|
|
|
return(1); |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
=head2 $clm->output(); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
Generate, format and output the 4 sections of a Citrix launch message. |
189
|
|
|
|
|
|
|
The sections internally accessed are 'client' (Citrix Client), 'app' (Citrix Application), |
190
|
|
|
|
|
|
|
'appserv' (Citrix Application Server host). |
191
|
|
|
|
|
|
|
Return none |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=cut |
194
|
|
|
|
|
|
|
sub output { |
195
|
0
|
|
|
0
|
0
|
|
my ($lm) =@_; |
196
|
|
|
|
|
|
|
# Possibly encapsulate this to citrix launcher |
197
|
|
|
|
|
|
|
#inisect($ctxt::enc, "Encoding"); |
198
|
|
|
|
|
|
|
#inisect($t_c, "WFClient"); |
199
|
|
|
|
|
|
|
#inisect($t_as, "ApplicationServers"); |
200
|
|
|
|
|
|
|
#inisect($t_a, $capp); |
201
|
0
|
|
|
|
|
|
my $t_c = $lm->{'client'}; |
202
|
0
|
|
|
|
|
|
my $t_a = $lm->{'app'}; |
203
|
0
|
|
|
|
|
|
my $t_as = $lm->{'appserv'}; |
204
|
|
|
|
|
|
|
############################### |
205
|
0
|
|
|
|
|
|
my $OUT = ''; |
206
|
0
|
|
|
|
|
|
$OUT .= inisect($lm->{'enc'}, "Encoding"); |
207
|
0
|
|
|
|
|
|
$OUT .= inisect($t_c, "WFClient"); |
208
|
0
|
|
|
|
|
|
$OUT .= inisect($t_as, "ApplicationServers"); |
209
|
|
|
|
|
|
|
#????:inisect($t_a, $capp); |
210
|
0
|
|
|
|
|
|
$OUT .= inisect($t_a, $lm->{'appid'}); |
211
|
|
|
|
|
|
|
#OLD:print($OUT); |
212
|
0
|
|
|
|
|
|
return($OUT); |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
# ???? |
216
|
|
|
|
|
|
|
#sub initas { |
217
|
|
|
|
|
|
|
#} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# Internal method for Generic INI-section creation. |
220
|
|
|
|
|
|
|
# Create section with name $n (in [...]) followed by key-value pairs from %{$rn->{$n}} or directly from %$rn |
221
|
|
|
|
|
|
|
sub inisect { |
222
|
0
|
|
|
0
|
0
|
|
my ($rn, $n) = @_; |
223
|
|
|
|
|
|
|
# Try looking up sub-node, fallback on node itself |
224
|
0
|
0
|
|
|
|
|
my $h = $rn->{$n} ? $rn->{$n} : $rn; |
225
|
0
|
|
|
|
|
|
my $OUT = "[$n]\r\n"; |
226
|
0
|
|
|
|
|
|
$OUT .= join('', map({"$_=$h->{$_}\r\n"} sort(keys(%$h))), "\r\n"); |
|
0
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
#OLD:print($OUT); |
228
|
0
|
|
|
|
|
|
return($OUT); |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
# Internal method to find out IP address for a host by name. |
232
|
|
|
|
|
|
|
# Could use Citrix-based resolution to make more independent from (non-core) Perl Modules |
233
|
|
|
|
|
|
|
sub dnsresolve { |
234
|
0
|
|
|
0
|
0
|
|
my ($host, $dom) = @_; |
235
|
0
|
|
|
|
|
|
my @addr = (); |
236
|
0
|
|
|
|
|
|
require Net::DNS; |
237
|
0
|
|
|
|
|
|
my $resv = Net::DNS::Resolver->new(); |
238
|
0
|
0
|
|
|
|
|
my $usehost = $dom ? "$host.$dom" : $host; |
239
|
0
|
|
|
|
|
|
my $query = $resv->search($usehost); |
240
|
0
|
0
|
|
|
|
|
if (!$query) {return(undef);} |
|
0
|
|
|
|
|
|
|
241
|
0
|
|
|
|
|
|
for my $rr ($query->answer()) { |
242
|
0
|
0
|
|
|
|
|
if ($rr->type eq "A") {push(@addr, $rr->address());} |
|
0
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
} |
244
|
0
|
|
|
|
|
|
return(@addr); |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
1; |