File Coverage

blib/lib/SMS/Handler/Email.pm
Criterion Covered Total %
statement 66 764 8.6
branch 0 290 0.0
condition 0 113 0.0
subroutine 22 69 31.8
pod 6 10 60.0
total 94 1246 7.5


line stmt bran cond sub pod time code
1             package SMS::Handler::Email;
2              
3             require 5.005_62;
4              
5 3     3   55716 use Carp;
  3         7  
  3         306  
6 3     3   20 use strict;
  3         6  
  3         108  
7 3     3   17 use warnings;
  3         6  
  3         99  
8 3     3   3216 use IO::File;
  3         26699  
  3         567  
9 3     3   3763 use Net::SMTP;
  3         111696  
  3         218  
10 3     3   4240 use Net::POP3;
  3         15515  
  3         199  
11 3     3   3576 use Mail::Audit;
  3         146166  
  3         29  
12 3     3   3253 use Date::Parse;
  3         27562  
  3         427  
13 3     3   76 use Digest::MD5;
  3         7  
  3         116  
14 3     3   1300 use SMS::Handler;
  3         6  
  3         151  
15 3     3   3562 use MIME::Parser;
  3         337263  
  3         114  
16 3     3   3486 use Text::Abbrev;
  3         128  
  3         191  
17 3     3   3071 use HTML::Parser;
  3         18893  
  3         134  
18 3     3   33 use Mail::Address;
  3         6  
  3         97  
19 3     3   2796 use Unicode::Map8;
  3         12797  
  3         257  
20 3     3   3300 use Net::SMPP 1.04;
  3         101958  
  3         42  
21 3     3   6032 use POSIX qw(strftime);
  3         28919  
  3         32  
22 3     3   4231 use Fcntl qw(SEEK_SET);
  3         23  
  3         187  
23 3     3   1516 use SMS::Handler::Utils;
  3         55  
  3         136  
24 3     3   1856 use SMS::Handler::Dispatcher;
  3         9  
  3         477  
25 3     3   2217 use Params::Validate qw(:all);
  3         37081  
  3         812  
26 3     3   31 use MIME::WordDecoder qw(unmime);
  3         5  
  3         54626  
27              
28             # $Id: Email.pm,v 1.55 2003/03/13 20:41:54 lem Exp $
29              
30             our $VERSION = q$Revision: 1.55 $;
31             $VERSION =~ s/Revision: //;
32              
33             our @ISA = qw(SMS::Handler::Dispatcher);
34              
35             our $Debug = 0;
36              
37             =pod
38              
39             =head1 NAME
40              
41             SMS::Handler::Email - Process Email related commands
42              
43             =head1 SYNOPSIS
44              
45             use SMS::Handler::Email;
46              
47             my $h = SMS::Handler::Email->new(-queue => $queue_obj,
48             -state => $ref_to_hash,
49             -secret => $my_secret_phrase,
50             -addr => '9.9.5551212',
51             -pop => 'pop.your.com',
52             -smtp => 'smtp.your.com',
53             -maxlen => 160,
54             -maxfetch => 1024,
55             -compact => 1,
56             -spamcheck => $obj,
57             -cmds => { ... },
58             -help => { ... },
59             -maxspam => 10,
60             );
61              
62             $h->handle({ ... });
63              
64             =head1 DESCRIPTION
65              
66             This module implements a simple responder class. It will respond to
67             any message directed to the specified phone number, with the specified
68             message.
69              
70             The Email message is assumed to be in ISO-8859-1 (Latin1)
71             encoding. Mappings are provided to convert the messages to a safe
72             7-Bit character set which is believed to be compatible with any SMS
73             receiver. This mapping is lossy in the sense that accents and special
74             characters are converted to a close but incorrect representation. For
75             instance, an B is converted to a plain B.
76              
77             =head2 SUPPORTED COMMANDS
78              
79             The following commands are supported in the first line of the
80             SMS. Commands can be abbreviated to any unique substring. The first
81             line can be separated by the rest of the message either with a
82             new-line or two consecutive space characters.
83              
84             =over 2
85              
86             =item B<.ACCOUNT login password>
87              
88             Associates the given account with the source address of the
89             SMS. Further commands coming from this address are attempted with
90             these supplied credentials, which are the login or username and
91             password of the POP server.
92              
93             =item B<.CHECK>
94              
95             Checks all the messages in the mailbox looking for spam. This requires
96             that a B item be passed to C<-Enew()> at object
97             creation time. A specially formatted response message, suitable to
98             remove all the SPAM messages will be returned.
99              
100             If this command is followed by the B symbol, messages recognized as
101             SPAM will be erased automatically.
102              
103             =item B<.SEND to subject>
104              
105             Sends the remainder of the SMS as a message to the address(es)
106             specified in the B field. Multiple addresses can be specified by
107             separating them with commas. No spaces are allowed in the addresses.
108              
109             Before the actual sending of the message, a POP authentication is
110             attempted. Only if this authentication succeeds will the message be
111             sent.
112              
113             =item B<.LIST>
114              
115             Retrieves the current list of messages in the POP server.
116              
117             =item B<.DELETE msg>
118              
119             Deletes the message B from the POP server. When omitting B,
120             the command will refer to the most recent message. 0 is a synonim to
121             1.
122              
123             =item B<.REPLY msg>
124              
125             Replies to the message specified by B. When omitting B, the
126             command will refer to the most recent message. 0 is a synonim to 1.
127              
128             =item B<.ALL msg>
129              
130             Replies to the message specified by B. When omitting B, the
131             command will refer to the most recent message. 0 is a synonim to
132             1. All recipients of the original message are copied of the response.
133              
134             =item B<.FORWARD msg to>
135              
136             Forwards the message specified by B to the addresses specified in
137             B. When omitting B, the command will refer to the most recent
138             message. 0 is a synonim to 1.
139              
140             =item B<.GET msg [block]>
141              
142             Retrieves the message B from the POP server. Only the first chars
143             of the body are retrieved. If a numeric block is specified, that block
144             of octets is presented to the user. The first block is 1. The first
145             block of the most recent message can be requested by omitting B
146             and B. B must be specified in order for B to be
147             specified too. A B 0 is synonim to 1.
148              
149             =item B<.ALIAS [address] nick>
150              
151             Creates a nick for the user whose address is specified. If no address
152             is specified, erases the nick. Nick names or aliases, are used as
153             shorthand for the complete email address of a user.
154              
155             =item B<.HELP>
156              
157             Sends a (very) short usage summary.
158              
159             =back
160              
161             The variable C<$SMS::Handler::Email::DefaultLanguage> can be used to
162             choose the default language of all the answers.
163              
164             =pod
165              
166             =head2 HELP TABLE
167              
168             The help table is a hash stored in C<%SMS::Handler::Email::Help>. Each
169             key of the hash, is a command name. Each corresponding value is the
170             reference to a hash, whose key is a language code and its value is a
171             brief explanation of what the command does in the corresponding
172             language.
173              
174             Probably it is wise to avoid explanations that use 8 bit characters,
175             as those are not safely handled by all the phones out there.
176              
177             =cut
178              
179             our %Help =
180             (
181             ACCOUNT =>
182             {
183             en => q{+ login & password for the wanted account},
184             es => q{+ login y clave de la cuenta deseada},
185             },
186             ALIAS =>
187             {
188             en => q{+ nick erases or creates an alias for the given address},
189             es => q{+ nick borra o crea un alias para la direccion dada},
190             },
191             CHECK =>
192             {
193             en => q{checks mailbox for spam. With !, removes it},
194             es => q{revisa el buzon buscando spam. Con ! lo borra},
195             },
196             SEND =>
197             {
198             en => q{+ recipients and subject in the first line sends email},
199             es => q{+ destinatarios y asunto en la 1a linea envia un email},
200             },
201             INTERFACE =>
202             {
203             en => q{+ language, changes the language for the responses},
204             es => q{+ lenguage, cambia el lenguaje para las respuestas},
205             },
206             LIST =>
207             {
208             en => q{returns the amount of messages in the registered account},
209             es => q{contesta la cantidad de mensajes en la cuenta registrada},
210             },
211             DELETE =>
212             {
213             en => q{+ msg number removes it from the mailbox},
214             es => q{+ numero de mens lo remueve del buzon},
215             },
216             REPLY =>
217             {
218             en => q{+ msg number replies the remainder of the SMS},
219             es => q{+ numero de mens contesta el resto del SMS},
220             },
221             ALL =>
222             {
223             en => q{+ msg number replies the remainder of the SMS to all},
224             es => q{+ numero de mens contesta el resto del SMS a todos},
225             },
226             FORWARD =>
227             {
228             en => q{+ msg number + dest addr + the remainder of the SMS},
229             es => q{+ numero de mens + dest + el resto del SMS},
230             },
231             GET =>
232             {
233             en => q{+ msg number fetches it from the mailbox},
234             es => q{+ numero de mens lo trae del buzon},
235             },
236             HELP =>
237             {
238             en => q{sends this help text},
239             es => q{envia estos textos de ayuda},
240             },
241             );
242              
243            
244             =pod
245            
246             =head2 RESPONSE TABLE
247              
248             The commands must supply responses to the user based on its
249             success. In order to support multiple languages, responses are stored
250             in a hash table (C<%SMS::Handler::Email::Messages>). Each key on this
251             hash table correspond to a language tag as described in
252             L. The corresponding value, is a reference to a hash
253             whose keys are message tags (ie, an identifier for the message) and a
254             message in the required language.
255              
256             Please see the source code for the specific message identifiers
257             used. Note that you must call C if this table is changed.
258              
259             Supported languages must be added to the
260             C<%SMS::Handler::Email::SupportedLangages> hash before calling the
261             init method.
262              
263             =cut
264             ;
265              
266             my %SupportedLanguages =
267             (
268             'SPANISH' => 'es',
269             'ESPAÑOL' => 'es',
270             'CASTELLANO' => 'es',
271             'ESPANOL' => 'es',
272             'INGLÉS' => 'en',
273             'INGLES' => 'en',
274             'ENGLISH' => 'en',
275             );
276              
277             my %Languages;
278              
279             my $DefaultLanguage = 'en';
280              
281             our %Messages =
282             (
283             en =>
284             {
285             HANDLE_CMD_ERR => q{Command error},
286             ACCOUNT_OK => q{ok},
287             ALIAS_OK => q{updated},
288             LANG_OK => q{interface set to},
289             SEND_OK => q{Send ok},
290             DELETE_OK => q{deleted ok},
291             REPLY_OK => q{replied ok},
292             FWD_OK => q{forwarded ok},
293             LIST_NO_REG => q{Use .ACCOUNT to register an email account},
294             HEAD_SUBJECT => q{Sub:},
295             HEAD_DATE => q{Date:},
296             HEAD_FROM => q{From:},
297             HEAD_PHONE => q{Phone:},
298             REPLY_YOU => q{You},
299             REPLY_SAID => q{said:},
300             MSG_COUNT_ONE => q{Message},
301             MSG_COUNT_MANY => q{Messages},
302             MSG_COUNT_LAST => q{Last},
303             HTML_TRANS => q{[Translated from HTML]},
304             FETCH_NOMSG => q{POP Error. No message},
305             FETCH_MIME => q{Error parsing MIME message},
306             FETCH_BODY => q{POP Error in fetching body},
307             POP_CONNECT => q{POP Error connecting to server},
308             POP_USER => q{POP Error in USER},
309             POP_PASS => q{POP Error in PASS (Invalid password?)},
310             POP_DELE => q{POP Error in DELE},
311             POP_POPSTAT => q{POP Error in STAT},
312             POP_QUIT => q{POP Error in QUIT},
313             SMTP_CONNECT => q{Error connecting to SMTP server},
314             SMTP_MAIL => q{SMTP Error in MAIL FROM},
315             SMTP_RCPT => q{SMTP: Check your destination addresses},
316             SMTP_DATA => q{SMTP Error in DATA},
317             SMTP_HDATASEND => q{SMTP Error sending message header},
318             SMTP_BDATASEND => q{SMTP Error sending message body},
319             SMTP_DATAEND => q{SMTP Error committing message},
320             SMTP_QUIT => q{SMTP Error in QUIT},
321             CHK_SPAM_ONE => q{spam message},
322             CHK_SPAM_MANY => q{spam messages},
323             CHK_DEL_ONE => q{message deleted},
324             CHK_DEL_MANY => q{messages deleted},
325             TRUNC_START => q{[only },
326             TRUNC_END => q{octets shown]},
327             ATTACH => q{attach},
328             MSG_SHORT => q{Message does not have that many segments},
329             },
330             es =>
331             {
332             HANDLE_CMD_ERR => q{Error en el comando},
333             ACCOUNT_OK => q{ok},
334             ALIAS_OK => q{ha sido actualizado},
335             LANG_OK => q{Interfaz en},
336             SEND_OK => q{Enviado correctamente},
337             DELETE_OK => q{borrado correctamente},
338             REPLY_OK => q{contestado correctamente},
339             FWD_OK => q{re-enviado ok},
340             LIST_NO_REG => q{Use .ACCOUNT para asociar una cuenta de correo},
341             HEAD_SUBJECT => q{Asunto:},
342             HEAD_DATE => q{Fecha:},
343             HEAD_FROM => q{De:},
344             HEAD_PHONE => q{Tel:},
345             REPLY_YOU => q{Usted},
346             REPLY_SAID => q{dijo:},
347             MSG_COUNT_ONE => q{Mensaje},
348             MSG_COUNT_MANY => q{Mensajes},
349             MSG_COUNT_LAST => q{Ultimo},
350             HTML_TRANS => q{[Traducido de HTML]},
351             FETCH_NOMSG => q{Error POP. No existe el mensaje},
352             FETCH_MIME => q{Error interpretando mensaje MIME},
353             FETCH_BODY => q{Error POP en la extraccion del mensaje},
354             POP_CONNECT => q{Error POP conectandose al servidor},
355             POP_USER => q{Error POP en comando USER},
356             POP_PASS => q{Error POP en comando PASS (Clave incorrecta?)},
357             POP_DELE => q{Error POP en comando DELE},
358             POP_POPSTAT => q{Error POP en comando STAT},
359             POP_QUIT => q{Error POP en comando QUIT},
360             SMTP_CONNECT => q{Error en conexion al servidor SMTP},
361             SMTP_MAIL => q{Error SMTP en comando MAIL FROM},
362             SMTP_RCPT => q{SMTP: Revise direcciones de destino},
363             SMTP_DATA => q{Error SMTP en comando DATA},
364             SMTP_HDATASEND => q{Error SMTP enviando encabezado del mensaje},
365             SMTP_BDATASEND => q{Error SMTP enviando el cuerpo del mensaje},
366             SMTP_DATAEND => q{Error SMTP terminando el envio},
367             SMTP_QUIT => q{Error SMTP terminando la sesion},
368             CHK_SPAM_ONE => q{mensaje spam},
369             CHK_SPAM_MANY => q{mensajes spam},
370             CHK_DEL_ONE => q{mensaje borrado},
371             CHK_DEL_MANY => q{mensajes borrados},
372             TRUNC_START => q{[solo se muestra un bloque de },
373             TRUNC_END => q{octetos]},
374             ATTACH => q{anexo},
375             MSG_SHORT => q{El mensaje no contiene tantos segmentos},
376             },
377             );
378              
379             sub _msg
380             {
381 0     0     my $self = shift;
382 0           my $code = shift;
383              
384 0   0       my $lang = $self->{_state}->{lang} || $DefaultLanguage;
385 0   0       return ${$self->{messages}}{$lang}->{$code} || "*** NO MESSAGE $lang/$code ***";
386             }
387              
388             sub _init_state
389             {
390 0     0     my $self = shift;
391              
392 0 0         $self->{pops} && $self->{pops}->quit;
393 0 0         $self->{e} && $self->{e}->purge;
394              
395 0           $self->{trunc} = undef;
396 0           $self->{mime} = undef;
397 0           $self->{body} = undef;
398 0           $self->{pops} = undef;
399 0           $self->{msg} = undef;
400 0           $self->{num} = undef;
401 0           $self->{e} = undef;
402              
403 0           $self->{part} = 1;
404             }
405              
406             sub _fetch_state
407             {
408 0     0     my $self = shift;
409 0           my $source = shift;
410 0 0         warn "Email: Fetch state for $source\n" if $Debug;
411 0           $self->{_state} = $self->{state}->{$source};
412 0 0         $self->{_state} || $self->fixup_state($source);
413 0   0       $self->{_state} ||= {};
414             }
415              
416             sub _store_state
417             {
418 0     0     my $self = shift;
419 0           my $source = shift;
420 0 0         warn "Email: Store state for $source\n" if $Debug;
421 0           $self->{state}->{$source} = $self->{_state};
422             }
423              
424             sub _canon_ref
425             {
426 0     0     my $ref = shift;
427 0 0         $$ref = lc $$ref unless $$ref =~ m/[[:lower:]]/;
428 0           return $ref;
429             }
430              
431             sub init
432             {
433 0     0 0   my $self = shift;
434 0           %Languages = abbrev keys %SupportedLanguages;
435 0           $self->{abbrevs} = { abbrev keys %{$self->{cmds}} };
  0            
436              
437             # These are used to convert messages back
438             # to plain ASCII
439              
440 0           $self->{map} = Unicode::Map8->new('ASCII');
441              
442 0           for my $m (
443             [ ' ', [ 160 ] ], [ '!', [ 161 ] ], [ 'c', [ 162, 231 ] ],
444             [ 'L', [ 163 ] ], [ '*', [ 164, 188 .. 190 ] ],
445             [ 'Y', [ 165 ] ], [ '|', [ 166 ] ], [ 'S', [ 167 ] ],
446             [ '^', [ 168 ] ], [ 'C', [ 169, 199 ] ],
447             [ 'a', [ 170, 224 .. 230 ] ], [ '<', [ 171 ] ],
448             [ '!', [ 172 ] ], [ '-', [ 173, 175 ] ], [ 'R', [ 174 ] ],
449             [ 'o', [ 176, 186 ] ], [ '+', [ 177 ] ], [ '2', [ 178 ] ],
450             [ '3', [ 179 ] ], [ 'u', [ 181 ] ], [ 'P', [ 182, 222, 254 ] ],
451             [ '.', [ 183 ] ], [ ',', [ 184 ] ], [ '1', [ 185 ] ],
452             [ '>', [ 187 ] ], [ '?', [ 191 ] ], [ 'A', [ 192 .. 198 ] ],
453             [ 'E', [ 200 .. 203, 208 ] ], [ 'I', [ 204 .. 207 ] ],
454             [ 'N', [ 209 ] ], [ 'O', [ 210 .. 214, 216 ] ],
455             [ 'x', [ 215 ] ], [ 'U', [ 217 .. 220 ] ], [ 'Y', [ 221 ] ],
456             [ 'B', [ 223 ] ], [ 'e', [ 232 .. 235, 240 ] ],
457             [ 'i', [ 236 .. 239 ] ], [ 'n', [ 241 ] ],
458             [ 'o', [ 242 .. 246, 248 ] ], [ '/', [ 247 ] ],
459             [ 'u', [ 249 .. 251 ] ], [ 'y', [ 252, 255 ] ],
460             )
461             {
462 0           for (@{$m->[1]})
  0            
463             {
464 0           $self->{map}->addpair($_, ord($m->[0]));
465             }
466             }
467              
468 0           $self->{map}->default_to16(ord('?'));
469 0           $self->{map}->default_to8(ord('?'));
470 0           return $self;
471             }
472              
473             =pod
474              
475             The following methods are provided:
476              
477             =over 4
478              
479             =item C<-Enew()>
480              
481             Creates a new C object. It accepts parameters as a
482             number of key / value pairs. The following parameters are supported.
483              
484             =over 2
485              
486             =item C $queue_obj>
487              
488             An object obeying the interface defined in L, where the
489             response message generated by this module will be stored.
490              
491             =item C $ref_to_hash>
492              
493             Reference to a (potentially Cd) hash where state about the user
494             will be stored. Passwords will be stored in this hash, under the
495             protection of reversible crypto. Therefore, care must be taken to
496             prevent unauthorized access to this.
497              
498             =item C $my_secret_phrase>
499              
500             A secret phrase used to obscure the passwords stored for the users.
501              
502             =item C $my_addr>
503              
504             The address assigned to this service, in B format. The
505             destination address of the SMS, must match this argument. If this
506             address is left unspecified, the SMS will be accepted no matter what
507             destination address is used.
508              
509             =item C $your_pop_server>
510              
511             The name or IP address of the POP server.
512              
513             =item C $your_smtp_server>
514              
515             The name or IP address of the SMTP server.
516              
517             =item C $max_sms_length>
518              
519             Maximum length of an SMS. Defaults to 160.
520              
521             =item C $max_message_length>
522              
523             The amount of bytes to fetch from the body of the email. Defaults to
524             1024 bytes.
525              
526             =item C $fold_whitespace>
527              
528             If set to a true value (the default) forces successions of whitespace
529             to be folded into single spaces. This generally improves readability
530             of the SMS.
531              
532             =item C $obj>
533              
534             If passed, this is assumed to be an object supporting a
535             C<-Echeck()> method as described in L. This is
536             used to test fetched messages for SPAM-iness.
537              
538             =item C $hashref>
539              
540             Allows the specification of a new command table which overrides the
541             default.
542              
543             =item C $max>
544              
545             Defines the maximum number of messages to check for spamminess for
546             each B command. Defaults to test all the messages. Note that
547             checking a large number of messages at once can take very long.
548              
549             =back
550              
551             =cut
552              
553             sub new
554             {
555 0     0 1   my $name = shift;
556 0   0       my $class = ref($name) || $name;
557              
558             my %self = validate_with
559             (
560             params => \@_,
561             ignore_case => 1,
562             strip_leading => '-',
563             spec =>
564             {
565             queue =>
566             {
567             type => OBJECT,
568             can => [ qw(store) ],
569             },
570             state =>
571             {
572             type => HASHREF,
573             },
574             secret =>
575             {
576             type => SCALAR,
577             },
578             addr =>
579             {
580             type => SCALAR,
581             default => undef,
582             callbacks =>
583             {
584 0     0     'address format' => sub { $_[0] =~ /^\d+\.\d+\.\d+$/; }
585             }
586             },
587             pop =>
588             {
589             type => SCALAR,
590             },
591             smtp =>
592             {
593             type => SCALAR,
594             },
595             maxlen =>
596             {
597             type => SCALAR,
598             default => 160,
599             },
600             maxfetch =>
601             {
602             type => SCALAR,
603             default => 1024,
604             },
605             compact =>
606             {
607             type => SCALAR,
608             default => 1,
609             },
610             spamcheck =>
611             {
612             type => OBJECT,
613             default => undef,
614             can => [ qw(check) ],
615             },
616             spammax =>
617             {
618             type => SCALAR,
619             default => undef,
620             callbacks =>
621             {
622             'must be possitive' => sub {
623 0     0     $_[0] > 0;
624             },
625             },
626             },
627 0           cmds =>
628             {
629             type => HASHREF,
630             default =>
631             {
632             ACCOUNT => \&_CMD_ACCOUNT,
633             ALIAS => \&_CMD_ALIAS,
634             CHECK => \&_CMD_CHECK,
635             SEND => \&_CMD_SEND,
636             INTERFACE => \&_CMD_INTERFACE,
637             LIST => \&_CMD_LIST,
638             DELETE => \&_CMD_DELETE,
639             REPLY => \&_CMD_REPLY,
640             ALL => \&_CMD_REPLY_ALL,
641             FORWARD => \&_CMD_FORWARD,
642             GET => \&_CMD_GET,
643             HELP => \&_CMD_HELP,
644             }
645             },
646             help =>
647             {
648             type => HASHREF,
649             default => \%Help
650             },
651             messages =>
652             {
653             type => HASHREF,
654             default => \%Messages
655             }
656             }
657             );
658 0 0         if ($self{addr})
659             {
660 0           ($self{ton}, $self{npi}, $self{number}) = split(/\./, $self{addr}, 3);
661             }
662              
663 0           $self{mp} = new MIME::Parser;
664 0           $self{mp}->ignore_errors(1);
665 0           $self{mp}->extract_uuencode(1);
666              
667 0           $self{body} = '';
668 0           $self{head} = undef;
669 0           $self{wd} = undef;
670 0           $self{part} = 0;
671              
672 0           my $ret = bless \%self, $class;
673              
674             $self{parser} =
675             {
676             'text/html' =>
677             HTML::Parser->new
678             (
679             api_version => 3,
680             default_h => [ "" ],
681             start_h =>
682             [ sub
683             {
684 0     0     my $p = shift;
685 0           my $tag = shift;
686 0           my $attr = shift;
687            
688 0 0         return unless ($tag eq 'img');
689 0           $ret->{body} .= '[IMG';
690 0 0         $ret->{body} .= ' ' . $attr->{alt} if $attr->{alt};
691 0           $ret->{body} .= ']';
692            
693 0 0         $p->eof if length($ret->{body})
694             > $ret->{part} * $ret->{maxfetch};
695             },
696             "self, tagname, attr" ],
697            
698             text_h =>
699             [ sub
700             {
701 0     0     my $p = shift;
702 0           $ret->{body} .= shift;
703 0 0         $p->eof if length($ret->{body})
704             > $ret->{part} * $ret->{maxfetch};
705             },
706 0           "self, dtext" ],
707             ),
708             };
709              
710 0           $ret->{parser}->{'text/html'}->ignore_elements(qw(script style));
711 0           $ret->{parser}->{'text/html'}->strict_comment(1);
712 0           return $ret->init;
713             }
714              
715             =pod
716              
717             =item C<-Ehandle()>
718              
719             Process the given SMS. Commands are taken from a dispatch table and
720             appropiate handlers are called. Commands must be in the first line of
721             the SMS.
722              
723             An exception to this rule, is the fancy syntax supported by some
724             phones, that looks like
725              
726             you@some.where(subject)this is the message body
727             you@some.where (subject) this is the message body
728             you(subject)this is the message body
729             you (subject) this is the message body
730              
731             This syntax is transparently converted to our command based syntax.
732              
733             =cut
734              
735             sub handle
736             {
737 0     0 1   my $self = shift;
738 0           my $hsms = shift;
739              
740 0 0         warn "Email: handle for ", $hsms->{source_addr}, "\n" if $Debug;
741              
742 0           $self->fixup_sms($hsms);
743              
744 0           return $self->SUPER::handle($hsms, @_);
745             }
746              
747             =pod
748              
749             =item C<-Edispatch_error>
750              
751             Produce an error when a given command does not exist. Causes the
752             current SMS to be discarded from the queue.
753              
754             =cut
755              
756             sub dispatch_error
757             {
758 0     0 1   my $self = shift;
759 0           my $hsms = shift;
760 0           my $source = shift;
761 0           my $msg = shift;
762              
763 0 0         warn "Email: command not understood in $$msg\n" if $Debug;
764 0           $self->_answer($hsms, \ ($self->_msg('HANDLE_CMD_ERR')
765             . " <$$msg>"));
766 0           return SMS_STOP | SMS_DEQUEUE;
767             }
768              
769             =pod
770              
771             =item C<-E_CMD_ACCOUNT>
772              
773             Handler method for the ACCOUNT command. Note that access to the
774             underlying hash (C<$self-E{state}>) must be done in a manner that
775             C likes, as most likely the passed hash is tied using this
776             class.
777              
778             Also, the implementation must assume that other items of state
779             information might be stored in that hash. Those items should be
780             preserved.
781              
782             =cut
783              
784             sub _CMD_ACCOUNT
785             {
786 0     0     my $self = shift;
787 0           my $hsms = shift;
788 0           my $source = shift;
789 0           my $r_msg = shift;
790 0           my $r_body = shift;
791              
792             # warn "# I'm here with msg = $$r_msg\n";
793             # warn "# I'm here with body = $$r_body\n";
794              
795 0           $$r_msg =~ s/^\.\w+\s+(\S+)\s+(.+)\s*//;
796              
797 0 0 0       return unless defined $1 and defined $2;
798              
799 0 0         warn "Email: account map $source -> $1\n" if $Debug;
800 0           $self->_init_state;
801 0           $self->_fetch_state($source);
802 0           $self->{_state}->{login} = $1;
803 0           $self->{_state}->{passwd} = $self->_crypt($2);
804 0           $self->{_state}->{ac_time} = time;
805 0           $self->_store_state($source);
806 0           $self->_init_state;
807 0           $self->_answer($hsms, \ ($self->{_state}->{login}
808             . ' ' . $self->_msg('ACCOUNT_OK')));
809 0           return 1;
810             }
811              
812             =pod
813              
814             =item C<-E_CMD_ALIAS>
815              
816             Handler method for the ALIAS command.
817              
818             =cut
819              
820             sub _CMD_ALIAS
821             {
822 0     0     my $self = shift;
823 0           my $hsms = shift;
824 0           my $source = shift;
825 0           my $r_msg = shift;
826 0           my $r_body = shift;
827              
828 0           $$r_msg =~ s/^\.\w+//;
829 0           $$r_msg =~ s/^(\s+([^\s]+))?\s+(\w+)\s*//;
830              
831 0 0         return unless defined $1;
832              
833 0           my $alias = lc $3;
834 0           my $email = lc $1;
835            
836 0 0         warn "Email: _CMD_ALIAS $alias $email\n" if $Debug;
837              
838 0           $self->_init_state;
839 0           $self->_fetch_state($source);
840              
841 0 0         unless ($self->_authen($hsms, $source))
842             {
843 0 0         warn "Email: authentication failed for $source\n" if $Debug;
844 0           return;
845             }
846              
847 0 0         if ($email)
848             {
849 0           $self->{_state}->{alias}->{$alias} = $email;
850 0 0         warn "Email: stored $alias => $email\n" if $Debug;
851             }
852             else
853             {
854 0           delete $self->{_state}->{alias}->{$alias};
855 0 0         warn "Email: deleted $alias\n" if $Debug;
856             }
857              
858 0           $self->_store_state($source);
859              
860 0 0         $self->_answer($hsms, \ ($alias . ' ' . $self->_msg('ALIAS_OK')
861             . ". $self->{num} " .
862             ($self->{num} == 1 ?
863             $self->_msg('MSG_COUNT_ONE') :
864             $self->_msg('MSG_COUNT_MANY'))));
865 0           $self->_init_state;
866 0           return 1;
867             }
868             =pod
869              
870             =item C<-E_CMD_INTERFACE>
871              
872             Handler for setting the interface language.
873              
874             =cut
875              
876             sub _CMD_INTERFACE
877             {
878 0     0     my $self = shift;
879 0           my $hsms = shift;
880 0           my $source = shift;
881 0           my $r_msg = shift;
882 0           my $r_body = shift;
883              
884             # warn "# I'm here with msg = $$r_msg\n";
885             # warn "# I'm here with body = $$r_body\n";
886            
887 0           $$r_msg =~ s/^\.\w+//;
888 0           $$r_msg =~ s/^\s+(\S+)\s*//;
889              
890 0 0         return unless defined $1;
891              
892 0           $self->_init_state;
893 0           $self->_fetch_state($source);
894              
895 0 0         unless (exists $SupportedLanguages{$Languages{uc $1}})
896             {
897 0 0         warn "Email: unsupported language map $source -> $1\n" if $Debug;
898 0           return 0;
899             }
900              
901 0           my $lang = $SupportedLanguages{$Languages{uc $1}};
902              
903 0 0         warn "Email: language map $source -> $lang\n" if $Debug;
904              
905 0           $self->{_state}->{lang} = $lang;
906 0           $self->_store_state($source);
907            
908 0           $self->_answer($hsms, \ ( $self->_msg('LANG_OK') . " <$lang>"));
909 0           $self->_init_state;
910 0           return 1;
911             }
912              
913             =pod
914              
915             =item C<-E_CMD_SEND>
916              
917             Handler method for the SEND command.
918              
919             =cut
920              
921             sub _CMD_SEND
922             {
923 0     0     my $self = shift;
924 0           my $hsms = shift;
925 0           my $source = shift;
926 0           my $r_msg = shift;
927 0           my $r_body = shift;
928              
929 0           $$r_msg =~ s/^\.\w+//;
930 0           $$r_msg =~ s/^\s+(\S+)\s*(.*)\s*$//;
931              
932 0 0 0       return unless defined $1 and defined $2;
933              
934 0           my $to = $1;
935 0           my $subject = $2;
936              
937 0 0         warn "Email: _CMD_SEND $to $subject\n" if $Debug;
938              
939 0           $self->_init_state;
940 0           $self->_fetch_state($source);
941              
942 0           $self->{body} = $ { _canon_ref($r_body)};
  0            
943              
944 0 0 0       if ($self->_authen($hsms, $source)
945             and $self->_deliver($hsms, $source, $self->_expanded_addresses($to),
946             $subject))
947             {
948 0 0         warn "Email: send from $source to $to ok with $self->{num} msgs\n"
949             if $Debug;
950 0 0         if ($self->{num} == 1)
951             {
952 0           $self->_answer($hsms, \ ( $self->_msg('SEND_OK')
953             . ". $self->{num} "
954             . $self->_msg('MSG_COUNT_ONE')));
955             }
956             else
957             {
958 0           $self->_answer($hsms, \ ( $self->_msg('SEND_OK')
959             . ". $self->{num} "
960             . $self->_msg('MSG_COUNT_MANY')));
961             }
962 0           $self->_init_state;
963 0           return 1;
964             }
965              
966 0           warn "Email: deliver for $source failed\n";
967 0           $self->_init_state;
968 0           return;
969             }
970              
971             =pod
972              
973             =item C<-E_CMD_LIST>
974              
975             Handler method for the LIST command.
976              
977             =cut
978              
979             sub _CMD_LIST
980             {
981 0     0     my $self = shift;
982 0           my $hsms = shift;
983 0           my $source = shift;
984 0           my $r_msg = shift;
985 0           my $r_body = shift;
986              
987 0           $$r_msg =~ s/^\.\w+\s*//;
988              
989 0           $self->_init_state;
990 0           $self->_fetch_state($source);
991              
992 0 0         if (exists $self->{_state}->{login})
993             {
994 0 0         if ($self->_authen($hsms, $source))
995             {
996 0           my $last = $self->{pops}->last;
997 0 0         warn "Email: list with $self->{num} msgs\n" if $Debug;
998 0 0         if ($self->{num} == 1)
999             {
1000 0 0         $self->_answer($hsms,
1001             \ ($self->{num} . " " .
1002             $self->_msg('MSG_COUNT_ONE')
1003             . ($last ? ". "
1004             . $self->_msg('MSG_COUNT_LAST')
1005             . " $last" : '')));
1006             }
1007             else
1008             {
1009 0 0         $self->_answer($hsms,
1010             \ ($self->{num} . " " .
1011             $self->_msg('MSG_COUNT_MANY')
1012             . ($last ? ". "
1013             . $self->_msg('MSG_COUNT_LAST')
1014             . " $last" : '')));
1015             }
1016 0           $self->_init_state;
1017 0           return 1;
1018             }
1019              
1020 0 0         warn "Email: authentication failed for $source\n" if $Debug;
1021 0           $self->_init_state;
1022 0           return;
1023             }
1024             else
1025             {
1026 0 0         warn "Email: No account for $source\n" if $Debug;
1027 0           $self->_answer($hsms, \ ($self->_msg('LIST_NO_REG')));
1028 0           $self->_init_state;
1029 0           return;
1030             }
1031             }
1032              
1033             =pod
1034              
1035             =item C<-E_CMD_HELP>
1036              
1037             Handler method for the HELP command. Sends a SMS message for each
1038             supported command, containing the help messages defined.
1039              
1040             =cut
1041              
1042             sub _CMD_HELP
1043             {
1044 0     0     my $self = shift;
1045 0           my $hsms = shift;
1046 0           my $source = shift;
1047 0           my $r_msg = shift;
1048 0           my $r_body = shift;
1049              
1050 0           $$r_msg =~ s/^\.\w+//;
1051 0           $$r_msg =~ s/^(\s+(\w+))?\s*//;
1052              
1053 0           my $cmd = $2;
1054              
1055 0 0         warn "Email: $source wants help on ",
    0          
1056             (defined $cmd ? $cmd : 'all commands '),
1057             "\n" if $Debug;
1058              
1059 0           $self->_init_state;
1060 0           $self->_fetch_state($source);
1061              
1062 0   0       my $lang = $self->{_state}->{lang} || $DefaultLanguage;
1063 0           my @list;
1064              
1065 0 0         if ($cmd)
1066             {
1067 0           $cmd = uc $cmd;
1068 0 0         $cmd = $self->{abbrevs}->{$cmd} unless exists $self->{cmds}->{$cmd};
1069 0           @list = grep { $cmd eq $_ } keys %{ $self->{cmds} };
  0            
  0            
1070             }
1071              
1072 0 0         @list = keys %{$self->{help}} unless @list;
  0            
1073              
1074 0           $self->_answer($hsms, \ ($_ . "\n" .
1075 0           ${$self->{help}}{$_}->{$lang})) for (sort @list);
1076              
1077 0           $self->_init_state;
1078 0           return 1;
1079             }
1080              
1081             =pod
1082              
1083             =item C<-E_CMD_DELETE>
1084              
1085             Handler method for the DELETE command.
1086              
1087             =cut
1088              
1089             sub _CMD_DELETE
1090             {
1091 0     0     my $self = shift;
1092 0           my $hsms = shift;
1093 0           my $source = shift;
1094 0           my $r_msg = shift;
1095 0           my $r_body = shift;
1096              
1097 0           $$r_msg =~ s/^\.\w+//;
1098 0           $$r_msg =~ s/^\s*(\S+)?\s*//;
1099              
1100 0           $self->_init_state;
1101 0           $self->_fetch_state($source);
1102 0   0       $self->{msg} = $1 || $self->{num} || 1;
1103            
1104 0 0 0       if ($self->_dele($hsms, $source)
1105             and $self->_quit($hsms, $source))
1106             {
1107 0 0         warn "Email: $source delete $source $self->{msg}\n" if $Debug;
1108 0 0         if ($self->{num} == 1)
1109             {
1110 0           $self->_answer($hsms, \ ( $self->{msg} . " "
1111             . $self->_msg('DELETE_OK')
1112             . ". $self->{num} "
1113             . $self->_msg('MSG_COUNT_ONE')));
1114             }
1115             else
1116             {
1117 0           $self->_answer($hsms, \ ( $self->{msg} . " "
1118             . $self->_msg('DELETE_OK')
1119             . ". $self->{num} "
1120             . $self->_msg('MSG_COUNT_MANY')));
1121             }
1122 0           $self->_init_state;
1123 0           return 1;
1124             }
1125 0           $self->_init_state;
1126 0           return;
1127             }
1128              
1129              
1130              
1131             =pod
1132              
1133             =item C<-E_CMD_REPLY>
1134              
1135             Handler method for the REPLY command.
1136              
1137             =cut
1138              
1139             sub _CMD_REPLY
1140             {
1141 0     0     my $self = shift;
1142 0           my $hsms = shift;
1143 0           my $source = shift;
1144 0           my $r_msg = shift;
1145 0           my $r_body = shift;
1146              
1147 0           $$r_msg =~ s/^\.\w+//;
1148 0           $$r_msg =~ s/^(\s*!)?\s*(\d+)?\s*//;
1149              
1150             # Fetch the selected message
1151 0           $self->_init_state;
1152 0           $self->_fetch_state($source);
1153              
1154 0           $self->{trunc} = !$1;
1155 0           $self->{mime} = $1;
1156 0           $self->{msg} = $2;
1157              
1158 0 0         if (defined $self->{msg})
1159             {
1160 0   0       $self->{msg} ||= 1;
1161             }
1162             else
1163             {
1164 0           $self->_authen($hsms, $source);
1165 0   0       $self->{msg} ||= $self->{num} || 1;
      0        
1166             }
1167              
1168            
1169 0 0         if ($self->_fetch($hsms, $source))
1170             {
1171            
1172 0   0       my $from = $self->d_m($self->{head}->get('From'))
1173             || $self->_msg('REPLY_YOU');
1174              
1175 0           my $sub = $self->d_m($self->{head}->get('Subject'));
1176              
1177 0           my $text = '';
1178            
1179 0           substr($text, 0, 0, "\n");
1180 0           substr($text, 0, 0, $sub);
1181 0           substr($text, 0, 0, 'Subject: ');
1182              
1183 0           substr($text, 0, 0, $self->{head}->get('Date'));
1184 0           substr($text, 0, 0, 'Date: ');
1185              
1186 0 0         if ($self->{head}->get('Cc'))
1187             {
1188 0           substr($text, 0, 0, $self->d_m($self->{head}->get('Cc')));
1189 0           substr($text, 0, 0, 'Cc: ');
1190             }
1191              
1192 0 0         if ($self->{head}->get('To'))
1193             {
1194 0           substr($text, 0, 0, $self->d_m($self->{head}->get('To')));
1195 0           substr($text, 0, 0, 'To: ');
1196             }
1197              
1198 0           substr($text, 0, 0, $from);
1199 0           substr($text, 0, 0, 'From: ');
1200              
1201 0           chomp $from;
1202              
1203 0           substr($text, 0, 0, "\n\n");
1204 0           substr($text, 0, 0, $self->_msg('REPLY_SAID'));
1205 0           substr($text, 0, 0, "$from ");
1206              
1207 0           substr($text, 0, 0, "\n\n\n");
1208 0           substr($text, 0, 0, $ {_canon_ref($r_body)});
  0            
1209              
1210 0           substr($sub, 0, 0, "Re: ");
1211              
1212 0           $self->{e}->add_part
1213             (MIME::Entity->build(Type => 'text/plain',
1214             Data => $text),
1215             0);
1216 0           $self->{e}->sync_headers(Length => 'COMPUTE');
1217              
1218 0 0         if ($self->{mime})
1219             {
1220 0           $self->{body} = '';
1221             }
1222             else
1223             {
1224 0           $self->_truncate($hsms, $source);
1225             }
1226              
1227 0 0 0       if ($self->_deliver($hsms, $source,
1228             _addresses($self->{head}->get('Reply-To')
1229             || $self->{head}->get('From')),
1230             $sub))
1231             {
1232 0 0         warn "Email: $source reply $self->{msg}\n" if $Debug;
1233 0           $self->_answer($hsms, \ ( $self->{msg} . " "
1234             . $self->_msg('REPLY_OK')));
1235 0           $self->_init_state;
1236 0           return 1;
1237             }
1238             }
1239            
1240 0 0         warn "Email: Failed $source reply $self->{msg}\n" if $Debug;
1241 0           $self->_init_state;
1242 0           return 0;
1243             }
1244              
1245             =pod
1246              
1247             =item C<-E_CMD_REPLY_ALL>
1248              
1249             Handler method for the ALL command.
1250              
1251             =cut
1252              
1253             sub _CMD_REPLY_ALL
1254             {
1255 0     0     my $self = shift;
1256 0           my $hsms = shift;
1257 0           my $source = shift;
1258 0           my $r_msg = shift;
1259 0           my $r_body = shift;
1260              
1261 0           $$r_msg =~ s/^\.\w+//;
1262 0           $$r_msg =~ s/^(\s*!)?\s*(\d+)?\s*//;
1263              
1264             # Fetch the selected message
1265 0           $self->_init_state;
1266 0           $self->_fetch_state($source);
1267              
1268 0           $self->{trunc} = !$1;
1269 0           $self->{mime} = $1;
1270 0           $self->{msg} = $2;
1271              
1272 0 0         if (defined $self->{msg})
1273             {
1274 0   0       $self->{msg} ||= 1;
1275             }
1276             else
1277             {
1278 0           $self->_authen($hsms, $source);
1279 0   0       $self->{msg} ||= $self->{num} || 1;
      0        
1280             }
1281              
1282 0 0         if ($self->_fetch($hsms, $source))
1283             {
1284            
1285 0   0       my $from = $self->d_m($self->{head}->get('From'))
1286             || $self->_msg('REPLY_YOU');
1287              
1288 0           my $sub = $self->d_m($self->{head}->get('Subject'));
1289              
1290 0           my $text = '';
1291            
1292 0           substr($text, 0, 0, "\n");
1293 0           substr($text, 0, 0, $sub);
1294 0           substr($text, 0, 0, 'Subject: ');
1295              
1296 0           substr($text, 0, 0, $self->{head}->get('Date'));
1297 0           substr($text, 0, 0, 'Date: ');
1298              
1299 0 0         if ($self->{head}->get('Cc'))
1300             {
1301 0           substr($text, 0, 0, $self->d_m($self->{head}->get('Cc')));
1302 0           substr($text, 0, 0, 'Cc: ');
1303             }
1304              
1305 0 0         if ($self->{head}->get('To'))
1306             {
1307 0           substr($text, 0, 0, $self->d_m($self->{head}->get('To')));
1308 0           substr($text, 0, 0, 'To: ');
1309             }
1310              
1311 0           substr($text, 0, 0, $from);
1312 0           substr($text, 0, 0, 'From: ');
1313              
1314 0           chomp $from;
1315              
1316 0           substr($text, 0, 0, "\n\n");
1317 0           substr($text, 0, 0, $self->_msg('REPLY_SAID'));
1318 0           substr($text, 0, 0, "$from ");
1319              
1320 0           substr($text, 0, 0, "\n\n\n");
1321 0           substr($text, 0, 0, $ {_canon_ref($r_body)});
  0            
1322              
1323 0           substr($sub, 0, 0, "Re: ");
1324              
1325 0           $self->{e}->add_part
1326             (MIME::Entity->build(Type => 'text/plain',
1327             Data => $text),
1328             0);
1329 0           $self->{e}->sync_headers(Length => 'COMPUTE');
1330              
1331 0 0         if ($self->{mime})
1332             {
1333 0           $self->{body} = '';
1334             }
1335             else
1336             {
1337 0           $self->_truncate($hsms, $source);
1338             }
1339              
1340 0 0 0       if ($self->_deliver($hsms, $source,
    0          
1341             _addresses(($self->{head}->get('Reply-To')
1342             || $self->{head}->get('From'))
1343             . ($self->{head}->get('Cc') ?
1344             ',' . $self->{head}->get('Cc') :
1345             '')),
1346             $sub))
1347             {
1348 0 0         warn "Email: $source reply $self->{msg}\n" if $Debug;
1349 0           $self->_answer($hsms, \ ( $self->{msg} . " "
1350             . $self->_msg('REPLY_OK')));
1351 0           $self->_init_state;
1352 0           return 1;
1353             }
1354             }
1355            
1356 0 0         warn "Email: Failed $source reply $self->{msg}\n" if $Debug;
1357 0           $self->_init_state;
1358 0           return 0;
1359             }
1360              
1361             =pod
1362              
1363             =item C<-E_CMD_FORWARD>
1364              
1365             Handler method for the FORWARD command.
1366              
1367             =cut
1368              
1369             sub _CMD_FORWARD
1370             {
1371 0     0     my $self = shift;
1372 0           my $hsms = shift;
1373 0           my $source = shift;
1374 0           my $r_msg = shift;
1375 0           my $r_body = shift;
1376              
1377 0           $$r_msg =~ s/^\.\w+//;
1378 0           $$r_msg =~ s/^(\s*!)?(\s+(\d+))?\s*(\S+)\s*$//;
1379              
1380 0 0         return unless defined $4;
1381              
1382             # Fetch the selected message
1383 0           $self->_fetch_state($source);
1384 0           $self->_init_state;
1385              
1386 0           $self->{mime} = $1;
1387 0           $self->{trunc} = !$1;
1388 0           $self->{msg} = $3;
1389 0           my $to = $4;
1390              
1391 0 0         if (defined $self->{msg})
1392             {
1393 0   0       $self->{msg} ||= 1;
1394             }
1395             else
1396             {
1397 0           $self->_authen($hsms, $source);
1398 0   0       $self->{msg} ||= $self->{num} || 1;
      0        
1399             }
1400              
1401 0 0         if ($self->_fetch($hsms, $source))
1402             {
1403 0   0       my $from = $self->d_m($self->{head}->get('From'))
1404             || $self->_msg('REPLY_YOU');
1405              
1406 0           my $sub = $self->d_m($self->{head}->get('Subject'));
1407              
1408 0           my $text = '';
1409            
1410 0           substr($text, 0, 0, "\n");
1411 0           substr($text, 0, 0, $sub);
1412 0           substr($text, 0, 0, 'Subject: ');
1413              
1414 0           substr($text, 0, 0, $self->{head}->get('Date'));
1415 0           substr($text, 0, 0, 'Date: ');
1416              
1417 0 0         if ($self->{head}->get('Cc'))
1418             {
1419 0           substr($text, 0, 0, $self->d_m($self->{head}->get('Cc')));
1420 0           substr($text, 0, 0, 'Cc: ');
1421             }
1422              
1423 0 0         if ($self->{head}->get('To'))
1424             {
1425 0           substr($text, 0, 0, $self->d_m($self->{head}->get('To')));
1426 0           substr($text, 0, 0, 'To: ');
1427             }
1428              
1429 0           substr($text, 0, 0, $from);
1430 0           substr($text, 0, 0, 'From: ');
1431              
1432 0           chomp $from;
1433              
1434 0           substr($text, 0, 0, "\n\n");
1435 0           substr($text, 0, 0, $self->_msg('REPLY_SAID'));
1436 0           substr($text, 0, 0, "$from ");
1437              
1438 0           substr($text, 0, 0, "\n\n\n");
1439 0           substr($text, 0, 0, $ { _canon_ref($r_body)});
  0            
1440              
1441 0           substr($sub, 0, 0, "Fwd: ");
1442              
1443 0           my %mime = ();
1444              
1445 0           $self->{e}->add_part(MIME::Entity->build(Type => 'text/plain',
1446             Data => $text),
1447             0);
1448 0           $self->{e}->sync_headers(Length => 'COMPUTE');
1449              
1450 0 0         if ($self->{mime})
1451             {
1452 0           $self->{body} = '';
1453             }
1454             else
1455             {
1456 0           $self->_translate($hsms, $source);
1457 0           $self->_truncate($hsms, $source);
1458             }
1459              
1460 0 0         if ($self->_deliver($hsms, $source,
1461             $self->_expanded_addresses($to), $sub))
1462             {
1463 0 0         warn "Email: $source forward $self->{msg}\n" if $Debug;
1464 0           $self->_answer($hsms, \ ( $self->{msg} . " " .
1465             $self->_msg('FWD_OK')));
1466 0           $self->_init_state;
1467 0           return 1;
1468             }
1469             }
1470            
1471 0 0         warn "Email: Failed $source forward $self->{msg}\n" if $Debug;
1472 0           $self->_init_state;
1473 0           return 0;
1474             }
1475              
1476             =pod
1477              
1478             =item C<-E_CMD_GET>
1479              
1480             Handler method for the GET command.
1481            
1482             =cut
1483              
1484             sub _CMD_GET
1485             {
1486 0     0     my $self = shift;
1487 0           my $hsms = shift;
1488 0           my $source = shift;
1489 0           my $r_msg = shift;
1490 0           my $r_body = shift;
1491              
1492 0           $$r_msg =~ s/^\.\w+//;
1493 0           $$r_msg =~ s/^(\s+(\d+))?(\s+(\d+))?\s*//;
1494              
1495 0           $self->_init_state;
1496 0           $self->_fetch_state($source);
1497              
1498 0           $self->{msg} = $2;
1499 0   0       $self->{part} = $4 || 1;
1500              
1501 0 0         if (defined $self->{msg})
1502             {
1503 0   0       $self->{msg} ||= 1;
1504             }
1505             else
1506             {
1507 0           $self->_authen($hsms, $source);
1508 0   0       $self->{msg} ||= $self->{num} || 1;
      0        
1509             }
1510              
1511 0 0 0       if ($self->_fetch($hsms, $source)
      0        
1512             and $self->_translate($hsms, $source)
1513             and $self->_truncate($hsms, $source))
1514             {
1515             # Place the required headers in the email,
1516             # in as compact a way as possible.
1517              
1518 0           substr($self->{body}, 0, 0, "\n");
1519 0           substr($self->{body}, 0, 0, $self->d_m($self->{head}->get('Subject')));
1520 0           substr($self->{body}, 0, 0, " ");
1521 0           substr($self->{body}, 0, 0, $self->_msg('HEAD_SUBJECT'));
1522            
1523 0           substr($self->{body}, 0, 0,
1524             strftime "%d/%m/%y %H:%M\n",
1525             localtime(str2time($self->{head}->get('Date'))));
1526 0           substr($self->{body}, 0, 0, " ");
1527 0           substr($self->{body}, 0, 0, $self->_msg('HEAD_DATE'));
1528            
1529 0           substr($self->{body}, 0, 0, $self->d_m($self->{head}->get('From')));
1530 0           substr($self->{body}, 0, 0, " ");
1531 0           substr($self->{body}, 0, 0, $self->_msg('HEAD_FROM'));
1532 0 0         if ($self->{head}->get('X-SMS-From'))
1533             {
1534 0           substr($self->{body}, 0, 0,
1535             $self->{head}->get('X-SMS-From'));
1536 0           substr($self->{body}, 0, 0, " ");
1537 0           substr($self->{body}, 0, 0, $self->_msg('HEAD_PHONE'));
1538             }
1539              
1540 0 0         warn "Email: Get $self->{msg}\n" if $Debug;
1541 0           $self->_answer($hsms, \$self->{body});
1542 0           $self->_init_state;
1543 0           return 1;
1544             }
1545 0           $self->_init_state;
1546 0           return 0;
1547             }
1548              
1549              
1550             =pod
1551              
1552             =item C<-E_CMD_CHECK>
1553              
1554             Handler method for the CHECK command. Only makes sense if
1555             C<-Enew()> is called with B defined. Currently a noop.
1556              
1557             =cut
1558              
1559             sub _CMD_CHECK
1560             {
1561 0     0     my $self = shift;
1562 0           my $hsms = shift;
1563 0           my $source = shift;
1564 0           my $r_msg = shift;
1565 0           my $r_body = shift;
1566              
1567 0           $$r_msg =~ s/^\.\w+//;
1568 0           $$r_msg =~ s/^(\s+(!))?\s*//;
1569              
1570 0           warn "Email: CHECK: Temporarily out of order\n";
1571 0           return;
1572              
1573             }
1574              
1575             ###########################################
1576             # Utility functions related to SMS handling
1577             ###########################################
1578              
1579             # Decode a string
1580              
1581             sub d_r
1582             {
1583 0     0 0   my $self = shift;
1584              
1585 0           $self->{body} = $self->{map}->to8
1586             ($self->{map}->to16($self->{wd}->decode($self->{body})));
1587              
1588 0           return 1;
1589             }
1590              
1591             sub d_t
1592             {
1593 0     0 0   my $self = shift;
1594              
1595 0   0       return $self->{map}->to8
1596             ($self->{map}->to16($self->{wd}->decode(shift||'')));
1597              
1598             }
1599              
1600             sub d_m
1601             {
1602 0     0 0   my $self = shift;
1603 0   0       return $self->{map}->to8
1604             ($self->{map}->to16($self->{wd}->decode(shift||'')));
1605             }
1606              
1607             sub _do_answer
1608             {
1609 0     0     my $self = shift;
1610 0           my $hsms = shift;
1611 0           my $r_part = shift;
1612              
1613 0           my $pdu = new Net::SMPP::PDU;
1614              
1615 0           $pdu->source_addr_ton($self->{ton});
1616 0           $pdu->source_addr_npi($self->{npi});
1617 0           $pdu->source_addr($self->{number});
1618 0           $pdu->dest_addr_ton($hsms->{source_addr_ton});
1619 0           $pdu->dest_addr_npi($hsms->{source_addr_npi});
1620 0           $pdu->destination_addr($hsms->{source_addr});
1621 0           $pdu->short_message($$r_part);
1622            
1623 0           my ($fh, $qid) = $self->{queue}->store;
1624            
1625 0           $pdu->nstore_fd($fh);
1626            
1627 0 0         unless ($fh->close)
1628             {
1629 0 0         warn "Email: Invalid response $qid: $!\n" if $Debug;
1630 0           $self->{queue}->unlock($qid);
1631 0           return;
1632             }
1633            
1634 0 0         warn "Email: Response $qid ok\n" if $Debug;
1635 0           $self->{queue}->unlock($qid);
1636 0           return 1;
1637             }
1638              
1639             sub _addresses
1640             {
1641 0     0     my $addr = shift;
1642 0 0         warn "Email: parsing addresses from $addr\n" if $Debug;
1643 0           my @ret = map { $_->address } Mail::Address->parse($addr);
  0            
1644 0 0         warn "Email: addresses are ", join(', ', @ret), "\n" if $Debug;
1645 0           return \@ret;
1646             }
1647              
1648             sub _expanded_addresses
1649             {
1650 0     0     my $self = shift;
1651 0           my $addr = shift;
1652              
1653 0 0         warn "Email: expanding addresses from $addr\n" if $Debug;
1654              
1655 0           my @ret = ();
1656 0           for my $a (Mail::Address->parse($addr))
1657             {
1658 0 0         if (defined $self->{_state}->{alias}->{lc $a->address})
1659             {
1660 0           my ($t) = Mail::Address->parse
1661             ($self->{_state}->{alias}->{lc $a->address});
1662 0           push @ret, $t->address;
1663             }
1664             else
1665             {
1666 0           push @ret, $a->address;
1667             }
1668             }
1669 0 0         warn "Email: expanded addresses are ", join(', ', @ret), "\n" if $Debug;
1670 0           return [ @ret ];
1671             }
1672              
1673             # This method produces an answer
1674             # containing $msg as the short message
1675             sub _answer
1676             {
1677 0     0     my $self = shift;
1678 0           my $hsms = shift;
1679 0           my $msg = shift;
1680              
1681 0 0         if (length($$msg) > $self->{maxlen})
1682             {
1683              
1684              
1685             # Here we'll split the message in actual
1686             # chunks and iterate through them until
1687             # we get the lengths right.
1688              
1689 0           my $r_msg = SMS::Handler::Utils::Split_msg($self->{maxlen}, $msg);
1690 0   0       $self->_do_answer($hsms, \$_) || return for @$r_msg;
1691             }
1692             else
1693             {
1694 0 0         $self->_do_answer($hsms, $msg) || return;
1695             }
1696              
1697 0           return 1;
1698             }
1699              
1700             sub _add_trunc
1701             {
1702 0     0     my $self = shift;
1703 0           my $source = shift;
1704              
1705 0           $self->{body} .= "\n\n";
1706 0           $self->{body} .= $self->_msg('TRUNC_START');
1707 0           $self->{body} .= " " . $self->{maxfetch} . " ";
1708 0           $self->{body} .= $self->_msg('TRUNC_END');
1709 0           1;
1710             }
1711             ######################################
1712             # Utility function related to POP3 and
1713             # SMTP protocols
1714             ######################################
1715             sub _fetch
1716             {
1717 0     0     my $self = shift;
1718 0           my $hsms = shift;
1719 0           my $source = shift;
1720              
1721 0           $self->{e} = undef;
1722 0           $self->{mp}->filer->purge; # Get rid of old files
1723              
1724 0 0 0       return unless $self->{pops}
1725             or $self->_authen($hsms, $source);
1726              
1727 0           my $fh = new_tmpfile IO::File;
1728              
1729 0 0         unless ($self->{pops}->get($self->{msg}, $fh))
1730             {
1731 0 0         warn "Email: POP failure at GET $self->{msg}\n" if $Debug;
1732 0           $self->_answer($hsms, \ ($self->_msg('FETCH_NOMSG')
1733             . " ($self->{msg})"));
1734 0           return;
1735             }
1736              
1737 0           $fh->seek(0, SEEK_SET);
1738              
1739 0           my $error; # MIME::Parser error
1740            
1741 0           eval { $self->{e} = $self->{mp}->parse($fh); };
  0            
1742 0   0       $error = ($@ || $self->{mp}->last_error);
1743              
1744 0 0         if ($error)
1745             {
1746 0 0         warn "Email: $source MIME parsing of $self->{msg}: $error\n"
1747             if $Debug;
1748 0           $self->_answer($hsms, \ ($self->_msg('FETCH_MIME')
1749             . " ($self->{msg})"));
1750 0           $fh->close;
1751 0           $self->{e} = undef;
1752 0           $self->{mp}->filer->purge;
1753 0           return;
1754             }
1755              
1756 0           $fh->close;
1757              
1758 0 0         $self->_remove_alternatives
1759             if (lc $self->{e}->head->get('Content-Type')
1760             eq 'multipart/alternative');
1761              
1762 0           $self->{e}->make_multipart;
1763 0           $self->{head} = $self->{e}->head;
1764              
1765 0           $self->_setup_decoder($self->{e});
1766              
1767 0           return 1;
1768             }
1769              
1770             # This might be a multipart/alternative
1771             # message. In this case, get rid of all
1772             # redundant parts and keep just one.
1773              
1774             sub _remove_alternatives
1775             {
1776 0     0     my $self = shift;
1777              
1778 0 0         warn "Email: Stripping multipart/alternative\n"
1779             if $Debug;
1780              
1781 0           $self->{e}->parts([$self->{e}->parts(0)]);
1782             }
1783              
1784             sub _setup_decoder
1785             {
1786 0     0     my $self = shift;
1787 0           my $e = shift;
1788            
1789 0 0 0       if ($e
    0 0        
      0        
1790             and $e->head->get('Content-Type')
1791             and $e->head->get('Content-Type') =~ m!charset="([^\"]+)"!)
1792             {
1793 0   0       $self->{wd} = MIME::WordDecoder->supported($1)
1794             || MIME::WordDecoder->supported('ISO-8859-1');
1795 0 0         warn "wd for $1 is $self->{wd}\n" if $Debug;
1796             }
1797             elsif ($self->{head}->get('Content-Type')
1798             and $self->{head}->get('Content-Type') =~ m!charset="([^\"]+)"!)
1799             {
1800 0   0       $self->{wd} = MIME::WordDecoder->supported($1)
1801             || MIME::WordDecoder->supported('ISO-8859-1');
1802 0 0         warn "wd for $1 is $self->{wd}\n" if $Debug;
1803             }
1804             else
1805             {
1806 0           $self->{wd} = supported MIME::WordDecoder "ISO-8859-1";
1807 0 0         warn "default wd is $self->{wd}\n" if $Debug;
1808             }
1809             }
1810              
1811             sub _translate
1812             {
1813 0     0     my $self = shift;
1814 0           my $hsms = shift;
1815 0           my $source = shift;
1816              
1817             # At this point, $self->{e} should be a
1818             # MIME::Entity (always Multipart)
1819              
1820 0 0         unless ($self->_fetch_helper($hsms, $source, $self->{e}))
1821             {
1822 0           $self->{e} = undef;
1823 0           $self->{mp}->filer->purge;
1824 0           return;
1825             }
1826             # Fold whitespace as much as possible
1827             # if requested
1828 0 0         if ($self->{compact})
1829             {
1830 0           $self->{body} =~ s/^[[:space:]]*$/\n/mg;
1831 0           $self->{body} =~ s/[[:blank:]]+/ /g;
1832 0           $self->{body} =~ s/[\r\n]+/\n/msg;
1833             }
1834            
1835 0           return 1;
1836             }
1837              
1838             sub _truncate
1839             {
1840 0     0     my $self = shift;
1841 0           my $hsms = shift;
1842 0           my $source = shift;
1843              
1844 0           $self->d_r();
1845              
1846 0 0         if (length($self->{body}) < ($self->{part} - 1) * $self->{maxfetch})
1847             {
1848 0           $self->_answer($hsms, \ ($self->_msg('MSG_SHORT')));
1849 0           return;
1850             }
1851            
1852 0           substr($self->{body}, 0, ($self->{part} - 1) * $self->{maxfetch}, '');
1853              
1854 0 0         if (length($self->{body}) > $self->{maxfetch})
1855             {
1856 0           $self->{body} = substr($self->{body}, 0, $self->{maxfetch});
1857 0           $self->_add_trunc($source);
1858             }
1859              
1860 0           return 1;
1861             }
1862              
1863             sub _fetch_helper
1864             {
1865 0     0     my $self = shift;
1866 0           my $hsms = shift;
1867 0           my $source = shift;
1868 0           my $ent = shift;
1869              
1870 0 0         return 1 if length($self->{body}) > $self->{part} * $self->{maxfetch};
1871              
1872 0           $self->_setup_decoder($ent);
1873              
1874 0 0         if (my @parts = $ent->parts)
    0          
1875             {
1876 0           for (@parts)
1877             {
1878 0 0         return 1 if length($self->{body})
1879             > $self->{part} * $self->{maxfetch};
1880 0           my $ret = $self->_fetch_helper($hsms, $source, $_);
1881 0 0         return unless $ret;
1882             }
1883             }
1884             elsif (my $body = $ent->bodyhandle)
1885             {
1886 0           my $type = $ent->head->mime_type;
1887 0 0         warn "Email: $type: ", Digest::MD5::md5_hex($body->as_string), "\n"
1888             if $Debug;
1889 0 0         if ($type eq 'text/plain')
    0          
1890             {
1891 0           $self->{body} .= $body->as_string;
1892             }
1893             elsif ($type eq 'text/html')
1894             {
1895             # XXX - The assignment to $waste below
1896             # prevents the decoding process to turn
1897             # crazy after its first invocation. Looks
1898             # like a bug to me, but I'm unable to
1899             # replicate it with a smaller piece of code.
1900 0           my $text = $body->as_string;
1901 0           my $waste = Digest::MD5::md5_hex($text);
1902 0           $self->{parser}->{$type}->parse($text);
1903             }
1904             else
1905             {
1906 0           $self->{body} .= '[';
1907 0           $self->{body} .= $self->_msg('ATTACH');
1908 0           $self->{body} .= " $type ";
1909 0   0       $self->{body} .= $ent->head->recommended_filename || '';
1910 0           $self->{body} .= ']';
1911             }
1912             }
1913 0           return 1;
1914             }
1915              
1916             sub _dele
1917             {
1918 0     0     my $self = shift;
1919 0           my $hsms = shift;
1920 0           my $source = shift;
1921 0 0         my @msg = map { /(\d+)-(\d+)/ ? $1 .. $2 : $_ } split /,/,
  0            
1922             $self->{msg};
1923              
1924 0 0 0       return unless $self->{pops}
1925             or $self->_authen($hsms, $source);
1926              
1927 0           foreach (@msg)
1928             {
1929 0 0         unless ($self->{pops}->delete($_))
1930             {
1931 0 0         warn "Email: POP failure at DELE $_\n" if $Debug;
1932 0           $self->_answer($hsms, \ ($self->_msg('POP_DELE')
1933             . " ($_)"));
1934 0           return;
1935             }
1936             }
1937              
1938 0 0         unless (defined ($self->{num} = ($self->{pops}->popstat)[0]))
1939             {
1940 0 0         warn "Email: POP failure at POPSTAT\n" if $Debug;
1941 0           $self->_answer($hsms, \ ($self->_msg('POP_POPSTAT')));
1942 0           return;
1943             }
1944 0           return 1;
1945             }
1946              
1947             sub _quit
1948             {
1949 0     0     my $self = shift;
1950 0           my $hsms = shift;
1951 0           my $source = shift;
1952              
1953 0 0         return unless defined $self->{pops};
1954              
1955 0 0         unless ($self->{pops}->quit)
1956             {
1957 0 0         warn "Email: POP failure at QUIT\n" if $Debug;
1958 0           $self->_answer($hsms, \ ($self->_msg('POP_QUIT')));
1959 0           return;
1960             }
1961            
1962 0           return 1;
1963             }
1964              
1965             sub _authen
1966             {
1967 0     0     my $self = shift;
1968 0           my $hsms = shift;
1969 0           my $source = shift;
1970              
1971 0           $self->{pops} = Net::POP3->new($self->{pop},
1972             Timeout => 30);
1973 0 0         unless ($self->{pops})
1974             {
1975 0 0         warn "Email: Can't connect to POP server: $!\n" if $Debug;
1976 0           $self->_answer($hsms, \ ($self->_msg('POP_CONNECT')));
1977 0           return;
1978             }
1979            
1980 0 0         unless ($self->{pops}->user($self->{_state}->{login}))
1981             {
1982 0 0         warn "Email: POP failure at USER\n" if $Debug;
1983 0           $self->_answer($hsms, \ ($self->_msg('POP_USER')));
1984 0           return;
1985             }
1986              
1987 0 0         unless ($self->{num} = $self->{pops}->pass
1988             ($self->_crypt($self->{_state}->{passwd})))
1989             {
1990 0 0         warn "Email: POP failure at PASS\n" if $Debug;
1991 0           $self->_answer($hsms, \ ($self->_msg('POP_PASS')));
1992 0           return;
1993             }
1994              
1995 0           $self->{num} += 0;
1996 0           return 1;
1997             }
1998              
1999             sub _deliver
2000             {
2001 0     0     my $self = shift;
2002 0           my $hsms = shift;
2003 0           my $source = shift;
2004 0           my $to = shift;
2005 0           my $subject = shift;
2006 0   0       my $r_head = shift || {};
2007              
2008 0           my $smtp = Net::SMTP->new($self->{smtp},
2009             Timeout => 30,
2010             );
2011              
2012 0 0         unless ($smtp)
2013             {
2014 0 0         warn "Email: Can't connect to SMTP server: $!\n" if $Debug;
2015 0           $self->_answer($hsms, \ ($self->_msg('SMTP_CONNECT')));
2016 0           return;
2017             }
2018              
2019 0 0         unless ($smtp->mail($self->{_state}->{login}))
2020             {
2021 0 0         warn "Email: SMTP error (MAIL FROM): $!\n" if $Debug;
2022 0           $self->_answer($hsms, \ ($self->_msg('SMTP_MAIL')));
2023 0           return;
2024             }
2025              
2026 0           foreach (@$to)
2027             {
2028             # warn "Email: SMTP deliver $_\n";
2029 0 0         unless($smtp->to($_))
2030             {
2031 0 0         warn "Email: SMTP error (RCPT TO $_): $!\n" if $Debug;
2032 0           $self->_answer($hsms, \ ($self->_msg('SMTP_RCPT')
2033             . " ($_)"));
2034 0           return;
2035             }
2036             }
2037              
2038 0           my $e = MIME::Entity->build(Type => 'multipart/mixed',
2039             From => $self->{_state}->{login},
2040             'Reply-To' => $self->{_state}->{login},
2041             To => shift @$to,
2042             Cc => join(', ', @$to),
2043             Subject => $subject,
2044             'X-Mailer' => 'SMS::Handler::Email '
2045             . $VERSION,
2046             'X-SMS-From' =>
2047             $self->fixup_phone($hsms, $source),
2048             );
2049              
2050 0           $e->attach(Type => 'text/plain',
2051             Data => $self->{body});
2052              
2053 0 0         if ($self->{e})
2054             {
2055 0           $e->add_part($_) for $self->{e}->parts;
2056             }
2057              
2058 0           $e->sync_headers(Length => 'COMPUTE');
2059              
2060 0 0         unless ($smtp->data)
2061             {
2062 0 0         warn "Email: SMTP error (DATA): $!\n" if $Debug;
2063 0           $self->_answer($hsms, \ ($self->_msg('SMTP_DATA')));
2064 0           return;
2065             }
2066              
2067 0 0         unless ($smtp->datasend($e->as_string))
2068             {
2069 0 0         warn "Email: SMTP error (DATASEND header): $!\n" if $Debug;
2070 0           $self->_answer($hsms, \ ($self->_msg('SMTP_HDATASEND')));
2071 0           return;
2072             }
2073              
2074 0 0         unless ($smtp->dataend)
2075             {
2076 0 0         warn "Email: SMTP error (DATAEND): $!\n" if $Debug;
2077 0           $self->_answer($hsms, \ ($self->_msg('SMTP_DATAEND')));
2078 0           return;
2079             }
2080              
2081 0 0         unless ($smtp->quit)
2082             {
2083 0 0         warn "Email: SMTP error (QUIT): $!\n" if $Debug;
2084 0           $self->_answer($hsms, \ ($self->_msg('SMTP_QUIT')));
2085 0           return;
2086             }
2087              
2088 0           return 1;
2089             }
2090              
2091             =pod
2092              
2093             =head2 CUSTOMIZABLE HANDLERS
2094              
2095             To further enhance the customization of this class, the following
2096             functions can be overriden to tweak the behavior of this module.
2097              
2098             =over
2099              
2100             =item C
2101              
2102             This method is invoked after issuing the call to
2103             C<-E_fetch_state()> (and failing). Its main purpose is to allow
2104             the definition of default credentials for every user. It can return a
2105             false value (the default) to cause an error to be reported when no
2106             credentials are available. This function should provide values to
2107             C<$self-E{_state}>. It is called with the source address of the
2108             cellular phone in the format C.
2109              
2110             =cut
2111              
2112 0     0 1   sub fixup_state { return; }
2113              
2114             =pod
2115              
2116             =item C
2117              
2118             This function is used to convert a number in C format
2119             to a phone number as expected by cellular users. It must return the
2120             phone number as expected by the user.
2121              
2122             =cut
2123              
2124 0     0 1   sub fixup_phone { return (split(/\./, $_[2], 3))[2]; }
2125              
2126             =pod
2127              
2128             =item C
2129              
2130             This function is invoked from within the C<-Ehandle> method,
2131             before dispatching to the handlers. This can be used to perform custom
2132             transformations in the messages before processing. The default method,
2133             provides a translation from Nokia-like syntax into the expected
2134             B<.SEND> syntax.
2135              
2136             =cut
2137              
2138             sub fixup_sms
2139             {
2140 0     0 1   my $self = shift;
2141 0           my $hsms = shift;
2142              
2143             # This handles a somewhat common syntax
2144             # for writing an email on an SMS.
2145            
2146 0 0         if ($hsms->{short_message}
2147             =~ s/^(\(\d+\) )?\s*([^\.\(\)][^\(]*)\(([^\)]*)\)\s*//)
2148             {
2149 0 0         warn "Email: Converting to .SEND syntax\n" if $Debug;
2150 0 0         substr($hsms->{short_message}, 0, 0,
2151             ".SEND $2 " . (defined($3) ? $3 : 'No Subject') . "\n");
2152             }
2153             }
2154              
2155             =pod
2156              
2157             =back
2158              
2159             =head2 ENCRYPTION OF THE USER PASSWORDS
2160              
2161             The encription of the user passwords is intended to prevent a casual
2162             observer looking at the hash, from getting the passwords. Since the
2163             crypto is both, simplistic and reversible, you should assume that any
2164             compromise of the hash containing the passwords lead directly to
2165             password compromise.
2166              
2167             =cut
2168              
2169             sub _crypt
2170             {
2171 0     0     my $self = shift;
2172 0           my $text = reverse shift;
2173              
2174 0           my $key = '';
2175 0           my $ret = '';
2176              
2177 0           $key .= $self->{secret} while length($key) < length($text);
2178 0           $key = substr($key, 0, length($text));
2179            
2180 0           while (my $k = chop ($key))
2181             {
2182 0           my $t = chop $text;
2183 0           $ret .= chr(ord($k) ^ ord($t));
2184             }
2185              
2186 0           return $ret;
2187             }
2188              
2189             1;
2190              
2191             __END__