File Coverage

File:/home/mik/work/module/Tivoli/AccessManager/Admin/Group.pm
Coverage:99.6%

linestmtbrancondsubpodtimecode
1package Tivoli::AccessManager::Admin::Group;
2
15
15
15
260
54
428
use strict;
3
15
15
15
198
58
211
use warnings;
4
15
15
15
186
51
279
use Carp;
5
6#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
7# $Id: Group.pm 329 2006-11-20 19:01:01Z mik $
8#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
9$Tivoli::AccessManager::Admin::Group::VERSION = '0.04';
10
15
205
use Inline( C => 'DATA',
11                INC => '-I/opt/PolicyDirector/include',
12                LIBS => ' -lpthread -lpdadminapi -lstdc++',
13                CCFLAGS => '-Wall',
14# VERSION => '0.04',
15                NAME => 'Tivoli::AccessManager::Admin::Group',
16
15
15
181
45
                );
17
15
15
15
192
55
230
use Tivoli::AccessManager::Admin::Response;
18
19sub new {
20
48
1
411
    my $class = shift;
21
48
4062
    my $cont = shift;
22
48
530
    my $resp = Tivoli::AccessManager::Admin::Response->new();
23
24
48
1025
    unless (defined($cont) and UNIVERSAL::isa($cont,'Tivoli::AccessManager::Admin::Context')) {
25
2
58
        warn "Incorrect syntax -- did you forget the context?\n";
26
2
18
        return undef;
27    }
28
29
46
707
    if (@_ % 2) {
30
3
92
        warn "Invalid syntax -- you did not send a hash\n";
31
3
31
        return undef;
32    }
33
43
458
    my %opts = @_;
34
35
43
344
    my $self = bless {}, $class;
36
37
43
533
    $self->{name} = $opts{name} || '';
38
43
477
    $self->{cn} = $opts{cn} || '';
39
43
368
    $self->{dn} = $opts{dn} || '';
40
43
206
    $self->{exist} = 0;
41
43
501
    $self->_groupstore();
42
43
247
    $self->{context} = $cont;
43
44
43
276
    if ($self->{dn}) {
45
19
917731
        my $rc = $self->group_getbydn($resp);
46
47        # The group already exists
48
19
530
        if ($resp->isok()) {
49
3
78
            $self->{dn} = $self->group_getdn();
50
3
55
            $self->{cn} = $self->group_getcn();
51
3
50
            $self->{name} = $self->group_getname();
52
3
158100
            $self->{exist} = $self->group_get($resp);
53        }
54    }
55
56
43
706
    if ($self->{name} and not $self->{exist}) {
57
36
2010087
        my $rc = $self->group_get($resp);
58
36
982
        if ($resp->isok) {
59
15
337
            $self->{dn} = $self->group_getdn();
60
15
241
            $self->{cn} = $self->group_getcn();
61
15
241
            $self->{exist} = 1;
62        }
63    }
64
65
43
505
    return $self;
66}
67
68sub create {
69
24
1
230
    my $self = shift;
70
24
315
    my $resp = Tivoli::AccessManager::Admin::Response->new();
71
24
87
    my $rc;
72
73
24
184
    unless (ref $self) {
74
13
50
        my $pd = shift;
75
13
114
        $self = new($self, $pd, @_);
76    }
77
78
24
481
    if (@_ % 2) {
79
1
12
        $resp->set_message("Invalid syntax");
80
1
10
        $resp->set_isok(0);
81
1
10
        return $resp;
82    }
83
23
302
    my %opts = @_;
84
85
23
169
    if ($self->{exist}) {
86
1
12
        $resp->set_message("The group $self->{name} already exists");
87
1
9
        $resp->set_iswarning(1);
88
1
6
        $resp->set_value($self);
89
90
1
10
        return $resp;
91    }
92
93
22
322
    $self->{name} = $opts{name} || $self->{name} || '';
94
22
271
    $self->{dn} = $opts{dn} || $self->{dn} || '';
95
22
324
    $self->{cn} = $opts{cn} || $self->{cn} || '';
96
97
22
243
    $self->{cn} = $self->{name} unless $opts{cn};
98
99
22
374
    if ($self->{cn} and $self->{dn} and $self->{name}) {
100
17
1255893
        $rc = $self->group_create($resp, $opts{container} || "");
101
17
509
        if ($resp->isok) {
102
16
118
            $self->{exist} = 1;
103
16
179
            $resp->set_value($self);
104        }
105    }
106    else {
107
5
67
        $resp->set_message("Syntax error in creating group -- the cn, dn and name must be defined");
108
5
51
        $resp->set_isok(0);
109
5
37
        $resp->set_value('undef');
110    }
111
112
22
338
    return $resp;
113}
114
115sub delete {
116
24
1
199
    my $self = shift;
117
118
24
347
    my $resp = Tivoli::AccessManager::Admin::Response->new();
119
120
24
204
    if ($self->{exist}) {
121
23
79
        my $rc;
122
23
90
        my $reg = 0;
123
124
23
249
        if (@_ == 1) {
125
9
32
            $reg = shift;
126        }
127        elsif (@_ % 2) {
128
1
18
            $resp->set_message("Invalid syntax");
129
1
17
            $resp->set_isok(0);
130
1
20
            return $resp;
131        }
132        elsif (@_) {
133
8
80
            my %opts = @_;
134
8
125
            $reg = $opts{registry} || 0;
135        }
136
137
22
1825443
        $rc = $self->group_delete($resp,$reg);
138
22
697
        if ($resp->isok) {
139
21
644
            $resp->set_value($rc);
140
21
350
            $self->_groupfree;
141
21
269
            $self->{exist} = 0;
142        }
143    }
144    else {
145
1
15
        $resp->set_message("The specified group does not exist");
146
1
10
        $resp->set_isok(0);
147    }
148
149
23
261
    return $resp;
150}
151
152sub description {
153
8
1
60
    my $self = shift;
154
8
118
    my $resp = Tivoli::AccessManager::Admin::Response->new();
155
156
8
66
    if ($self->{exist}) {
157
7
32
        my $desc = '';
158
159
7
72
        if (@_ == 1) {
160
2
15
            $desc = shift;
161        }
162        elsif (@_ % 2) {
163
1
12
            $resp->set_message("Invalid syntax");
164
1
18
            $resp->set_isok(0);
165
1
12
            return $resp;
166        }
167        elsif (@_) {
168
2
18
            my %opts = @_;
169
2
42
            $desc = $opts{description} || '';
170        }
171
172
6
35
        if ($desc) {
173
3
175666
            my $rc = $self->group_setdescription($resp, $desc);
174
3
94
            $resp->isok && $self->group_get($resp);
175        }
176
6
88
        if ($resp->isok) {
177
5
150
            $resp->set_value($self->group_getdescription || '');
178        }
179    }
180    else {
181
1
11
        $resp->set_message("The group does not yet exist");
182
1
10
        $resp->set_isok(0);
183    }
184
185
7
65
    return $resp;
186}
187
188sub cn {
189
2
1
32
    my $self = shift;
190
191
2
21
    my $resp = Tivoli::AccessManager::Admin::Response->new();
192
193
2
20
    if ($self->{cn}) {
194
1
10
        $resp->set_value($self->{cn});
195    }
196    else {
197
1
15
        $resp->set_message("The cn for this group is not defined");
198
1
37
        $resp->set_isok(0);
199    }
200
2
17
    return $resp;
201}
202
203sub dn {
204
6
1
60
    my $self = shift;
205
206
6
71
    my $resp = Tivoli::AccessManager::Admin::Response->new();
207
208
6
52
    if ($self->{dn}) {
209
4
39
        $resp->set_value($self->{dn});
210    }
211    else {
212
2
20
        $resp->set_message("The dn for this group is not defined");
213
2
16
        $resp->set_isok(0);
214    }
215
6
40
    return $resp;
216}
217
218sub _addmembers {
219
16
98
    my $self = shift;
220
16
207
    my %opts = @_;
221
222
16
89
    my (%hash, @junc,@add);
223
224
16
255
    my $resp = Tivoli::AccessManager::Admin::Response->new();
225    # This is suckage. I need to make sure there are no duplicates in the add
226    # list.
227
16
33
16
95
340
128
    %hash = map { $_ => 1 } @{$opts{add}};
228
16
207
    @add = keys %hash;
229
230    # We need to translate the existing users into a hash for the next step.
231    # This also forces the names to lower case.
232
16
14
14
16
117
72
90
120
    %hash = map { my $f = lc $_; $f => 1 } @{$opts{existing}};
233
234    # Finally. Create a list of all the users in the add list that are not in
235    # the existing list.
236
16
33
107
283
    @junc = grep { not $hash{lc($_)} } @add;
237
238    # In theory, the two lists (those we are adding and those who are not in
239    # the list) should be the same.
240
16
258
    if (@junc != @add and $opts{force}) {
241
2
17
        @add = @junc;
242
2
35
        unless (@add) {
243
1
20
            $resp->set_message("All of the users are already members in $self->{name}");
244
1
15
            $resp->set_iswarning(1);
245
1
16
            return $resp;
246        }
247    }
248    elsif (@junc != @add) {
249
1
14
        my $message = "The following users are already in $self->{name}: ";
250
1
1
7
20
        $resp->set_message($message . join(", ", @{$opts{existing}}));
251
1
16
        $resp->set_value([@junc]);
252
1
11
        $resp->set_iswarning(1);
253
1
16
        return $resp;
254    }
255
256
14
1026814
    my $rc = $self->group_addmembers($resp,\@add);
257
258
14
666
    return $resp;
259}
260
261sub _removemembers {
262
16
94
    my $self = shift;
263
16
163
    my %opts = @_;
264
265
16
88
    my (%hash, @intersect, @rem);
266
267
16
208
    my $resp = Tivoli::AccessManager::Admin::Response->new();
268
16
21
21
16
70
98
213
114
    %hash = map { my $f = lc $_; $f => 1 } @{$opts{remove}};
269
16
167
    @rem = keys %hash;
270
271
16
27
27
16
76
101
211
95
    %hash = map { my $f = lc $_; $f => 1 } @{$opts{existing}};
272
16
21
104
141
    @intersect = grep { $hash{$_} } @rem;
273
274
16
229
    if (@intersect != @rem and $opts{force}) {
275
2
18
        unless (@intersect) {
276
1
12
            $resp->set_message("There are no members to remove");
277
1
10
            $resp->set_iswarning(1);
278
1
17
            return $resp;
279        }
280
1
19
        @rem = @intersect;
281    }
282    elsif (@intersect != @rem) {
283
2
3
10
37
        %hash = map {$_ => 1} @rem;
284
2
2
8
24
        delete $hash{lc($_)} for @intersect;
285
286
2
32
        my $message = "The following are not in $self->{name}: ";
287
2
32
        $resp->set_message($message, join(", ", keys %hash));
288
2
26
        $resp->set_isok(0);
289
2
22
        return $resp;
290    }
291
292
13
823419
    my $rc = $self->group_removemembers($resp, \@rem);
293
294
13
478
    return $resp;
295}
296
297sub members {
298
41
1
263
    my $self = shift;
299
41
574
    my $resp = Tivoli::AccessManager::Admin::Response->new();
300
41
636
    my %job = (add => \&_addmembers,
301      remove => \&_removemembers);
302
303
41
389
    if (@_ % 2) {
304
1
12
        $resp->set_message("Invalid syntax");
305
1
12
        $resp->set_isok(0);
306
1
12
        return $resp;
307    }
308
40
270
    my %opts = @_;
309
310
40
356
    $opts{force} = defined($opts{force}) ? $opts{force} : 0;
311
40
290
    unless ($self->{exist}) {
312
1
14
        $resp->set_message("The group does not exist");
313
1
10
        $resp->set_isok(0);
314
1
11
        return $resp;
315    }
316
317    # Get the list of users,
318
39
2633754
    my @rc = sort $self->group_getmembers($resp);
319
39
1167
    return $resp unless ($resp->isok);
320
321
36
592
    unless (defined($opts{add}) or defined($opts{remove})) {
322
3
53
        $resp->set_value(\@rc);
323
3
78
        return $resp;
324    }
325
326
33
241
    for my $action (qw/remove add/) {
327
63
1022
        if (defined($opts{$action}) and ref($opts{$action}) eq 'ARRAY') {
328
32
628
            $resp = $job{$action}-> ($self, %opts, existing => \@rc) ;
329
32
496
            return $resp unless $resp->isok;
330        }
331        elsif (defined($opts{$action})) {
332
2
30
            $resp->set_message("Invalid syntax: $action => array ref");
333
2
15
            $resp->set_isok(0);
334
2
47
            return $resp;
335        }
336
59
4078727
        @rc = sort $self->group_getmembers($resp);
337
59
1796
        return $resp unless $resp->isok;
338    }
339
29
546
    $resp->set_value(\@rc);
340
29
743
    return $resp;
341}
342
343sub list {
344
7
1
50
    my $class = shift;
345
7
115
    my $resp = Tivoli::AccessManager::Admin::Response->new();
346
7
28
    my $pd;
347
348    # I want this to be called as either Tivoli::AccessManager::Admin::Group->list of
349    # $self->list
350
7
44
    if (ref $class) {
351
4
29
        $pd = $class->{context};
352    }
353    else {
354
3
18
        $pd = shift;
355    }
356
357
7
50
    if (@_ % 2) {
358
1
19
        $resp->set_message("Invalid syntax");
359
1
16
        $resp->set_isok(0);
360
1
16
        return $resp;
361    }
362
6
62
    my %opts = @_;
363
6
68
    $opts{bydn} ||= 0;
364
365
6
72
    $opts{maxreturn} = 0 unless defined($opts{maxreturn});
366
6
46
    $opts{pattern} = '*' unless defined($opts{pattern});
367
368
369
6
501116
    my @rc = $opts{bydn} ? group_listbydn($pd, $resp, $opts{pattern},
370                                   $opts{maxreturn}) :
371                           group_list($pd, $resp, $opts{pattern},
372                                         $opts{maxreturn});
373
6
201
    $resp->isok() && $resp->set_value(\@rc);
374
6
125
    return $resp;
375}
376
377sub groupimport {
378
10
1
130
    my $self = shift;
379
10
153
    my $resp = Tivoli::AccessManager::Admin::Response->new();
380
381
10
93
    unless (ref $self) {
382
1
10
        my $pd = shift;
383
1
16
        $self = new($self, $pd, @_);
384    }
385
386
10
144
    if (@_ % 2) {
387
1
18
        $resp->set_message("Invalid syntax");
388
1
16
        $resp->set_isok(0);
389
1
14
        return $resp;
390    }
391
9
88
    my %opts = @_;
392
393
9
114
    if ($self->{exist}) {
394
1
18
        $resp->set_message("Cannot import a group that already exists");
395
1
17
        $resp->set_isok(0);
396
1
11
        return $resp;
397    }
398
399
8
58
    unless ($self->{name}) {
400
2
45
        $self->{name} = $opts{name} || "";
401    }
402
403
8
56
    unless ($self->{dn}) {
404
3
51
        $self->{dn} = $opts{dn} || "";
405    }
406
407
8
99
    if ($self->{name} and $self->{dn}) {
408
6
373165
        my $rc = $self->group_import($resp, $opts{container} || "");
409
6
172
        if ($resp->isok()) {
410
5
324537
            $self->group_get($resp);
411
5
193
            $self->{cn} = $self->group_getcn();
412
5
103
            $self->{exist} = 1;
413        }
414    }
415    else {
416
2
27
        $resp->set_message("groupimport needs the name and the DN");
417
2
22
        $resp->set_isok(0);
418    }
419
8
171
    return $resp;
420}
421
422sub DESTROY {
423
43
312
    my $self = shift;
424
425
43
587
    $self->_groupfree();
426}
427
428
29
1
542
sub name { $_[0]->{name} }
429
26
1
294
sub exist { $_[0]->{exist} }
430
4311;
432
433 - 775
=head1 NAME

Tivoli::AccessManager::Admin::Group

=head1 SYNOPSIS

    use Tivoli::AccessManager::Admin;

    my ($resp, @groups);

    my $pd  = Tivoli::AccessManager::Admin->new(password => 'N3ew0nk');

    # Lets see who is there
    $resp = Tivoli::AccessManager::Admin::Group->list($pd, pattern => "lgroup*");
    print join("\n", @{$resp->value});
    # Alternately, search by DN.
    $resp = Tivoli::AccessManager::Admin::Group->list($pd, pattern => "lgroup*", bydn => 1);
    print join("\n", @{$resp->value});

    # Create a new group the easy way
    $resp = Tivoli::AccessManager::Admin::Group->create($pd, 
					name => 'lgroup',
					dn => 'cn=lgroup,ou=groups,o=rox,c=us',
					cn => 'lgroup'
				   );
    $groups[0] = $resp->value if $resp->is_ok;

    # Create a few more groups in a different way
    for my $i (1 .. 3) {
	my $name = sprintf "lgroup%02d", $i;
	$groups[$i] = Tivoli::AccessManager::Admin::Group->new($pd, name => $name);
	# Don't attempt to create something that already exists
	next if $groups[$i]->exist;

	$resp = $groups[$i]->create(dn => "cn=$name,ou=groups,o=rox,c=us");
    }

    # Add members to the group, skipping those users already in the group
    $resp = $groups[0]->members(add => [qw/luser01 luser02 luser03 luser04 luser05/ ], 
    			     force => 1);

    # List the members
    $resp = $groups[0]->members();
    print "\t$_\n" for (@{$resp->value()});

    # Remove members
    $resp = $groups[0]->members(remove => [qw/luser02 luser03/]);

    # Add and remove members at the same time
    $resp = $groups[0]->members(remove => [qw/luser01 luser04/],
			     add    => [qw/luser02 luser03/ ]
			   );
    # Delete the group
    $resp = $groups[0]->delete();

    # We didn't remove it from the registry.  Import it and delete it again
    $resp = $groups[0]->groupimport();
    $resp = $groups[0]->delete(1);
    
=head1 DESCRIPTION

B<Tivoli::AccessManager::Admin::Group> provides the interface to the group portion of the TAM API.

=head1 CONSTRUCTOR

=head2 new(PDADMIN[, name=E<gt> NAME, dn =E<gt> DN, cn =E<gt> CN])

Creates a blessed B<Tivoli::AccessManager::Admin::Group> object and returns it.

=head3 Parameters

=over 4

=item PDADMIN

An initialized L<Tivoli::AccessManager::Admin::Context> object.  Please note that, after the
L<Tivoli::AccessManager::Admin::Group> object is created, you cannot change the context w/o destroying
the object and recreating it.

=item name =E<gt> NAME

The name of the group to which the object refers.  B<new> will query TAM to determine if the
group exists or not, retrieving the other values (cn and dn) if it does.

=item dn =E<gt> DN

The group's DN.  If this value is provided (but L</"name"> is not), B<new>
will look to see if the group is already defined.  If the group is, the other
fields (name and cn) will be retrieved from TAM.

=item cn =E<gt> CN

The group's common name.  Nothing special happens if you provide the cn.  If
this parameter is not provided, I will assume it is the same as the group's
name.

=back

=head3 Returns

A fully blessed L<Tivoli::AccessManager::Admin::Group> object.

=head1 CLASS METHODS

Class methods behave like instance methods -- they return
L<Tivoli::AccessManager::Adming::Response> objects.

=head2 list(PDADMIN [,maxreturn =E<gt> N, pattern =E<gt> STRING, bydn => 1])

Lists some subset of TAM groups.  

=head3 Parameters

=over 4

=item PDADMIN

A fully blessed L<Tivoli::AccessManager::Admin::Context> object.  Since this is a class method,
and L<Tivoli::AccessManager::Admin::Context> objects are stored in the instances, you must
provide it.

=item maxreturn =E<gt> N

The number of users to return from the query.  This will default to 0, which
means all users matching the pattern.  Depending on how your LDAP is
configured, this may cause issues.

=item pattern =E<gt> STRING

The pattern to search on.  The standard rules for TAM searches apply -- * and
? are legal wild cards.  If not specified, it will default to *, which may
cause issues with your LDAP.  

=item bydn =E<gt> 1

Search by DN instead of group name.  This changes the semantics of the search
in an interesting fashion.  By default, you will only get tamified groups.
Searching by dn will return any LDAP object that TAM recognizes as a group,
irrespective of the TAMification.  This parameters defaults to 0.

=back

=head3 Returns

The resulting list of users.

=head1 METHODS

This verse is the same as all the rest.  Methods called with optional
parameters will attempt to set.  Methods called with no options will perform a
get.

Given that all return values are cleverly hidden in the returned
L<Tivoli::AccessManager::Admin::Response> object, I am not going to worry about documenting that
part.  I will only say what will be in the object.

=head2 create(name =E<gt> NAME, dn =E<gt> DN, cn =E<gt> CN, container =E<gt> 'CONTAINER NAME')

L</"create"> a new group in TAM.  At the bare minimum, both the name and
the DN must be defined.  See L</"Parameters"> below for a full discussion.
B<create> can be called instead of B<new>.  The new object can be retrieved
from the L<Tivoli::AccessManager::Admin::Response> object.

=head3 Parameters

=over 4

=item container =E<gt> 'CONTAINER NAME'

The group container, if there is one.  This parameter is optional.  If you
don't understand it, don't provide it.

=item name =E<gt> NAME

=item dn   =E<gt> DN

=item cn   =E<gt> CN

These are the same as defined for L</"new"> and are only required if you either
did not provide them when you created the instance or if you are calling
L</"create"> to create a new object.

=back

=head3 Returns

The success of the ooerstion if B<new> was used, the new L<Tivoli::AccessManager::Admin::Group>
object otherwise.

=head2 delete([1])

Deletes the group from TAM.  

=head3 Parameters

=over 4

=item 1

If anything is provided that perl will interpret as "true", the group will be
deleted from the registry.  Defaults to false.

=back

=head3 Returns

The success of the operation

=head2 description([STRING])

Gets or sets the description of the group.

=head3 Parameters

=over 4

=item STRING

The new description for the group.  This is an optional parameter.

=back

=head3 Returns

The description for the group for either the set or the get.

=head2 exist

A flag indicating if the group exists or not.

=head2 cn

Returns the CN for the group.  This is a read-only function.  No sets are
allowed via the TAM API (go see L<Net::LDAP> if you really want to do this).

=head2 dn

Returns the DN for the group.  This is a read-only function.  No sets are
allowed via the TAM API. 

=head2 name

Returns the name for the group.  This is a read-only function.  No sets are
allowed via the TAM API. 

=head2 members(add =E<gt> [qw/list of users/],
	         remove =E<gt> [qw/list of users/],
	         force =E<gt> 1)

Adds, removes and retrieves the members of a group.  The add and remove
option can be used at the same time -- removes are processed first.  If the
removal fails, no adds will be attempted.

=head3 Parameters

=over 4

=item add =E<gt> [qw/list of users/]

An array reference to the list of users to be added to the group.

=item remove =E<gt> [qw/list of users/]

An array reference to the list of users to be removed from the group.

=item force =E<gt> 1

Under normal circumstances, TAM will get unhappy if you try to either add
members that are already in the group or delete members that don't exist.
Using the force option will cause B<members> to only add those members that
are not in the group or delete those that are.

If no members will be added/deleted, you will get a warning in the response.

=back

=head3 Returns

Unless there is an error, you will get the new membership list for the group.

=head2 list(maxreturn =E<gt> NUMBER, pattern =E<gt> STRING)

Gets a list of groups defined in TAM.  If the pattern contains an '=', list
will search by DNs.  Otherwise, it will search by name.  Yes, this is the same
as the class method.  I like being able to call this way as well, although it
rather breaks the metaphor.

=head3 Parameters

=over 4

=item maxreturn =E<gt> NUMBER

The maximum number of groups to return.  It will default to 0, which means
return all matching groups.  That could cause problems with the LDAP.

=item pattern =E<gt> STRING

The pattern to search for.  You can use the * and ? wildcards, but that is
about it.  This will default to *.  It too could cause issues with the LDAP.

=back

=head3 Returns

The list of groups that matched the search criteria.

=head2 groupimport([name =E<gt> NAME, dn =E<gt> DN, container =E<gt> 'CONTAINER')

Imports an already existing LDAP group into TAM.  This can also be used to
create a new L<Tivoli::AccessManager::Admin::Group> object.

=head3 Parameters

See L<create> for the full explanation.  Have I mentioned I am a lazy POD
writer yet?

=head3 Returns

The success or failure of the import if called as an instance method, the new
L<Tivoli::AccessManager::Admin::Group> object otherwise.

=head1 ACKNOWLEDGEMENTS

See L<Tivoli::AccessManager::Admin>.  I stand upon the shoulders of giants.

=head1 BUGS

None known yet.

=head1 AUTHOR

Mik Firestone <mikfire@gmail.com>

=head1 COPYRIGHT

Copyright (c) 2004-2011 Mik Firestone.  All rights reserved.  This program is
free software; you can redistibute it and/or modify it under the same terms as
Perl itself.

Standard IBM copyright, trademark, patent and ownership statement.

=cut
776