File Coverage

blib/lib/Citrix/LaunchMesg.pm
Criterion Covered Total %
statement 3 106 2.8
branch 0 34 0.0
condition 0 3 0.0
subroutine 1 7 14.2
pod 3 6 50.0
total 7 156 4.4


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;