View Javadoc
1   //////////////////////////////////////////////////////////////////////////////
2   // dexterIM - Instant Messaging Framework
3   // Copyright (C) 2003  Christoph Walcher
4   //
5   // This program is free software; you can redistribute it and/or modify
6   // it under the terms of the GNU General Public License as published by
7   // the Free Software Foundation; either version 2 of the License, or
8   // (at your option) any later version.
9   //
10  // This program is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  // GNU General Public License for more details.
14  //
15  // You should have received a copy of the GNU General Public License
16  // along with this program; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  //////////////////////////////////////////////////////////////////////////////
19  package net.sf.dexterim.msn;
20  
21  import java.io.IOException;
22  import java.io.InputStreamReader;
23  import java.io.PrintStream;
24  import java.net.Socket;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import net.sf.dexterim.core.AbstractIMConnection;
30  import net.sf.dexterim.core.Account;
31  import net.sf.dexterim.core.ContactList;
32  import net.sf.dexterim.core.Conversation;
33  import net.sf.dexterim.msn.entity.SwitchBoardServer;
34  import net.sf.dexterim.msn.event.MessageEvent;
35  import net.sf.dexterim.msn.event.MessageListener;
36  import net.sf.dexterim.msn.io.BufferedMsnMessageReader;
37  import net.sf.dexterim.msn.message.ChallengeMessage;
38  import net.sf.dexterim.msn.message.ContactListMessage;
39  import net.sf.dexterim.msn.message.InviteMessage;
40  import net.sf.dexterim.msn.message.MsnMessage;
41  import net.sf.dexterim.msn.message.NewServerMessage;
42  import net.sf.dexterim.msn.message.SwitchBoardMessage;
43  import net.sf.dexterim.msn.message.TweenerMessage;
44  import net.sf.dexterim.msn.tweener.LoginServer;
45  import net.sf.dexterim.msn.tweener.PassportNexus;
46  import net.sf.dexterim.msn.util.SpecialCharacters;
47  
48  import org.apache.commons.logging.Log;
49  import org.apache.commons.logging.LogFactory;
50  
51  /***
52   *
53   * @author  Christoph Walcher
54   */
55  public class MsnConnection extends AbstractIMConnection 
56                             implements MessageListener {
57                             	
58    
59    private static final String STANDARD_HOST = "messenger.hotmail.com";
60    private static final int STANDARD_PORT = 1863;
61    private static final int SOCKET_TIMEOUT = 10000;
62    
63    private boolean connected;
64    private Log log;
65    private int transmissionID;
66    private BufferedMsnMessageReader messageReader;
67    private PrintStream out;
68    private Socket socket;
69    private Account account;
70    private Map pendingSwitchBoardRequests;
71    private ContactList contactList;
72    
73    /*** Creates a new instance of MsnConnection */
74    public MsnConnection() {
75      connected = false;
76      
77      log = LogFactory.getLog(String.class);
78  
79      pendingSwitchBoardRequests = new java.util.HashMap();
80      transmissionID = 0;
81    }
82  
83    public void connect(Account account) {
84      if ((account == null)
85        || (account.getPassport() == null)
86        || (account.getIdentity() == null)) {
87        throw new IllegalArgumentException("Illegal Account parameter");
88      }
89  
90      this.account = account;
91  
92      connect(account, STANDARD_HOST, STANDARD_PORT);
93  
94      ReceiverThread receiver =
95        new ReceiverThread(connectedAs().toString(), messageReader);
96  
97      receiver.addMessageListener(this);
98  
99      receiver.start();
100   }
101 
102   protected void connect(Account account, String host, int port) {
103     if (log.isDebugEnabled()) {
104       log.debug("Connecting to server " + host + " on port " + port);
105     }
106 
107     try {
108       socket = new Socket(host, port);
109       socket.setSoTimeout(SOCKET_TIMEOUT);
110 
111       messageReader = new BufferedMsnMessageReader(
112           new InputStreamReader(socket.getInputStream()));
113       out = new PrintStream(socket.getOutputStream());
114 
115       List versions = MsnConfiguration.getInstance().getVersions();
116 
117       // Start Login by sending Versions of Protocol supported to server
118       send(out, "VER", joinVersions(versions));
119 
120       if (log.isDebugEnabled()) {
121         log.debug("Reply to VER Command: " + messageReader.readLine());
122       }
123 
124       send(out, "CVR", createCVRRequest(account));
125       
126       messageReader.readLine();
127      
128 			/*
129 			 * + The first parameter is the authentication system (always TWN). 
130 			 * + The second parameter is always the letter I (standing for initiating
131 			 * authentication). 
132 			 * + The third parameter is the account name that you want
133 			 * to log on with. If the server does not like your USR, it will close
134 			 * the connection with no reply, or possibly send an error first, Error
135 			 * 911 is sent if you replace the I with an S or when sending invalid
136 			 * account names such as hotmail.com. Error 928 is sent if you send a bad
137 			 * ticket. Sometimes, when the server is having problems or is down for
138 			 * maintenance, it will reply with an error instead of logging you in.
139 			 * Some possible errors include error 500, error 601, error 910, and
140 			 * error 921.
141 			 */
142       send(out, "USR", "TWN I " + account.getIdentity());
143       
144 
145       // Old MD5 Stuff - Changed in Version 10
146       //send(out, "INF");
147       //if (!(messageReader.readLine()).endsWith("MD5")) {
148       //  log.error("No Authentication received!");
149       //}
150       //check that info for user in on this server
151       //send(out, "USR", "MD5 I " + account.getIdentity());
152 
153       MsnMessage message = messageReader.readMesage();
154 
155       if ((message != null) && message instanceof NewServerMessage) {
156         socket.close();
157 
158         NewServerMessage nsMessage = (NewServerMessage)message;
159 
160         if (log.isDebugEnabled()) {
161           log.debug(
162             "Received redirect to "
163               + nsMessage.getHost()
164               + ":"
165               + nsMessage.getPort());
166         }
167 
168         connect(account, nsMessage.getHost(), nsMessage.getPort());
169 
170         return;
171       }
172       else if (message != null && message instanceof TweenerMessage) {
173       	log.debug("Tweener Message received!");
174       	
175       	String challenge = ((TweenerMessage)message).getTweenerToken();
176       	
177       	log.debug(((TweenerMessage)message).getTweenerToken());
178       	
179       	PassportNexus nexus = new PassportNexus();
180       	
181       	LoginServer server = nexus.connect();
182       	
183       	server.setUsername(account.getIdentity().toString());
184       	server.setChallenge(challenge);
185       	server.setPassword(account.getPassport().toString());
186       	
187       	String authTicket = server.authTicket();
188       	
189       	log.debug("Auth Ticket: " + authTicket);
190       	
191       	send(out, "USR", "TWN S " + authTicket);
192       	
193       	message = messageReader.readMesage();
194       	
195       	log.debug(message);
196       	
197       	message = messageReader.readMesage();
198       	
199       	log.debug(message);
200       	
201       	send(out, "CHG", "NLN 536870948 %3Cmsnobj%20Creator%3D%22christoph_walcher%40hotmail.com%22%20Size%3D%2221875%22%20Type%3D%223%22%20Location%3D%22TFR2.dat%22%20Friendly%3D%22AAA%3D%22%20SHA1D%3D%226gd8F29ZTWfHMcQPjwYhFSE20a4%3D%22%20SHA1C%3D%22tuiETKRq%2FKn6J4cxuJji1eqzTEI%3D%22%2F%3E");
202       	
203       	log.debug(SpecialCharacters.getInstance().decode("%3Cmsnobj%20Creator%3D%22christoph_walcher%40hotmail.com%22%20Size%3D%2221875%22%20Type%3D%223%22%20Location%3D%22TFR2.dat%22%20Friendly%3D%22AAA%3D%22%20SHA1D%3D%226gd8F29ZTWfHMcQPjwYhFSE20a4%3D%22%20SHA1C%3D%22tuiETKRq%2FKn6J4cxuJji1eqzTEI%3D%22%2F%3E", true));
204       	
205       	setConnected(true);
206       }
207 
208      /* 
209       else if ((message != null) && message instanceof MD5ReplyMessage) {
210         MD5ReplyMessage md5Message = (MD5ReplyMessage)message;
211 
212         String hash = md5Message.getHash() + account.getPassport();
213         
214         MD5 encrypt = new MD5(hash);
215 
216         send(out, "USR", "MD5 S " + encrypt.toHex());
217 
218         if (log.isDebugEnabled()) {
219           log.debug("Sending Encrypted Password: " + encrypt.toHex());
220         }
221 
222         message = messageReader.readMesage();
223 
224         if (log.isDebugEnabled()) {
225           log.debug("Received Message " + message);
226         }
227 
228         if ((message != null) && message instanceof LoginReplyMessage) {
229           LoginReplyMessage loginMessage = (LoginReplyMessage)message;
230 
231           account.setFriendlyName(loginMessage.getFriendlyName());
232 
233           setConnected(true);
234         }
235         else if (log.isErrorEnabled()) {
236           log.error("Wrong Password supplied. Received message: " + message);
237         }
238 
239         send(out, "CHG", "NLN");
240       }*/
241     }
242     catch (Exception ex) {
243       if (log.isErrorEnabled()) {
244         log.error("Could not connect to MSN Network", ex);
245       }
246     }
247   }
248 
249   private String joinVersions(List versions) {
250   	StringBuffer stringList = new StringBuffer();
251   	
252   	for (Iterator iter = versions.iterator(); iter.hasNext();) {
253 			String version = (String) iter.next();
254 			
255 			stringList.append(version);
256 			stringList.append(" ");
257 		}
258   	
259   	return stringList.toString();
260   }
261   
262 	private String createCVRRequest(Account account) {
263 		return MsnConfiguration.getInstance().getHostInformation() + " "
264 				+ account.getIdentity();
265 	}
266   
267   public void disconnect() {
268     try {
269       if (out != null) {
270         out.println("OUT");
271       }
272 
273       if (messageReader != null) {
274         messageReader.close();
275       }
276 
277       if (out != null) {
278         out.close();
279       }
280 
281       if (socket != null) {
282         socket.close();
283       }
284     }
285     catch (IOException ioex) {
286       if (log.isErrorEnabled()) {
287         log.error("Could not clean up resources", ioex);
288       }
289     }
290     finally {
291       setConnected(false);
292       this.account = null;
293     }
294   }
295 
296   public boolean isConnected() {
297     return connected;
298   }
299 
300   protected synchronized void send(String command, String body, PrintStream out) {
301     try {
302     	log.debug(command + " " + transmissionID + " " + body);
303     	
304       out.println(command + " " + transmissionID + " " + body);
305       transmissionID++;
306 
307       // increment trial ID
308     }
309     catch (Exception ex) {
310       if (log.isErrorEnabled()) {
311         log.error("Exception occured while send", ex);
312       }
313     }
314   }
315   
316   protected synchronized void send(PrintStream out, String s) {
317     try {
318     	log.debug(s + " " + transmissionID);
319     	
320       out.println(s + " " + transmissionID);
321       transmissionID++;
322 
323       // increment trial ID
324     }
325     catch (Exception ex) {
326       if (log.isErrorEnabled()) {
327         log.error("Exception occured while send", ex);
328       }
329     }
330   }
331 
332   /***
333   *  DOCUMENT ME!
334   *
335   *@param  s1  DOCUMENT ME!
336   *@param  s2  DOCUMENT ME!
337   */
338   protected synchronized void send(
339     PrintStream out,
340     String s1,
341     String s2) {
342     try {
343       if ("XFR".equals(s1)) {
344       	log.debug(s1 + " " + s2 + " " + transmissionID);
345       	out.println(s1 + " " + s2 + " " + transmissionID);
346       }
347       else {
348       	log.debug(s1 + " " + transmissionID + " " + s2);
349         out.println(s1 + " " + transmissionID + " " + s2);
350       }
351 
352       transmissionID++;
353 
354       // increment trial ID
355     }
356     catch (Exception ex) {
357       if (log.isErrorEnabled()) {
358         log.error("Exception occured while send", ex);
359       }
360     }
361   }
362 
363   /*** Setter for property connected.
364   * @param connected New value of property connected.
365   *
366   */
367   protected void setConnected(boolean connected) {
368     this.connected = connected;
369   }
370 
371   public Account connectedAs() {
372     return account;
373   }
374 
375   public Conversation createConversation() {
376     MsnConversation conversation = new MsnConversation(this);
377 
378     fireConversationCreated(conversation);
379 
380     return conversation;
381   }
382 
383   public synchronized void sendSwitchboardRequest(MsnConversation conversation) {
384     if (log.isDebugEnabled()) {
385       log.debug(
386         "Storing Switchboard request under id "
387           + Integer.toString(transmissionID));
388     }
389 
390     pendingSwitchBoardRequests.put(
391       Integer.toString(transmissionID),
392       conversation);
393 
394     out.println("XFR " + transmissionID + " SB");
395     transmissionID++;
396   }
397 
398   protected void switchBoardReceived(SwitchBoardMessage message) {
399     if (log.isDebugEnabled()) {
400       log.debug("Received Switchboard message with id " + message.getId());
401     }
402 
403     if (pendingSwitchBoardRequests.containsKey(message.getId())) {
404       MsnConversation conversation =
405         (MsnConversation)pendingSwitchBoardRequests.remove(message.getId());
406 
407       if (log.isDebugEnabled()) {
408         log.debug("Switchboard loaded from pending requests ");
409       }
410 
411       SwitchBoardServer server = message.getSwitchBoardServer();
412 
413       conversation.bind(server);
414     }
415   }
416 
417   public synchronized void challengeReceived(ChallengeMessage message) {
418   	log.debug("Challenge received Anser: QRY "
419         + transmissionID
420         + MsnConfiguration.getInstance().getChallengeMagic() + " 32\r\n"
421         + message.getMd5Hash().toHex());
422   
423     out.print(
424       "QRY "
425         + transmissionID
426         + MsnConfiguration.getInstance().getChallengeMagic() + " 32\r\n"
427         + message.getMd5Hash().toHex());
428         
429     transmissionID++;
430   }
431 
432   protected synchronized void inviteReceived(InviteMessage message) {
433     MsnConversation conversation = new MsnConversation(this);
434 
435     fireConversationCreated(conversation);
436 
437     conversation.answer(message.getServer(), message.getConversationID());
438   }
439 
440   public void messageReceived(MessageEvent event) {
441     MsnMessage message = (MsnMessage)event.getMessage();
442 
443     log.info(message);
444     
445     message.process(this);
446   }
447 
448   /***
449   *  DOCUMENT ME!
450   *
451   *@param  message  DOCUMENT ME!
452   *      the other lists
453   */
454   public void processContact(ContactListMessage message) {
455     log.debug("Contact List Message Received" + message.getAccount());
456     MsnContact contact = new MsnContact(
457       message.getAccount(),
458       message.getNick());
459     
460     contactList.add(contact);
461   }
462 
463   public void synchronizeContactList(ContactList contactList) {
464     this.contactList = contactList;
465 
466     send(out, "SYN", "0 0");
467   }
468 }