More VERSION tuning: to avoid unnecessary Perl upgrades
[p5sagit/p5-mst-13.2.git] / lib / Net / PH.pm
CommitLineData
406c51ee 1#
2# Copyright (c) 1995-1997 Graham Barr <gbarr@pobox.com> and
3# Alex Hristov <hristov@slb.com>. All rights reserved. This program is free
4# software; you can redistribute it and/or modify it under the same terms
5# as Perl itself.
6
7package Net::PH;
8
9require 5.001;
10
11use strict;
12use vars qw(@ISA $VERSION);
13use Carp;
14
15use Socket 1.3;
16use IO::Socket;
17use Net::Cmd;
18use Net::Config;
19
20$VERSION = "2.20"; # $Id: //depot/libnet/Net/PH.pm#7$
21@ISA = qw(Exporter Net::Cmd IO::Socket::INET);
22
23sub new
24{
25 my $pkg = shift;
26 my $host = shift if @_ % 2;
27 my %arg = @_;
28 my $hosts = defined $host ? [ $host ] : $NetConfig{ph_hosts};
29 my $ph;
30
31 my $h;
32 foreach $h (@{$hosts})
33 {
34 $ph = $pkg->SUPER::new(PeerAddr => ($host = $h),
35 PeerPort => $arg{Port} || 'csnet-ns(105)',
36 Proto => 'tcp',
37 Timeout => defined $arg{Timeout}
38 ? $arg{Timeout}
39 : 120
40 ) and last;
41 }
42
43 return undef
44 unless defined $ph;
45
46 ${*$ph}{'net_ph_host'} = $host;
47
48 $ph->autoflush(1);
49
50 $ph->debug(exists $arg{Debug} ? $arg{Debug} : undef);
51
52 $ph;
53}
54
55sub status
56{
57 my $ph = shift;
58
59 $ph->command('status')->response;
60 $ph->code;
61}
62
63sub login
64{
65 my $ph = shift;
66 my($user,$pass,$encrypted) = @_;
67 my $resp;
68
69 $resp = $ph->command("login",$user)->response;
70
71 if(defined($pass) && $resp == CMD_MORE)
72 {
73 if($encrypted)
74 {
75 my $challenge_str = $ph->message;
76 chomp($challenge_str);
77 Net::PH::crypt::crypt_start($pass);
78 my $cryptstr = Net::PH::crypt::encryptit($challenge_str);
79
80 $ph->command("answer", $cryptstr);
81 }
82 else
83 {
84 $ph->command("clear", $pass);
85 }
86 $resp = $ph->response;
87 }
88
89 $resp == CMD_OK;
90}
91
92sub logout
93{
94 my $ph = shift;
95
96 $ph->command("logout")->response == CMD_OK;
97}
98
99sub id
100{
101 my $ph = shift;
102 my $id = @_ ? shift : $<;
103
104 $ph->command("id",$id)->response == CMD_OK;
105}
106
107sub siteinfo
108{
109 my $ph = shift;
110
111 $ph->command("siteinfo");
112
113 my $ln;
114 my %resp;
115 my $cur_num = 0;
116
117 while(defined($ln = $ph->getline))
118 {
119 $ph->debug_print(0,$ln)
120 if ($ph->debug & 2);
121 chomp($ln);
122 my($code,$num,$tag,$data);
123
124 if($ln =~ /^-(\d+):(\d+):(?:\s*([^:]+):)?\s*(.*)/o)
125 {
126 ($code,$num,$tag,$data) = ($1, $2, $3 || "",$4);
127 $resp{$tag} = bless [$code, $num, $tag, $data], "Net::PH::Result";
128 }
129 else
130 {
131 $ph->set_status($ph->parse_response($ln));
132 return \%resp;
133 }
134 }
135
136 return undef;
137}
138
139sub query
140{
141 my $ph = shift;
142 my $search = shift;
143
144 my($k,$v);
145
146 my @args = ('query', _arg_hash($search));
147
148 push(@args,'return',_arg_list( shift ))
149 if @_;
150
151 unless($ph->command(@args)->response == CMD_INFO)
152 {
153 return $ph->code == 501
154 ? []
155 : undef;
156 }
157
158 my $ln;
159 my @resp;
160 my $cur_num = 0;
161
162 my($last_tag);
163
164 while(defined($ln = $ph->getline))
165 {
166 $ph->debug_print(0,$ln)
167 if ($ph->debug & 2);
168 chomp($ln);
169 my($code,$idx,$num,$tag,$data);
170
171 if($ln =~ /^-(\d+):(\d+):\s*([^:]*):\s*(.*)/o)
172 {
173 ($code,$idx,$tag,$data) = ($1,$2,$3,$4);
174 my $num = $idx - 1;
175
176 $resp[$num] ||= {};
177
178 $tag = $last_tag
179 unless(length($tag));
180
181 $last_tag = $tag;
182
183 if(exists($resp[$num]->{$tag}))
184 {
185 $resp[$num]->{$tag}->[3] .= "\n" . $data;
186 }
187 else
188 {
189 $resp[$num]->{$tag} = bless [$code, $idx, $tag, $data], "Net::PH::Result";
190 }
191 }
192 else
193 {
194 $ph->set_status($ph->parse_response($ln));
195 return \@resp;
196 }
197 }
198
199 return undef;
200}
201
202sub change
203{
204 my $ph = shift;
205 my $search = shift;
206 my $make = shift;
207
208 $ph->command(
209 "change", _arg_hash($search),
210 "make", _arg_hash($make)
211 )->response == CMD_OK;
212}
213
214sub _arg_hash
215{
216 my $hash = shift;
217
218 return $hash
219 unless(ref($hash));
220
221 my($k,$v);
222 my @r;
223
224 while(($k,$v) = each %$hash)
225 {
226 my $a = $v;
227 $a =~ s/\n/\\n/sog;
228 $a =~ s/\t/\\t/sog;
229 $a = '"' . $a . '"'
230 if $a =~ /\W/;
231 $a = '""'
232 unless length $a;
233
234 push(@r, "$k=$a");
235 }
236 join(" ", @r);
237}
238
239sub _arg_list
240{
241 my $arr = shift;
242
243 return $arr
244 unless(ref($arr));
245
246 my $v;
247 my @r;
248
249 foreach $v (@$arr)
250 {
251 my $a = $v;
252 $a =~ s/\n/\\n/sog;
253 $a =~ s/\t/\\t/sog;
254 $a = '"' . $a . '"'
255 if $a =~ /\W/;
256 push(@r, $a);
257 }
258
259 join(" ",@r);
260}
261
262sub add
263{
264 my $ph = shift;
265 my $arg = @_ > 1 ? { @_ } : shift;
266
267 $ph->command('add', _arg_hash($arg))->response == CMD_OK;
268}
269
270sub delete
271{
272 my $ph = shift;
273 my $arg = @_ > 1 ? { @_ } : shift;
274
275 $ph->command('delete', _arg_hash($arg))->response == CMD_OK;
276}
277
278sub force
279{
280 my $ph = shift;
281 my $search = shift;
282 my $force = shift;
283
284 $ph->command(
285 "change", _arg_hash($search),
286 "force", _arg_hash($force)
287 )->response == CMD_OK;
288}
289
290
291sub fields
292{
293 my $ph = shift;
294
295 $ph->command("fields", _arg_list(\@_));
296
297 my $ln;
298 my %resp;
299 my $cur_num = 0;
300 my @tags = ();
301
302 while(defined($ln = $ph->getline))
303 {
304 $ph->debug_print(0,$ln)
305 if ($ph->debug & 2);
306 chomp($ln);
307
308 my($code,$num,$tag,$data,$last_tag);
309
310 if($ln =~ /^-(\d+):(\d+):\s*([^:]*):\s*(.*)/o)
311 {
312 ($code,$num,$tag,$data) = ($1,$2,$3,$4);
313
314 $tag = $last_tag
315 unless(length($tag));
316
317 $last_tag = $tag;
318
319 if(exists $resp{$tag})
320 {
321 $resp{$tag}->[3] .= "\n" . $data;
322 }
323 else
324 {
325 $resp{$tag} = bless [$code, $num, $tag, $data], "Net::PH::Result";
326 push @tags, $tag;
327 }
328 }
329 else
330 {
331 $ph->set_status($ph->parse_response($ln));
332 return wantarray ? (\%resp, \@tags) : \%resp;
333 }
334 }
335
336 return;
337}
338
339sub quit
340{
341 my $ph = shift;
342
343 $ph->close
344 if $ph->command("quit")->response == CMD_OK;
345}
346
347##
348## Net::Cmd overrides
349##
350
351sub parse_response
352{
353 return ()
354 unless $_[1] =~ s/^(-?)(\d\d\d):?//o;
355 ($2, $1 eq "-");
356}
357
358sub debug_text { $_[2] =~ /^(clear)/i ? "$1 ....\n" : $_[2]; }
359
360package Net::PH::Result;
361
362sub code { shift->[0] }
363sub value { shift->[1] }
364sub field { shift->[2] }
365sub text { shift->[3] }
366
367package Net::PH::crypt;
368
369# The code in this package is based upon 'cryptit.c', Copyright (C) 1988 by
370# Steven Dorner, and Paul Pomes, and the University of Illinois Board
371# of Trustees, and by CSNET.
372
373use integer;
374use strict;
375
376sub ROTORSZ () { 256 }
377sub MASK () { 255 }
378
379my(@t1,@t2,@t3,$n1,$n2);
380
381sub crypt_start {
382 my $pass = shift;
383 $n1 = 0;
384 $n2 = 0;
385 crypt_init($pass);
386}
387
388sub crypt_init {
389 my $pw = shift;
390 my $i;
391
392 @t2 = @t3 = (0) x ROTORSZ;
393
394 my $buf = crypt($pw,$pw);
395 return -1 unless length($buf) > 0;
396 $buf = substr($buf . "\0" x 13,0,13);
397 my @buf = map { ord $_ } split(//, $buf);
398
399
400 my $seed = 123;
401 for($i = 0 ; $i < 13 ; $i++) {
402 $seed = $seed * $buf[$i] + $i;
403 }
404 @t1 = (0 .. ROTORSZ-1);
405
406 for($i = 0 ; $i < ROTORSZ ; $i++) {
407 $seed = 5 * $seed + $buf[$i % 13];
408 my $random = $seed % 65521;
409 my $k = ROTORSZ - 1 - $i;
410 my $ic = ($random & MASK) % ($k + 1);
411 $random >>= 8;
412 @t1[$k,$ic] = @t1[$ic,$k];
413 next if $t3[$k] != 0;
414 $ic = ($random & MASK) % $k;
415 while($t3[$ic] != 0) {
416 $ic = ($ic + 1) % $k;
417 }
418 $t3[$k] = $ic;
419 $t3[$ic] = $k;
420 }
421 for($i = 0 ; $i < ROTORSZ ; $i++) {
422 $t2[$t1[$i] & MASK] = $i
423 }
424}
425
426sub encode {
427 my $sp = shift;
428 my $ch;
429 my $n = scalar(@$sp);
430 my @out = ($n);
431 my $i;
432
433 for($i = 0 ; $i < $n ; ) {
434 my($f0,$f1,$f2) = splice(@$sp,0,3);
435 push(@out,
436 $f0 >> 2,
437 ($f0 << 4) & 060 | ($f1 >> 4) & 017,
438 ($f1 << 2) & 074 | ($f2 >> 6) & 03,
439 $f2 & 077);
440 $i += 3;
441 }
442 join("", map { chr((($_ & 077) + 35) & 0xff) } @out); # ord('#') == 35
443}
444
445sub encryptit {
446 my $from = shift;
447 my @from = map { ord $_ } split(//, $from);
448 my @sp = ();
449 my $ch;
450 while(defined($ch = shift @from)) {
451 push(@sp,
452 $t2[($t3[($t1[($ch + $n1) & MASK] + $n2) & MASK] - $n2) & MASK] - $n1);
453
454 $n1++;
455 if($n1 == ROTORSZ) {
456 $n1 = 0;
457 $n2++;
458 $n2 = 0 if $n2 == ROTORSZ;
459 }
460 }
461 encode(\@sp);
462}
463
4641;
465
466__END__
467
468=head1 NAME
469
470Net::PH - CCSO Nameserver Client class
471
472=head1 SYNOPSIS
473
474 use Net::PH;
475
476 $ph = Net::PH->new("some.host.name",
477 Port => 105,
478 Timeout => 120,
479 Debug => 0);
480
481 if($ph) {
482 $q = $ph->query({ field1 => "value1" },
483 [qw(name address pobox)]);
484
485 if($q) {
486 }
487 }
488
489 # Alternative syntax
490
491 if($ph) {
492 $q = $ph->query('field1=value1',
493 'name address pobox');
494
495 if($q) {
496 }
497 }
498
499=head1 DESCRIPTION
500
501C<Net::PH> is a class implementing a simple Nameserver/PH client in Perl
502as described in the CCSO Nameserver -- Server-Client Protocol. Like other
503modules in the Net:: family the C<Net::PH> object inherits methods from
504C<Net::Cmd>.
505
506=head1 CONSTRUCTOR
507
508=over 4
509
510=item new ( [ HOST ] [, OPTIONS ])
511
512 $ph = Net::PH->new("some.host.name",
513 Port => 105,
514 Timeout => 120,
515 Debug => 0
516 );
517
518This is the constructor for a new Net::PH object. C<HOST> is the
519name of the remote host to which a PH connection is required.
520
521If C<HOST> is not given, then the C<SNPP_Host> specified in C<Net::Config>
522will be used.
523
524C<OPTIONS> is an optional list of named options which are passed in
525a hash like fashion, using key and value pairs. Possible options are:-
526
527B<Port> - Port number to connect to on remote host.
528
529B<Timeout> - Maximum time, in seconds, to wait for a response from the
530Nameserver, a value of zero will cause all IO operations to block.
531(default: 120)
532
533B<Debug> - Enable the printing of debugging information to STDERR
534
535=back
536
537=head1 METHODS
538
539Unless otherwise stated all methods return either a I<true> or I<false>
540value, with I<true> meaning that the operation was a success. When a method
541states that it returns a value, failure will be returned as I<undef> or an
542empty list.
543
544=over 4
545
546=item query( SEARCH [, RETURN ] )
547
548 $q = $ph->query({ name => $myname },
549 [qw(name email schedule)]);
550
551 foreach $handle (@{$q}) {
552 foreach $field (keys %{$handle}) {
553 $c = ${$handle}{$field}->code;
554 $v = ${$handle}{$field}->value;
555 $f = ${$handle}{$field}->field;
556 $t = ${$handle}{$field}->text;
557 print "field:[$field] [$c][$v][$f][$t]\n" ;
558 }
559 }
560
561
562
563Search the database and return fields from all matching entries.
564
565The C<SEARCH> argument is a reference to a HASH which contains field/value
566pairs which will be passed to the Nameserver as the search criteria.
567
568C<RETURN> is optional, but if given it should be a reference to a list which
569contains field names to be returned.
570
571The alternative syntax is to pass strings instead of references, for example
572
573 $q = $ph->query('name=myname',
574 'name email schedule');
575
576The C<SEARCH> argument is a string that is passed to the Nameserver as the
577search criteria. The strings being passed should B<not> contain any carriage
578returns, or else the query command might fail or return invalid data.
579
580C<RETURN> is optional, but if given it should be a string which will
581contain field names to be returned.
582
583Each match from the server will be returned as a HASH where the keys are the
584field names and the values are C<Net::PH:Result> objects (I<code>, I<value>,
585I<field>, I<text>).
586
587Returns a reference to an ARRAY which contains references to HASHs, one
588per match from the server.
589
590=item change( SEARCH , MAKE )
591
592 $r = $ph->change({ email => "*.domain.name" },
593 { schedule => "busy");
594
595Change field values for matching entries.
596
597The C<SEARCH> argument is a reference to a HASH which contains field/value
598pairs which will be passed to the Nameserver as the search criteria.
599
600The C<MAKE> argument is a reference to a HASH which contains field/value
601pairs which will be passed to the Nameserver that
602will set new values to designated fields.
603
604The alternative syntax is to pass strings instead of references, for example
605
606 $r = $ph->change('email="*.domain.name"',
607 'schedule="busy"');
608
609The C<SEARCH> argument is a string to be passed to the Nameserver as the
610search criteria. The strings being passed should B<not> contain any carriage
611returns, or else the query command might fail or return invalid data.
612
613
614The C<MAKE> argument is a string to be passed to the Nameserver that
615will set new values to designated fields.
616
617Upon success all entries that match the search criteria will have
618the field values, given in the Make argument, changed.
619
620=item login( USER, PASS [, ENCRYPT ])
621
622 $r = $ph->login('username','password',1);
623
624Enter login mode using C<USER> and C<PASS>. If C<ENCRYPT> is given and
625is I<true> then the password will be used to encrypt a challenge text
626string provided by the server, and the encrypted string will be sent back
627to the server. If C<ENCRYPT> is not given, or I<false> then the password
628will be sent in clear text (I<this is not recommended>)
629
630=item logout()
631
632 $r = $ph->logout();
633
634Exit login mode and return to anonymous mode.
635
636=item fields( [ FIELD_LIST ] )
637
638 $fields = $ph->fields();
639 foreach $field (keys %{$fields}) {
640 $c = ${$fields}{$field}->code;
641 $v = ${$fields}{$field}->value;
642 $f = ${$fields}{$field}->field;
643 $t = ${$fields}{$field}->text;
644 print "field:[$field] [$c][$v][$f][$t]\n";
645 }
646
647In a scalar context, returns a reference to a HASH. The keys of the HASH are
648the field names and the values are C<Net::PH:Result> objects (I<code>,
649I<value>, I<field>, I<text>).
650
651In an array context, returns a two element array. The first element is a
652reference to a HASH as above, the second element is a reference to an array
653which contains the tag names in the order that they were returned from the
654server.
655
656C<FIELD_LIST> is a string that lists the fields for which info will be
657returned.
658
659=item add( FIELD_VALUES )
660
661 $r = $ph->add( { name => $name, phone => $phone });
662
663This method is used to add new entries to the Nameserver database. You
664must successfully call L<login> before this method can be used.
665
666B<Note> that this method adds new entries to the database. To modify
667an existing entry use L<change>.
668
669C<FIELD_VALUES> is a reference to a HASH which contains field/value
670pairs which will be passed to the Nameserver and will be used to
671initialize the new entry.
672
673The alternative syntax is to pass a string instead of a reference, for example
674
675 $r = $ph->add('name=myname phone=myphone');
676
677C<FIELD_VALUES> is a string that consists of field/value pairs which the
678new entry will contain. The strings being passed should B<not> contain any
679carriage returns, or else the query command might fail or return invalid data.
680
681
682=item delete( FIELD_VALUES )
683
684 $r = $ph->delete('name=myname phone=myphone');
685
686This method is used to delete existing entries from the Nameserver database.
687You must successfully call L<login> before this method can be used.
688
689B<Note> that this method deletes entries to the database. To modify
690an existing entry use L<change>.
691
692C<FIELD_VALUES> is a string that serves as the search criteria for the
693records to be deleted. Any entry in the database which matches this search
694criteria will be deleted.
695
696=item id( [ ID ] )
697
698 $r = $ph->id('709');
699
700Sends C<ID> to the Nameserver, which will enter this into its
701logs. If C<ID> is not given then the UID of the user running the
702process will be sent.
703
704=item status()
705
706Returns the current status of the Nameserver.
707
708=item siteinfo()
709
710 $siteinfo = $ph->siteinfo();
711 foreach $field (keys %{$siteinfo}) {
712 $c = ${$siteinfo}{$field}->code;
713 $v = ${$siteinfo}{$field}->value;
714 $f = ${$siteinfo}{$field}->field;
715 $t = ${$siteinfo}{$field}->text;
716 print "field:[$field] [$c][$v][$f][$t]\n";
717 }
718
719Returns a reference to a HASH containing information about the server's
720site. The keys of the HASH are the field names and values are
721C<Net::PH:Result> objects (I<code>, I<value>, I<field>, I<text>).
722
723=item quit()
724
725 $r = $ph->quit();
726
727Quit the connection
728
729=back
730
731=head1 Q&A
732
733How do I get the values of a Net::PH::Result object?
734
735 foreach $handle (@{$q}) {
736 foreach $field (keys %{$handle}) {
737 $my_code = ${$q}{$field}->code;
738 $my_value = ${$q}{$field}->value;
739 $my_field = ${$q}{$field}->field;
740 $my_text = ${$q}{$field}->text;
741 }
742 }
743
744How do I get a count of the returned matches to my query?
745
746 $my_count = scalar(@{$query_result});
747
748How do I get the status code and message of the last C<$ph> command?
749
750 $status_code = $ph->code;
751 $status_message = $ph->message;
752
753=head1 SEE ALSO
754
755L<Net::Cmd>
756
757=head1 AUTHORS
758
759Graham Barr <gbarr@pobox.com>
760Alex Hristov <hristov@slb.com>
761
762=head1 ACKNOWLEDGMENTS
763
764Password encryption code ported to perl by Broc Seib <bseib@purdue.edu>,
765Purdue University Computing Center.
766
767Otis Gospodnetic <otisg@panther.middlebury.edu> suggested
768passing parameters as string constants. Some queries cannot be
769executed when passing parameters as string references.
770
771 Example: query first_name last_name email="*.domain"
772
773=head1 COPYRIGHT
774
775The encryption code is based upon cryptit.c, Copyright (C) 1988 by
776Steven Dorner, and Paul Pomes, and the University of Illinois Board
777of Trustees, and by CSNET.
778
779All other code is Copyright (c) 1996-1997 Graham Barr <gbarr@pobox.com>
780and Alex Hristov <hristov@slb.com>. All rights reserved. This program is
781free software; you can redistribute it and/or modify it under the same
782terms as Perl itself.
783
784=cut