File Coverage

blib/lib/Net/MRIM.pm
Criterion Covered Total %
statement 24 433 5.5
branch 0 198 0.0
condition 0 80 0.0
subroutine 8 49 16.3
pod 0 15 0.0
total 32 775 4.1


line stmt bran cond sub pod time code
1             #
2             # $Date: 2009-01-11 21:41:07 $
3             #
4             # Copyright (c) 2007-2008 Alexandre Aufrere
5             # Licensed under the terms of the GPL (see perldoc MRIM.pm)
6             #
7              
8 1     1   5891 use 5.008;
  1         3  
  1         32  
9 1     1   7 use strict;
  1         2  
  1         59  
10              
11             # below is just an utility class
12             package Net::MRIM::Message;
13              
14             use constant {
15 1         854 TYPE_UNKNOWN => 0,
16             TYPE_MSG => 1,
17             TYPE_LOGOUT_FROM_SRV => 2,
18             TYPE_CONTACT_LIST => 3,
19             TYPE_SERVER => 4
20 1     1   5 };
  1         7  
21              
22             sub new {
23 0     0     my ($pkgname)=@_;
24 0           my $self={};
25 0           $self->{_type}=TYPE_UNKNOWN;
26 0           $self->{TYPE_SERVER_NOTIFY}=0;
27 0           $self->{TYPE_SERVER_ANKETA}=1;
28 0           $self->{TYPE_SERVER_AUTH_REQUEST}=2;
29 0           bless $self;
30 0           return $self;
31             }
32              
33             sub set_message {
34 0     0     my ($self, $from, $to, $message)=@_;
35 0           $self->{_type}=TYPE_MSG;
36 0           $self->{_from}=$from;
37 0           $self->{_to}=$to;
38 0           $self->{_message}=$message;
39             }
40              
41             sub is_message{
42 0     0     my ($self)=@_;
43 0           return ($self->{_type}==TYPE_MSG);
44             }
45              
46             sub get_from {
47 0     0     my ($self)=@_;
48 0           return $self->{_from};
49             }
50              
51             sub get_to {
52 0     0     my ($self)=@_;
53 0           return $self->{_to};
54             }
55              
56             sub get_message {
57 0     0     my ($self)=@_;
58 0           return $self->{_message};
59             }
60              
61             sub set_logout_from_server {
62 0     0     my ($self)=@_;
63 0           $self->{_type}=TYPE_LOGOUT_FROM_SRV;
64             }
65              
66             sub is_logout_from_server {
67 0     0     my ($self)=@_;
68 0           return ($self->{_type}==TYPE_LOGOUT_FROM_SRV);
69             }
70              
71             sub set_server_msg {
72 0     0     my ($self,$stype,$to,$message,$svalue)=@_;
73 0           $self->{_type}=TYPE_SERVER;
74 0           $self->{_stype}=$stype;
75 0           $self->{_from}='SERVER';
76 0           $self->{_to}=$to;
77 0           $self->{_message}=$message;
78 0 0         $self->{_from}=$svalue if ($stype==$self->{TYPE_SERVER_AUTH_REQUEST});
79             }
80              
81             sub is_server_msg {
82 0     0     my ($self)=@_;
83 0           return ($self->{_type}==TYPE_SERVER);
84             }
85              
86             sub get_subtype {
87 0     0     my ($self)=@_;
88 0           return $self->{_stype};
89             }
90              
91             sub set_contact_list {
92 0     0     my ($self, $groups, $contacts)=@_;
93 0           $self->{_type}=TYPE_CONTACT_LIST;
94 0           $self->{_groups}=$groups;
95 0           $self->{_contacts}=$contacts;
96             }
97              
98             sub is_contact_list {
99 0     0     my ($self)=@_;
100 0           return ($self->{_type}==TYPE_CONTACT_LIST);
101             }
102              
103             sub get_groups {
104 0     0     my ($self)=@_;
105 0           return $self->{_groups};
106             }
107              
108             sub get_contacts {
109 0     0     my ($self)=@_;
110 0           return $self->{_contacts};
111             }
112              
113             package Net::MRIM::Contact;
114              
115             sub new {
116 0     0     my ($pkgname,$email,$name,$status)=@_;
117 0           my $self={};
118 0           $self->{_email}=$email;
119 0           $self->{_name}=$name;
120 0           $self->{_status}=$status;
121 0           $self->{STATUS_ONLINE}=0x00000001;
122 0           $self->{STATUS_AWAY}=0x00000002;
123 0           bless $self;
124 0           return $self;
125             }
126              
127             sub get_email {
128 0     0     my $self=shift;
129 0           return $self->{_email};
130             }
131              
132             sub get_name {
133 0     0     my $self=shift;
134 0           return $self->{_name};
135             }
136              
137             sub get_status {
138 0     0     my $self=shift;
139 0           return $self->{_status};
140             }
141              
142             sub set_status {
143 0     0     my ($self,$status)=@_;
144 0           $self->{_status}=$status;
145             }
146              
147             package Net::MRIM;
148              
149             our $VERSION='1.11';
150              
151             =pod
152              
153             =head1 NAME
154              
155             Net::MRIM - Perl implementation of mail.ru agent protocol
156              
157             =head1 DESCRIPTION
158              
159             This is a Perl implementation of the mail.ru agent protocol, which specs can be found at http://agent.mail.ru/protocol.html
160              
161             =head1 SYNOPSIS
162              
163             To construct and connect to MRIM's servers:
164              
165             my $mrim=Net::MRIM->new(
166             Debug=>0,
167             PollFrequency=>5
168             );
169             $mrim->hello();
170              
171             To log in:
172              
173             if (!$mrim->login("login\@mail.ru","password")) {
174             print "LOGIN REJECTED\n";
175             exit;
176             } else {
177             print "LOGGED IN\n";
178             }
179              
180             To authorize a user:
181              
182             my $ret=$mrim->authorize_user("friend\@mail.ru");
183              
184             To add a user to contact list (sends automatically auth request):
185              
186             $ret=$mrim->add_contact("friend\@mail.ru");
187              
188             To remove a user from contact list:
189              
190             $ret=$mrim->remove_contact("friend\@mail.ru");
191              
192             To send a message:
193              
194             $ret=$mrim->send_message("friend\@mail.ru","hello");
195              
196             To change user status:
197              
198             $ret=$mrim->change_status(status);
199              
200             Where status=0 means online and status=1 means away
201              
202             Get information for a contact:
203              
204             $ret=$mrim->contact_info("friend\@mail.ru");
205            
206             Search for users:
207              
208             $ret=$mrim->search_user(email, sex, country, online);
209              
210             Where sex=(1|2), country can be found at http://agent.mail.ru/region.txt or in Net::MRIM::Data.pm, and online=(0|1)
211              
212             Analyze the return of the message:
213              
214             if ($ret->is_message()) {
215             print "From: ".$ret->get_from()." Message: ".$ret->get_message()." \n";
216             } elsif ($ret->is_server_msg()) {
217             print $ret->get_message()." \n";
218             }
219              
220             Looping to get messages:
221              
222             while (1) {
223             sleep(1);
224             $ret=$mrim->ping();
225             if ($ret->is_message()) {
226             print "From: ".$ret->get_from()." Message: ".$ret->get_message()." \n";
227             }
228             }
229              
230             Disconnecting:
231              
232             $mrim->disconnect();
233              
234             =head1 AUTHOR
235              
236             Alexandre Aufrere
237              
238             =head1 COPYRIGHT
239              
240             Copyright (c) 2007-2008 Alexandre Aufrere. This code may be used under the terms of the GPL version 2 (see at http://www.gnu.org/licenses/old-licenses/gpl-2.0.html). The protocol remains the property of Mail.Ru (see at http://www.mail.ru).
241              
242             =cut
243              
244 1     1   874 use IO::Socket::INET;
  1         25292  
  1         7  
245 1     1   1616 use IO::Select;
  1         1730  
  1         255  
246              
247             # the definitions below come straight from the protocol documentation
248             use constant {
249 1         439 CS_MAGIC => 0xDEADBEEF,
250             PROTO_VERSION => 0x10008,
251              
252             MRIM_CS_HELLO => 0x1001, ## C->S, empty
253             MRIM_CS_HELLO_ACK => 0x1002, ## S->C, UL mrim_connection_params_t
254              
255             MRIM_CS_LOGIN2 => 0x1038, ## C->S, LPS login, LPS password, UL status, LPS useragent
256             MRIM_CS_LOGIN_ACK => 0x1004, ## S->C, empty
257             MRIM_CS_LOGIN_REJ => 0x1005, ## S->C, LPS reason
258             MRIM_CS_LOGOUT => 0x1013, ## S->C, UL reason
259              
260             MRIM_CS_PING => 0x1006, ## C->S, empty
261              
262             MRIM_CS_USER_STATUS => 0x100f, ## S->C, UL status, LPS user
263             STATUS_OFFLINE => 0x00000000,
264             STATUS_ONLINE => 0x00000001,
265             STATUS_AWAY => 0x00000002,
266             STATUS_UNDETERMINED => 0x00000003,
267             MRIM_CS_USER_INFO => 0x1015,
268             MRIM_CS_ADD_CONTACT => 0x1019, # C->S UL flag, UL group_id, LPS email, LPS name
269             CONTACT_FLAG_VISIBLE => 0x00000008,
270             CONTACT_FLAG_REMOVED => 0x00000001,
271             CONTACT_FLAG_SMS => 0x00100000,
272             MRIM_CS_ADD_CONTACT_ACK => 0x101A,
273             CONTACT_OPER_SUCCESS => 0x00000000,
274             CONTACT_OPER_USER_EXISTS => 0x00000005,
275             MRIM_CS_AUTHORIZE => 0x1020, # C -> S, LPS user
276             MRIM_CS_MODIFY_CONTACT => 0x101B, # C -> S, UL id, UL flags, UL group_id, LPS email, LPS name, LPS unused
277             MRIM_CS_MODIFY_CONTACT_ACK => 0x101C,
278             MRIM_CS_AUTHORIZE_ACK => 0x1021, # C -> S, LPS user
279              
280             MRIM_CS_MESSAGE => 0x1008, ## C->S, UL flags, LPS to, LPS message, LPS rtf-message
281             MESSAGE_FLAG_OFFLINE => 0x00000001,
282             MESSAGE_FLAG_NORECV => 0x00000004,
283             MESSAGE_FLAG_AUTHORIZE => 0x00000008,
284             MESSAGE_FLAG_SYSTEM => 0x00000040,
285             MESSAGE_FLAG_RTF => 0x00000080,
286             MESSAGE_FLAG_NOTIFY => 0x00000400,
287             MESSAGE_FLAG_UNKOWN => 0x00100000,
288             MRIM_CS_MESSAGE_RECV => 0x1011,
289             MRIM_CS_MESSAGE_STATUS => 0x1012, # S->C
290             MRIM_CS_MESSAGE_ACK => 0x1009, #S->C
291             MRIM_CS_OFFLINE_MESSAGE_ACK => 0x101D, #S->C UIDL, LPS message
292             MRIM_CS_DELETE_OFFLINE_MESSAGE => 0x101E, #C->S UIDL
293              
294             MRIM_CS_CONNECTION_PARAMS =>0x1014, # S->C
295              
296             MRIM_CS_CHANGE_STATUS => 0x1022,
297             MRIM_CS_GET_MPOP_SESSION => 0x1024,
298             MRIM_CS_MPOP_SESSION => 0x1025,
299              
300             MRIM_CS_ANKETA_INFO => 0x1028, # S->C
301             MRIM_CS_WP_REQUEST =>0x1029, # C->S
302             MRIM_CS_MAILBOX_STATUS => 0x1033,
303             MRIM_CS_CONTACT_LIST2 => 0x1037, # S->C UL status, UL grp_nb, LPS grp_mask, LPS contacts_mask, grps, contacts
304              
305             MRIM_CS_SMS => 0x1039, # C->S UL unkown, LPS number, LPS message
306             MRIM_CS_SMS_ACK => 0x1040, # S->C UL status
307              
308             # Don't look for file transfer, it's simply not handled
309             # Mail.Ru only partially documented the old, unused P2P file transfer
310             # the new file transfer simply gives an (unusable) RFC1918 address
311             # when getting the MRIM_CS_FILE_TRANSFER packet
312             # Needs some reverse-engineering. Has been done by Miranda's MRA plugin guys,
313             # but for some reason i can't find the source
314              
315             MRIMUA => "Net::MRIM.pm v. "
316 1     1   7 };
  1         2  
317              
318 1     1   913 use bytes;
  1         8  
  1         5  
319              
320             # the constructor takes only one optionnal parameter: debug (true or false);
321             sub new {
322 0     0 0   my ($pkgname,%params)=@_;
323 0           my ($host, $port) = _get_host_port();
324 0           my $sock = IO::Socket::INET->new(
325             PeerAddr => $host,
326             PeerPort => $port,
327             Proto => 'tcp',
328             Type => SOCK_STREAM,
329             TimeOut => 20
330             );
331 0 0         die "couldn't connect" if (!defined($sock));
332 0 0 0       print "DEBUG Connected to $host:$port\n" if (($params{Debug})&&($params{Debug}==1));
333 0           my $self={};
334 0           $self->{_sock}=$sock;
335 0           $self->{_seq_real}=0;
336 0           $self->{_ping_period}=30; # value by default
337             # this stores the contact list:
338 0           $self->{_contacts}={};
339             # this stores the MRIM's UIDs for contacts (internal use only)
340 0           $self->{_all_contacts}={};
341 0 0 0       $self->{_debug}=$params{Debug} if (($params{Debug})&&($params{Debug}==1));
342 0   0       $self->{_freq}=$params{PollFrequency} || 5;
343 0 0         $self->{_freq}=30 if ($self->{_freq}>30);
344 0           $self->{_last_seq}=-1;
345 0           $self->{_last_type}=-1;
346 0           $self->{_last_time}=time();
347 0 0         print "DEBUG Poll Frequency: ".$self->{_freq}."\n" if ($self->{_debug});
348 0           bless $self;
349 0           return $self;
350             }
351              
352             # this is the technical "hello" header
353             # as a side note, it seems to me that this protocol was created by people who were used to e-mail ;-)
354             sub hello {
355 0     0 0   my ($self)=@_;
356 0           my $ret=$self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_HELLO,""),0);
357 0           my($msgrcv,$datarcv,$dlen)=_receive_data($self);
358 0           $datarcv=unpack("V",$datarcv);
359 0           $self->{_ping_period} = $datarcv;
360 0           $self->{_seq_real}++;
361 0 0 0       print "DEBUG Connected to MRIM. Ping period is $datarcv\n" if ($datarcv&&($self->{_debug}));
362             }
363              
364             # normally useless
365             sub get_ping_period {
366 0     0 0   my ($self)=@_;
367 0           return $self->{_ping_period};
368             }
369              
370             # the server should be ping'ed regularly to avoid being disconnected
371             sub ping {
372 0     0 0   my ($self)=@_;
373 0 0         print "DEBUG [ping]\n" if ($self->{_debug});
374 0           my $curtime=time();
375 0 0         if (($curtime-$self->{_last_time})>=($self->{_ping_period}-10)) {
376 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_PING,""),0);
377 0           $self->{_seq_real}++;
378 0           $self->{_last_time}=$curtime;
379             }
380 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
381 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
382             }
383              
384             # this is to log in...
385             sub login {
386 0     0 0   my ($self,$login,$pass)=@_;
387 0           my $status=STATUS_ONLINE;
388 0 0         print "DEBUG [status]: $status\n" if ($self->{_debug});
389 0           my $data=_to_lps($login)._to_lps($pass).pack("V",$status)._to_lps("".MRIMUA.$VERSION);
390 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_LOGIN2,$data));
391 0           $self->{_seq_real}++;
392 0           $self->{_login}=$login;
393 0           my($msgrcv,$datarcv,$dlen)=_receive_data($self);
394 0           my $norace=0;
395 0   0       while (($msgrcv==0)&&($norace<50)) {
396 0           ($msgrcv,$datarcv,$dlen)=_receive_data($self);
397 0           $norace++;
398             }
399 0 0         print "DEBUG [rcv login ack] $msgrcv\n" if ($self->{_debug});
400 0 0         return ($msgrcv==MRIM_CS_LOGIN_ACK)?1:0;
401             }
402              
403             # this is to send a message
404             sub send_message {
405 0     0 0   my ($self,$to,$message)=@_;
406 0 0         print "DEBUG [send message]: $message\n" if ($self->{_debug});
407 0           my $data=pack("V",MESSAGE_FLAG_NORECV)._to_lps($to)._to_lps($message)._to_lps("");
408 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_MESSAGE,$data));
409 0           $self->{_seq_real}++;
410 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
411 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
412             }
413              
414             # send SMS
415             # the implementation is awful: it adds and then remove an SMS entry to the contact list, and tries to send SMS in between.
416             # why ? i'm French, live in France, and don't have any access to a russian mobile phone... so it's impossible for me to test, i just use some web literature i found on the topic
417             # wish to help ? contact me - aau@cpan.org (vozmozhno i po-russkiy - lyudi! pomogite! ;-))))
418             sub send_sms {
419 0     0 0   my ($self,$numberto,$message)=@_;
420 0           my $dontremove=0;
421 0 0         print "DEBUG [send SMS]: $message\n" if ($self->{_debug});
422             # first, we should "add" it as SMS contact...
423 0           my $data=pack("V",CONTACT_FLAG_SMS).pack("V",0xffffffff)._to_lps($numberto)._to_lps("SMS")._to_lps($numberto);
424 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_ADD_CONTACT,$data));
425 0           $self->{_seq_real}++;
426 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
427 0           my @datas=_from_mrim_us("uu",$datarcv);
428 0           my $cid=$datas[1];
429             # This is ugly: in case some message is in between, return without sending the SMS actually
430 0 0         if ($msgrcv != MRIM_CS_ADD_CONTACT_ACK) {
431 0 0         print "DEBUG [send SMS]: $message was NOT sent\n" if ($self->{_debug});
432 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
433             }
434             # In case adding the contact failed, return without sending the SMS actually, but with an error message
435 0 0 0       if (($datas[0] != CONTACT_OPER_SUCCESS)&&($datas[0] != CONTACT_OPER_USER_EXISTS)) {
436 0           my $data=new Net::MRIM::Message();
437 0           $data->set_server_msg($data->{TYPE_SERVER_NOTIFY},$self->{_login},"CONTACT_OPER_ERROR_SMS: Error adding contact for SMS sending");
438 0           return $data;
439             }
440 0 0         $dontremove=1 if ($datas[0] == CONTACT_OPER_USER_EXISTS);
441 0           $data=pack("V",0)._to_lps($numberto)._to_lps($message);
442 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_SMS,$data));
443 0           $self->{_seq_real}++;
444 0           my ($msgrcvsms,$datarcvsms,$dlensms)=_receive_data($self);
445             # if contact was already in contact list, do not try to remove it.
446 0 0         return _analyze_received_data($self,$msgrcvsms,$datarcvsms,$dlensms) if ($dontremove==1);
447 0           $data=pack("V",$cid).pack("V",CONTACT_FLAG_SMS|CONTACT_FLAG_REMOVED).pack("V",0xffffffff)._to_lps($numberto)._to_lps("SMS")._to_lps($numberto);
448 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_MODIFY_CONTACT,$data));
449 0           $self->{_seq_real}++;
450 0           ($msgrcv,$datarcv,$dlen)=_receive_data($self);
451             # This is ugly: in case some message is in between, return without knowing if the SMS was actually sent
452 0 0         if ($msgrcv != MRIM_CS_MODIFY_CONTACT_ACK) {
453 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
454             }
455 0           return _analyze_received_data($self,$msgrcvsms,$datarcvsms,$dlensms);
456             }
457              
458             # to authorize a user to add us to the contact list
459             sub authorize_user {
460 0     0 0   my ($self,$user)=@_;
461 0           my $data=_to_lps($user);
462 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_AUTHORIZE,$data));
463 0           $self->{_seq_real}++;
464 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
465 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
466             }
467              
468             # change user's status: 0=online, 1=away
469             sub change_status {
470 0     0 0   my ($self,$status)=@_;
471 0           my $data=pack('V',STATUS_ONLINE);
472 0 0         $data=pack('V',STATUS_AWAY) if ($status==1);
473 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_CHANGE_STATUS,$data));
474 0           $self->{_seq_real}++;
475 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
476 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
477             }
478              
479             # to add a contact to the contact list
480             sub add_contact {
481 0     0 0   my ($self, $email)=@_;
482 0 0         print "DEBUG [add contact]: $email\n" if ($self->{_debug});
483 0           my $data=pack("V",0).pack("V",0xffffffff)._to_lps($email).pack("V",0).pack("V",0);
484 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_ADD_CONTACT,$data));
485 0           $self->{_seq_real}++;
486             # not in the protocol: after sending an add request, one should send an auth message !
487 0           $data=pack("V",MESSAGE_FLAG_AUTHORIZE)._to_lps($email)._to_lps("Please authorize me")._to_lps("");
488 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_MESSAGE,$data));
489 0           $self->{_seq_real}++;
490 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
491 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
492             }
493              
494             # to remove a contact from the contact list
495             sub remove_contact {
496 0     0 0   my ($self, $email)=@_;
497 0 0         print "DEBUG [remove contact]: $email ".$self->{_all_contacts}->{$email}."\n" if ($self->{_debug});
498 0 0         return new Net::MRIM::Message if (!defined($self->{_all_contacts}->{$email}));
499             # C -> S, UL id, UL flags, UL group_id, LPS email, LPS name, LPS unused
500 0           my $data=pack("V",$self->{_all_contacts}->{$email}).pack("V",CONTACT_FLAG_REMOVED).pack("V",0xffffffff)._to_lps($email).pack("V",0).pack("V",0);
501 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_MODIFY_CONTACT,$data));
502 0           $self->{_seq_real}++;
503 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
504 0 0         if ($msgrcv==MRIM_CS_MODIFY_CONTACT_ACK) {
505 0           my @datas=_from_mrim_us("uu",$datarcv.pack("V",0));
506 0 0         if ($datas[0]==0) {
507 0 0         print "DEBUG $email removed from CL\n" if ($self->{_debug});
508 0           $self->{_contacts}->{$email}=undef;
509 0           $self->{_all_contacts}->{$email}=undef;
510             }
511             }
512 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
513             }
514              
515             # get contact info from server (send contact info request)
516             sub contact_info {
517 0     0 0   my ($self, $email)=@_;
518 0           $email=~m/^([a-z0-9\_\-\.]+)\@([a-z0-9\_\-\.]+)$/i;
519 0           my $cuser=$1;
520 0           my $cdomain=$2;
521 0           my $data=pack("V",0x00000000)._to_lps($cuser).pack("V",0x00000001)._to_lps($cdomain).pack("V",0x00000009)._to_lps('1');
522 0 0         print "DEBUG Getting infor for $cuser $cdomain\n" if ($self->{_debug});
523 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_WP_REQUEST,$data));
524 0           $self->{_seq_real}++;
525 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
526 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
527             }
528              
529             # get contact avatar url
530             sub get_contact_avatar_url {
531 0     0 0   my ($self, $email)=@_;
532 0           $email=~m/^([a-z0-9\_\-\.]+)\@([a-z0-9\_\-]+)\.([a-z0-9\_\-]+)$/i;
533 0           my $cuser=$1;
534 0           my $cdomain=$2;
535 0           return "http://avt.foto.mail.ru/$cdomain/$cuser/_avatar";
536             }
537              
538             # search users. for now only by nickname, sex, country
539             sub search_user {
540 0     0 0   my ($self, $email, $sex, $country, $online)=@_;
541 0           $email=~m/^([a-z0-9\_\-\.]+)\@([a-z0-9\_\-\.]+)$/i;
542 0           my $cuser=$1;
543 0           my $cdomain=$2;
544 0           my $data='';
545 0 0         $data.=pack("V",0x00000000)._to_lps($cuser).pack("V",0x00000001)._to_lps($cdomain) if ($email ne '');
546 0 0 0       $data.=pack("V",0x00000005)._to_lps("$sex") if (($sex ne '')&&($sex ne '0'));
547 0 0         $data.=pack("V",0x0000000F)._to_lps("$country") if ($country ne '');
548 0 0         $data.=pack("V",0x00000009)._to_lps('1') if ($online == 1);
549 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_WP_REQUEST,$data));
550 0           $self->{_seq_real}++;
551 0           my ($msgrcv,$datarcv,$dlen)=_receive_data($self);
552 0           return _analyze_received_data($self,$msgrcv,$datarcv,$dlen);
553             }
554              
555             # and finally to disconnect
556             sub disconnect {
557 0     0 0   my ($self)=@_;
558 0           $self->{_sock}->close;
559             }
560              
561             #### private methods below ####
562              
563             # build the MRIM packet accordingly to the protocol specs
564             sub _make_mrim_packet {
565 0     0     my ($self,$msg, $data) = @_;
566 0           my ($magic, $proto, $seq, $from, $fromport) = (CS_MAGIC, PROTO_VERSION, $self->{_seq_real}, 0, 0);
567             # actually, i'm not even sure this is needed...
568 0 0         $seq=$self->{_last_seq} if ($msg==MRIM_CS_MESSAGE_RECV);
569 0           my $dlen = 0;
570 0 0         $dlen = length($data) if $data;
571 0           my $mrim_packet = pack("V7", $magic, $proto, $seq, $msg, $dlen, $from, $fromport);
572 0           $mrim_packet.=pack("C[16]",0);
573 0 0         $mrim_packet .= $data if $data;
574 0 0         printf("DEBUG [send packet]: MAGIC=$magic, PROTO=$proto, SEQ=$seq, TYP=0x%04x, LEN=$dlen\n",$msg) if ($self->{_debug});
575 0           return $mrim_packet;
576             }
577              
578             # retrieve a real host:port, as "mrim.mail.ru" can be several servers
579             # note that we connect on port 443, as this will always work for sure...
580             sub _get_host_port {
581 0     0     my $sock = new IO::Socket::INET (
582             PeerAddr => 'mrim.mail.ru',
583             PeerPort => 443,
584             PeerProto => 'tcp',
585             TimeOut => 10 );
586 0           my $data="";
587 0           $sock->recv($data, 18);
588 0           close $sock;
589 0           chomp $data;
590 0           return split /:/, $data;
591             }
592              
593             # reading the data from server
594             sub _receive_data {
595 0     0     my ($self)=@_;
596 0           my $buffer="";
597 0           my $data="";
598 0           my $typ=0;
599 0 0         print "DEBUG [recv packet]: waiting for header data\n" if ($self->{_debug});
600 0 0 0       return (MRIM_CS_LOGOUT,"",0) if ((!($self->{_sock}))||(!$self->{_sock}->connected()));
601 0           my $s = IO::Select->new();
602 0           $s->add($self->{_sock});
603             # check, since socket registration *could* fail
604 0 0         return (MRIM_CS_LOGOUT,"",0) if (!defined($s->exists($self->{_sock})));
605 0           my $dllen=0;
606             # this stuff is to not wait for ever data from the server
607             # note that we're mixing a bit unbuffered and buffered I/O, this is not 100% great
608 0 0         if ($s->can_read(int($self->{_ping_period}/$self->{_freq}))) {
609 0           $self->{_sock}->recv($buffer,44);
610 0           my ($magic, $proto, $seq, $msg, $dlen, $from, $fromport, $r1, $r2, $r3, $r4) = unpack ("V11", $buffer);
611 1     1   3298 use bytes;
  1         2  
  1         5  
612 0 0 0       if (($seq>0)&&($seq<=$self->{_last_seq})&&($msg==$self->{_last_type})) {
      0        
613             # this should work, but it doesn't. since i don't understand, better leave it deactivated.
614             #return(-1,"",0);
615             } else {
616 0           $self->{_last_type}=$msg;
617 0 0         $self->{_last_seq}=$seq if ($seq>0);
618             }
619 0           $self->{_sock}->recv($buffer,$dlen);
620 0           $data=$buffer;
621 0           $typ=$msg;
622 0           $dllen=$dlen;
623             # unfortunately "buffer I/O" isn't that buffered...
624 0           while (length($data)<$dlen) {
625 0           $self->{_sock}->recv($buffer,$dlen-length($data));
626 0           $data.=$buffer;
627             }
628 0 0         printf("DEBUG [recv packet]: MAGIC=$magic, PROTO=$proto, SEQ=$seq, LASTSEQ=$self->{_last_seq}, TYP=0x%04x, LEN=$dlen ".length($data)."\n",$msg) if ($self->{_debug});
629             }
630 0           return ($typ,$data,$dllen);
631             }
632              
633             # the packet analyzer
634             sub _analyze_received_data {
635 0     0     my ($self,$msgrcv,$datarcv,$dlen)=@_;
636 0 0         $dlen = 0 if (!defined($dlen));
637 0           my $data=new Net::MRIM::Message();
638 0 0 0       if (!defined($msgrcv)) {
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
639 0           $data->set_logout_from_server();
640             } elsif ($msgrcv==MRIM_CS_OFFLINE_MESSAGE_ACK) {
641 0           my $msg='';
642 0           my @datas=_from_mrim_us("s",substr($datarcv,8,-1));
643 0           LINE: foreach my $msgline (split(/\n/,$datas[1])) {
644             # some headers cleanup
645 0 0         if ($msgline!~m/^(Boundary:|Version:|X-MRIM-Flags:|Subject:|\-\-)/) {
    0          
646 0           $msg.=$msgline."\n";
647             }
648             # remove everything past the boundary
649             elsif ($msgline=~m/^\-\-[0-9A-Z]+/) {
650 0           last LINE;
651             }
652             }
653 0           $data->set_message("OFFLINE",$self->{_login},$msg);
654 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_DELETE_OFFLINE_MESSAGE,substr($datarcv,0,8)));
655             } elsif ($msgrcv==MRIM_CS_MESSAGE_ACK) {
656 0           my @datas=_from_mrim_us("uusss",$datarcv);
657             # below is a work-around: it seems that sometimes message_flag is left to 0...
658             # as well, it seems the flags can be combined...
659             # lastly, this flag was recently added, i don't know why...
660 0           while ($datas[1]>=MESSAGE_FLAG_UNKOWN) {
661 0           $datas[1]=$datas[1] - MESSAGE_FLAG_UNKOWN;
662             }
663 0 0 0       if (($datas[1]==MESSAGE_FLAG_NORECV)||($datas[1]==MESSAGE_FLAG_OFFLINE)) {
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
664 0           $data->set_message($datas[2],$self->{_login},"".$datas[3]);
665             } elsif (($datas[1]==0)||($datas[1]==MESSAGE_FLAG_RTF)) {
666 0           $data->set_message($datas[2],$self->{_login},"".$datas[3]);
667 0           $self->{_sock}->send(_make_mrim_packet($self,MRIM_CS_MESSAGE_RECV,_to_lps($datas[2]).pack("V",$datas[0])));
668             } elsif (($datas[1]==MESSAGE_FLAG_NOTIFY)||($datas[1]==(MESSAGE_FLAG_NOTIFY+MESSAGE_FLAG_NORECV))) {
669 0 0         $data->set_message($datas[2],$self->{_login},"pishu") if ($self->{_debug});
670             } elsif (($datas[1]==MESSAGE_FLAG_AUTHORIZE)
671             ||($datas[1]==(MESSAGE_FLAG_AUTHORIZE+MESSAGE_FLAG_NORECV))
672             ||($datas[1]==(MESSAGE_FLAG_AUTHORIZE+MESSAGE_FLAG_OFFLINE))
673             ) {
674 0           $data->set_server_msg($data->{TYPE_SERVER_AUTH_REQUEST},$self->{_login},$datas[3],$datas[2]);
675             } elsif (($datas[1]==MESSAGE_FLAG_SYSTEM)
676             ||($datas[1]==(MESSAGE_FLAG_SYSTEM+MESSAGE_FLAG_NORECV))
677             ||($datas[1]==(MESSAGE_FLAG_SYSTEM+MESSAGE_FLAG_OFFLINE))
678             ) {
679 0           $data->set_server_msg($data->{TYPE_SERVER_NOTIFY},$self->{_login},$datas[3]);
680             } else {
681 0 0         print "DEBUG: ack msg $datas[1] from $datas[2] text: $datas[3]\n" if ($self->{_debug});
682             }
683             } elsif ($msgrcv==MRIM_CS_LOGOUT) {
684 0           $data->set_logout_from_server();
685             } elsif ($msgrcv==MRIM_CS_MAILBOX_STATUS) {
686 0           my @datas=_from_mrim_us("u",$datarcv);
687 0           $data->set_server_msg($data->{TYPE_SERVER_NOTIFY},$self->{_login},"NEW_MAIL: ".$datas[0]);
688             } elsif ($msgrcv==MRIM_CS_CONTACT_LIST2) {
689             # S->C UL status, UL grp_nb, LPS grp_mask, LPS contacts_mask, grps, contacts
690 0           my @datas=_from_mrim_us("uuss",$datarcv);
691 0           my $nb_groups=$datas[1];
692 0           my $gr_mask=$datas[2];
693 0           my $ct_mask=$datas[3];
694 0 0         print "DEBUG: found $datas[1] groups, $datas[2] gr mask, $datas[3] contact mask\n" if ($self->{_debug});
695 0           $datarcv=$datas[4];
696 0           my $groups={};
697 0           for (my $i=0; $i<$nb_groups; $i++) {
698 0           my ($grp_id,$grp_name)=(0,"");
699 0           ($grp_id,$grp_name,$datarcv)=_from_mrim_us($gr_mask,$datarcv);
700 0 0         print "DEBUG: Found group $grp_name of id $grp_id\n" if ($self->{_debug});
701 0           $groups->{$grp_id}=$grp_name;
702             }
703 0           my $contacts=$self->{_contacts};
704 0           my $all_contacts=$self->{_all_contacts};
705 0           my $i=scalar(keys(%{$all_contacts}))+1;
  0            
706 0 0         $i=20 if ($i<10);
707 0           my $clen=8+length($gr_mask)+length($ct_mask);
708 0   0       while ((length($datarcv)>1)||($clen < $dlen)) {
709             # TODO works only with current pattern uussuus . if it changes, will break...
710 0           my ($flags,$group, $email, $name, $sflags, $status, $unk)=(0,"");
711 0           ($flags,$group, $email, $name, $sflags, $status, $unk, $datarcv)=_from_mrim_us($ct_mask,$datarcv);
712 0           $name=~s/\n//g;
713 0 0         print "DEBUG: Found contact $name of id $email flags $flags $sflags $status $group unknown: $unk clen $clen dlen $dlen\n" if ($self->{_debug});
714 0 0         $name=$email if (length($name)<1);
715 0 0 0       $status=STATUS_OFFLINE if (($flags==CONTACT_FLAG_REMOVED)||($flags==CONTACT_FLAG_SMS)||($flags==(CONTACT_FLAG_SMS|CONTACT_FLAG_REMOVED))); # to take care about SMS contacts, if any
      0        
716 0 0 0       $contacts->{$email}=new Net::MRIM::Contact($email,$name,$status) if (($status != STATUS_OFFLINE)&&($status != STATUS_UNDETERMINED)&&(length($email)>1));
      0        
717 0           $all_contacts->{$email}=$i;
718 0           $clen=16+length($name)+length($email)+length($unk)+$clen;
719 0 0         $datarcv="" if($clen>$dlen);
720 0           $i++;
721             }
722 0           $self->{_contacts}=$contacts;
723 0           $self->{_all_contacts}=$all_contacts;
724 0           $self->{_groups}=$groups;
725 0           $data->set_contact_list($groups,$contacts);
726             } elsif (($msgrcv==MRIM_CS_USER_STATUS)||($msgrcv==MRIM_CS_AUTHORIZE_ACK)) {
727             # if user changes status, or has accepted to be added to our list,
728             # then we should update the contact list accordingly
729 0           my @datas=();
730 0 0         if ($msgrcv==MRIM_CS_USER_STATUS) {
731 0           @datas=_from_mrim_us("us",$datarcv);
732             } else {
733 0           my @tmp=_from_mrim_us("s",$datarcv);
734 0           @datas=(STATUS_ONLINE,$tmp[0]);
735             }
736 0           my $contacts=$self->{_contacts};
737 0           my $all_contacts=$self->{_all_contacts};
738 0           my $groups=$self->{_groups};
739 0           my @ckeys=keys%{$contacts};
  0            
740 0           my $i=scalar(keys(%{$all_contacts}))+1;
  0            
741 0 0         $i=20 if ($i<10);
742 0 0 0       if (($datas[0] != STATUS_OFFLINE)&&($datas[0] != STATUS_UNDETERMINED)) {
    0 0        
743 0           $contacts->{$datas[1]}=new Net::MRIM::Contact($datas[1],$datas[1],$datas[0]);
744 0           $all_contacts->{$datas[1]}=$i;
745             } elsif (($datas[0] == STATUS_OFFLINE)&&(grep(/$datas[1]/,@ckeys))) {
746 0           $contacts->{$datas[1]}=undef;
747 0           $all_contacts->{$datas[1]}=undef;
748             }
749 0           $self->{_contacts}=$contacts;
750 0           $self->{_all_contacts}=$all_contacts;
751 0           $data->set_contact_list($groups,$contacts);
752             } elsif (($msgrcv==MRIM_CS_ADD_CONTACT_ACK)||($msgrcv==MRIM_CS_MODIFY_CONTACT_ACK)) {
753             # this is useless for now, as the contact list only stores online users
754 0           my @datas=_from_mrim_us("uu",$datarcv.pack("V",0));
755 0 0         print "DEBUG add_contact_ack: $datas[0] $datas[1]\n" if ($self->{_debug});
756 0           $data->set_contact_list($self->{_groups},$self->{_contacts});
757 0 0         $data->set_server_msg($data->{TYPE_SERVER_NOTIFY},$self->{_login},"CONTACT_OPER_ERROR: Error adding/removing contact") if ($datas[0] != CONTACT_OPER_SUCCESS);
758             } elsif ($msgrcv==MRIM_CS_ANKETA_INFO) {
759 0           my @datas=_from_mrim_us("uuuu",$datarcv);
760 0           my $dataparse="";
761 0           for (my $i=0; $i<$datas[1]; $i++) { $dataparse.='ss'; }
  0            
762 0           my $fulldata="INFO\n";
763 0           my $fentr=0;
764 0 0         print "DEBUG anketa_info: found ".$datas[0].' '.$datas[1].' '.$datas[2].' '.$datas[3]." entries\n" if ($self->{_debug});
765 0   0       while (($fentr<$datas[2])&&($fentr<50)) {
766 0           @datas=_from_mrim_us("uuuu".$dataparse,$datarcv);
767             # this flag will trace if a record was found
768 0           my $found=1;
769 0           for (my $i=4;$i<($datas[1]+4);$i++) {
770 0           my $label=$datas[$i];
771 0           my $value=$datas[($i+$datas[1])];
772 0           my $entry.=_to_lps($value);
773             # this is to remove the entry from received data, to allow "iteration" ammong values
774 0           $entry=~s/(\W)/\\$1/g;
775 0           $datarcv=~s/$entry//;
776 0 0         if ($label eq 'Username') {
    0          
    0          
777 0 0         $found=0 if ($value eq '');
778 0 0         $fulldata.="User\t\t: $value\@" if ($found==1);
779             } elsif ($label eq 'Domain') {
780 0 0         $fulldata.=$value."\n" if ($found==1);
781             } elsif ($label eq 'Sex') {
782 0 0         if ($value eq '1') {
    0          
783 0           $value='Male';
784             } elsif ($value eq '2') {
785 0           $value='Female';
786             } else {
787 0           $value='Unknown';
788             }
789 0 0         $fulldata.=$label."\t\t: ".$value."\n" if ($found==1);
790             } else {
791 0 0         $fulldata.=$label."\t: ".$value."\n" if ($found==1);
792             }
793             }
794 0           $fentr++;
795             # this is the separator between two entries
796 0 0         $fulldata.="----------------------------------------\n" if ($found==1);
797             }
798 0 0         print "DEBUG anketa_info: $fulldata\n" if ($self->{_debug});
799 0           $data->set_server_msg($data->{TYPE_SERVER_ANKETA},$self->{_login},$fulldata);
800             } elsif ($msgrcv==MRIM_CS_USER_INFO) {
801 0           my @datas=_from_mrim_us("ssss",$datarcv);
802 0           $data->set_server_msg($data->{TYPE_SERVER_NOTIFY},$self->{_login},"$datas[0]: $datas[1] | $datas[2]: $datas[3]");
803             } elsif ($msgrcv==MRIM_CS_SMS_ACK) {
804 0           my @datas=_from_mrim_us("u",$datarcv);
805             # actually, MRIM seems to return always "1"... so i leave the outpout only for debug
806 0 0         $data->set_message("DEBUG",$self->{_login},"SMS ACK: $datas[0]") if ($self->{_debug});
807             # wild guess that "0" should mean "SUCCESS"
808 0 0         $data->set_server_msg($data->{TYPE_SERVER_NOTIFY},$self->{_login},"SMS_SENT") if ($datas[0]==0);
809             } else {
810 0 0         $data->set_message("DEBUG",$self->{_login},$datarcv) if ($self->{_debug});
811             }
812 0           return $data;
813             }
814              
815             # this is to decode mrim's combination of ulong and lps that is sent as message data
816             sub _from_mrim_us {
817 0     0     my ($pattern,$data)=@_;
818 0           my @res=();
819 0           for (my $i=0;$i
820 0           my $datatype=substr($pattern,$i,1);
821 0 0         if ($datatype eq 'u') {
    0          
822 0 0         if ( $data=~m/^(\C{4})(\C*)/) {
823 0           my $item=unpack("V",$1);
824 0           $data=$2;
825 0           push @res,$item;
826             } else {
827 0           push @res,0;
828             }
829             } elsif ($datatype eq 's') {
830 0           $data=~m/^(\C{4})(\C*)/s;
831 0           my $itemlength=$1;
832 0 0         if ($itemlength) {
833 0           $data=$2;
834 0           $itemlength=unpack("V",$itemlength);
835 0 0         if ($itemlength<4096) {
836 0           $data=~m/^(\C{$itemlength})(\C*)/;
837 0           my $item=$1;
838 0           $data=$2;
839 0           push @res,$item;
840             } else {
841 0           $data=~s/^\0//;
842 0           push @res, "";
843             }
844             } else {
845 0           push @res, "";
846             }
847             }
848             }
849 0           push @res,$data;
850 0           return @res;
851             }
852              
853             # convert to LPS (read the protocol !)
854             sub _to_lps {
855 0     0     my ($str)=@_;
856 0           return pack("V",length($str)).$str;
857             }
858              
859             1;