001    /*
002     Copyright (C) 2003 Adam Olsen
003     This program is free software; you can redistribute it and/or modify
004     it under the terms of the
005     GNU General Public License as published by
006     the Free Software Foundation; either version 1, or (at your option)
007     any later version.
008     This program is distributed in the hope that it will be useful,
009     but WITHOUT ANY WARRANTY; without even the implied warranty of
010     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
011     GNU General Public License for more details.
012     You should have received a copy of the GNU General Public License
013     along with this program; if not, write to the Free Software
014     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
015     */
016    package com.valhalla.jbother;
017    
018    import java.awt.event.ActionEvent;
019    import java.util.*;
020    
021    import javax.swing.*;
022    
023    import org.jivesoftware.smack.*;
024    import org.jivesoftware.smack.filter.*;
025    import org.jivesoftware.smack.packet.*;
026    import org.jivesoftware.smack.provider.ProviderManager;
027    import org.jivesoftware.smackx.*;
028    import org.jivesoftware.smackx.packet.*;
029    import org.jivesoftware.smackx.filetransfer.*;
030    import org.jivesoftware.smackx.muc.MultiUserChat;
031    import org.jivesoftware.smackx.packet.Time;
032    import org.jivesoftware.smackx.provider.*;
033    
034    import com.valhalla.gui.Standard;
035    import com.valhalla.jbother.*;
036    import com.valhalla.jbother.jabber.smack.*;
037    import com.valhalla.jbother.jabber.*;
038    import com.valhalla.jbother.preferences.*;
039    import com.valhalla.jbother.jabber.smack.provider.*;
040    import com.valhalla.misc.GnuPG;
041    import com.valhalla.misc.SimpleXOR;
042    import com.valhalla.settings.Settings;
043    
044    /**
045     * Attempts to connect to the server. If the connection is made successfully, it
046     * sets up the various packet listeners and displays the BuddyList
047     *
048     * @author Adam Olsen
049     * @author Andrey Zakirov
050     * @created April 10, 2005
051     * @version 1.0
052     */
053    public class ConnectorThread implements Runnable {
054        private static ConnectorThread instance = null;
055        private Thread thread = null;
056    
057        private ResourceBundle resources = ResourceBundle.getBundle(
058                "JBotherBundle", Locale.getDefault());
059    
060        private String server, username, resource;
061    
062        private String password = null;
063    
064        private boolean ssl;
065        private boolean gmail;
066        private boolean proxy;
067    
068        private int port = 0;
069        private String proxyhost = "";
070        private int proxyport = 0;
071    
072        private String errorMessage;
073    
074        private XMPPConnection connection = null;
075    
076        private boolean hasHadError = false;
077    
078        private com.valhalla.jbother.jabber.smack.ConnectionListener conListener = new com.valhalla.jbother.jabber.smack.ConnectionListener();
079    
080        private com.valhalla.jbother.jabber.smack.RosterListener
081         rosterListener =
082          new com.valhalla.jbother.jabber.smack.RosterListener();
083        private MessagePacketListener messageListener = new MessagePacketListener();
084    
085        private Presence.Mode connectMode = Presence.Mode.AVAILABLE;
086    
087        private String statusString = null;
088    
089        private boolean persistent = false;
090    
091        private boolean cancelled = false;
092    
093        private boolean away = false;
094    
095        private MessageEventManager eventManager;
096    
097        private String gnupgSecretKey = Settings.getInstance().getProperty(
098                "gnupgSecretKeyID");
099    
100        private String gnupgTempPass = null;
101    
102        private int connectCount = 0;
103        private Roster roster = null;
104        private RosterExchangeManager exchangeManager;
105        private FileTransferManager ftmanager = null;
106    
107        /**
108         * Sets up the connector thread
109         *
110         * @param connectMode
111         *            Description of the Parameter
112         * @param statusString
113         *            Description of the Parameter
114         * @param away
115         *            Description of the Parameter
116         */
117        private ConnectorThread() {
118    
119            ProviderManager.addIQProvider("query", "jabber:iq:last",
120                    new LastActivityProvider());
121    
122            ProviderManager.addExtensionProvider("x", "jabber:x:encrypted",
123                    new EncryptedProvider());
124            ProviderManager.addExtensionProvider("x", "jabber:x:signed",
125                    new SignedProvider());
126    
127            ProviderManager.addIQProvider("query", "jabber:iq:search",
128                new com.valhalla.jbother.jabber.smack.provider.SearchProvider());
129    
130            ProviderManager.addIQProvider("vCard", "vcard-temp",
131                new VCardProvider());
132    
133            PrivateDataManager.addPrivateDataProvider("storage", "storage:bookmarks", new BookmarkProvider());
134        }
135    
136        public XMPPConnection getConnection() {
137            return connection;
138        }
139        
140        public FileTransferManager getFileTransferManager() {
141            return ftmanager;
142        }
143    
144        public boolean isAlive()
145        {
146            if( thread == null ) return false;
147            else return thread.isAlive();
148        }
149    
150        public void start()
151        {
152            thread = new Thread(this);
153            thread.start();
154        }
155    
156        public RosterExchangeManager getExchangeManager() { return exchangeManager; }
157    
158        public static ConnectorThread getInstance()
159        {
160            if( instance == null ) instance = new ConnectorThread();
161            return instance;
162        }
163    
164        public Roster getRoster() { return roster; }
165    
166        public ConnectorThread init( Presence.Mode connectMode, String statusString,
167                boolean away )
168        {
169            this.server = Settings.getInstance().getProperty("defaultServer");
170            if( server != null ) server = server.toLowerCase();
171            this.username = Settings.getInstance().getProperty("username");
172            this.resource = Settings.getInstance().getProperty("resource");
173            this.ssl = Settings.getInstance().getBoolean("useSSL");
174            this.gmail = Settings.getInstance().getBoolean("gmailBox");
175            this.proxy = Settings.getInstance().getBoolean("useProxy");
176    
177            String p = Settings.getInstance().getProperty("port");
178            if (p != null) {
179                try{ port = Integer.parseInt(p); }
180                catch (NumberFormatException ignore){ }
181            }
182    
183            if(this.proxy){
184                this.proxyhost = Settings.getInstance().getProperty("proxyHost");
185                String t = Settings.getInstance().getProperty("proxyPort");
186                try{ proxyport = Integer.parseInt(t); }
187                catch(NumberFormatException ignore){ }
188            }
189    
190            hasHadError = false;
191            this.connectMode = connectMode;
192            this.statusString = statusString;
193            this.away = away;
194            connectCount = 0;
195    
196            return instance;
197        }
198    
199        public void resetCredentials() {
200            gnupgSecretKey = Settings.getInstance().getProperty("gnupgSecretKeyID");
201            password = Settings.getInstance().getProperty("password");
202            if (password != null)
203                password = SimpleXOR.decrypt(password, "JBother rules!");
204            gnupgTempPass = null;
205        }
206    
207        /**
208         * Gets the messageEventManager attribute of the ConnectorThread class
209         *
210         * @return The messageEventManager value
211         */
212        public MessageEventManager getMessageEventManager() {
213            return eventManager;
214        }
215    
216        /**
217         * Sets the cancelled attribute of the ConnectorThread class
218         *
219         * @param c
220         *            The new cancelled value
221         */
222        public void setCancelled(boolean c) {
223            cancelled = c;
224            connectCount = 0;
225        }
226    
227        /**
228         * Sets whether or not this thread should try to reconnect if there is a
229         * connection error
230         *
231         * @param persistent
232         *            set to <tt>true</tt> if you want the thread to continue to
233         *            try and connect even if there's an error
234         */
235        public void setPersistent(boolean persistent) {
236            this.persistent = persistent;
237            connectCount = 0;
238        }
239    
240        /**
241         * Returns the ConnectionListener
242         *
243         * @return the connection listener
244         */
245        public com.valhalla.jbother.jabber.smack.ConnectionListener getConnectionListener() {
246            return conListener;
247        }
248    
249        /**
250         * Sets whether or not a connection error has already been thrown for this
251         * connection
252         *
253         * @param has
254         *            true if an error has already been thrown
255         */
256        public void setHasHadError(boolean has) {
257            hasHadError = has;
258        }
259    
260        /**
261         * Called when the Threads .start() method is called
262         */
263        public void run() {
264            errorMessage = null;
265            resetCredentials();
266            com.valhalla.Logger.debug("Connector thread starting...");
267            if (!BuddyList.getInstance().getStatusMenu()
268                    .blinkTimerIsRunning()) {
269                BuddyList.getInstance().getStatusMenu()
270                        .startBlinkTimer();
271            }
272    
273            if( connectCount >= 15 )
274            {
275                BuddyList.getInstance().getStatusMenu().stopBlinkTimer();
276    
277                SwingUtilities.invokeLater( new Runnable()
278                    {
279                        public void run()
280                        {
281                            BuddyList.getInstance().getStatusMenu()
282                                    .setModeChecked(null);
283                        }
284                    } );
285                int result = JOptionPane.showConfirmDialog(
286                                null,
287                                resources.getString( "connectCountTooHigh" ),
288                                "JBother", JOptionPane.YES_NO_OPTION);
289    
290                connectCount = 0;
291    
292                if( result == JOptionPane.YES_OPTION )
293                {
294                    run();
295                    return;
296                }
297    
298                cancelled = true;
299                return;
300            }
301    
302            if (password == null) {
303                PasswordDialog dialog = new PasswordDialog(BuddyList.getInstance().getContainerFrame(),resources
304                        .getString("jabberPassword"));
305                password = dialog.getText();
306            }
307    
308            if (gnupgSecretKey != null && !JBotherLoader.isGPGEnabled()) {
309                int result = JOptionPane
310                        .showConfirmDialog(
311                                null,
312                                "Warning: There is a GnuPG secrety key ID in your profile,\nbut it appears as though GnuPG is not installed on this system.\nWould you still like to connect to the server?",
313                                "GnuPG", JOptionPane.YES_NO_OPTION);
314    
315                if (result != JOptionPane.YES_OPTION) {
316                    BuddyList.getInstance().getStatusMenu()
317                            .stopBlinkTimer();
318                    BuddyList.getInstance().init(null);
319                    return;
320                }
321            }
322    
323            else if ((gnupgSecretKey != null && JBotherLoader.isGPGEnabled())
324                    && (BuddyList.getInstance().getGnuPGPassword() == null)) {
325                GnuPG gnupg = new GnuPG();
326                while (true) {
327                    PasswordDialog dialog = new PasswordDialog(BuddyList.getInstance().getContainerFrame(),resources
328                            .getString("gnupgKeyPassword"));
329                    gnupgTempPass = dialog.getText();
330                    if ((gnupgTempPass != null)
331                            && (gnupg.sign("1", gnupgSecretKey, gnupgTempPass))) {
332                        BuddyList.getInstance().setGnuPGPassword(gnupgTempPass);
333                        break;
334                    } else {
335                        BuddyList.getInstance().getStatusMenu()
336                                .stopBlinkTimer();
337                        Standard
338                                .warningMessage(null, "GnuPG Error",
339                                        "Wrong GnuPG passphrase! Please, try connecting again.");
340                        BuddyList.getInstance().init(null);
341                        BuddyList.getInstance().setGnuPGPassword(null);
342                        return;
343                    }
344                }
345            }
346    
347            if (cancelled) {
348                BuddyList.getInstance().getStatusMenu()
349                        .stopBlinkTimer();
350                cancelled = false;
351                connectCount = 0;
352                return;
353            }
354    
355            try {
356                XMPPConnection.DEBUG_ENABLED = true;
357                if (com.valhalla.settings.Arguments.getInstance().getProperty("smackdebug") != null) {
358                }else {
359                    System.setProperty("smack.debuggerClass", "com.valhalla.jbother.jabber.Debugger");
360                }
361    
362                int port = this.port;
363                if (port == 0 && ssl) {
364                    port = 5223;
365                } else if (port == 0 && !ssl) {
366                    port = 5222;
367                }
368    
369                connection = null;
370    
371                //smart user@service with different servername
372                //talk.google.com users use username@gmail.com and talk.google.com for servername
373                int b = 0;
374                String service = null;
375                if( ( b = username.indexOf("@") ) != -1){
376                    service = username.substring(b+1);
377                    username = username.substring(0, b);
378                }   
379                
380                if(proxy){
381                    if ( service == null){
382                        connection = new XMPPConnection(server, port, server, new ProxySocketFactory(proxyhost, proxyport));
383                    } else connection = new XMPPConnection(server, port, service, new ProxySocketFactory(proxyhost, proxyport));  
384                }else if (ssl){
385                    if ( service == null){
386                        connection = new SSLXMPPConnection(server, port);
387                    } else connection = new SSLXMPPConnection(server, port, service);
388                }else{
389                    if ( service == null){
390                        connection = new XMPPConnection(server, port);
391                    } else connection = new XMPPConnection(server, port, service);
392                }
393    
394                BuddyList.getInstance().init(connection);
395                BuddyList.getInstance().clearTree();
396            }catch(Exception ex){
397                errorMessage = ex.getMessage();
398            }
399    
400            if(cancelled){
401                BuddyList.getInstance().getStatusMenu().stopBlinkTimer();
402                cancelled = false;
403    
404                return;
405            }
406    
407            // get the resource from the login box
408            String tmp = resource;
409            if (tmp == null || tmp.equals("")) {
410                tmp = "JBother";
411            }
412            final String resource = tmp;
413    
414            if (errorMessage == null && connection != null) {
415                PacketFilter anyFilter = new PacketFilter() {
416                    public boolean accept(Packet packet) {
417                        return true;
418                    }
419                };
420    
421                // sets up the various packet listeners
422                PacketFilter filter = new PacketTypeFilter(Presence.class);
423    
424                connection.addPacketListener(new PresencePacketListener(), filter);
425                filter = new PacketTypeFilter(Message.class);
426                connection.addPacketListener(messageListener, filter);
427                connection.addConnectionListener(conListener);
428                filter = new PacketTypeFilter(Version.class);
429                connection.addPacketListener(new VersionListener(), filter);
430                filter = new PacketTypeFilter(com.valhalla.jbother.jabber.smack.LastActivity.class);
431                connection.addPacketListener(new LastActivityListener(), filter);
432                filter = new PacketTypeFilter(Time.class);
433                connection.addPacketListener(new TimeListener(), filter);
434                connection.addPacketListener(new IQPacketListener(), filter);
435                ftmanager = new FileTransferManager(connection);
436                ftmanager.addFileTransferListener(new FTReceiveListener());
437    
438                exchangeManager = new RosterExchangeManager(connection);
439                exchangeManager.addRosterListener(new ExchangeListener());
440    
441                // this filter will listen to three types of messages:
442                // <si>, <streamhost> and <streamhost-used>
443                /*filter = new OrFilter(new PacketTypeFilter(Streamhost.class),
444                        new PacketTypeFilter(StreamhostUsed.class));
445                OrFilter filter2 = new OrFilter(new PacketTypeFilter(
446                        StreamInitiation.class), filter);
447                connection.addPacketListener( new StreamInitiationListener(),
448                 filter2 );*/
449    
450                // attempts to connect
451                try {
452                    connection.login(username, password, resource);
453    
454                    //SmackConfiguration.setPacketReplyTimeout(5000);
455    
456                    roster = connection.getRoster();
457    
458                    roster.setSubscriptionMode(
459                            Roster.SUBSCRIPTION_MANUAL);
460                    roster.addRosterListener( rosterListener );
461    
462                    eventManager = new MessageEventManager(connection);
463    
464                    eventManager
465                            .addMessageEventNotificationListener(new EventNotificationListener());
466                    eventManager
467                            .addMessageEventRequestListener(new EventRequestListener());
468                    MultiUserChat.addInvitationListener(connection,
469                            new InvitationPacketListener());
470                } catch (XMPPException e) {
471                    errorMessage = e.getMessage();
472                    if (e.getXMPPError() != null) {
473                        errorMessage = resources.getString("xmppError"
474                                + e.getXMPPError().getCode());
475                    }
476                }
477            }
478    
479            // if there was an error, display it, and then redisplay a LoginDialog
480            if (errorMessage != null || connection == null) {
481                resetCredentials();
482    
483                SwingUtilities.invokeLater( new Runnable()
484                    {
485                        public void run()
486                        {
487                            BuddyList.getInstance().getStatusMenu()
488                                    .setModeChecked(null);
489                        }
490                    } );
491    
492                if (connection != null) {
493                    connection.removeConnectionListener(conListener);
494                }
495    
496                if (persistent) {
497                    try {
498                        Thread.sleep(connectCount*5000);
499                    } catch (InterruptedException ex) {
500                        com.valhalla.Logger.logException(ex);
501                    }
502                    com.valhalla.Logger.debug("Connection error was: "
503                            + errorMessage);
504                    errorMessage = null;
505    
506                    if (cancelled) {
507                        cancelled = false;
508    
509                        BuddyList.getInstance().getStatusMenu()
510                                .stopBlinkTimer();
511                        connectCount = 0;
512                        return;
513                    }
514    
515                    messageListener.resetQueue();
516                    com.valhalla.Logger.debug( "Retrying, attempt #" + connectCount );
517                    connectCount++;
518    
519                    run();
520                    return;
521                }
522    
523                password = Settings.getInstance().getProperty("password");
524    
525                BuddyList.getInstance().getStatusMenu()
526                        .stopBlinkTimer();
527    
528                if (errorMessage == null) {
529                    errorMessage = resources.getString("connectionError");
530                }
531                if (errorMessage.equals("Unauthorized")) {
532                    errorMessage = new String(resources
533                            .getString("invalidPassword"));
534                }
535    
536                Standard.warningMessage(null, resources
537                        .getString("couldNotConnect"), errorMessage);
538                BuddyList.getInstance().init(null);
539    
540                return;
541            }
542    
543            SwingUtilities.invokeLater( new Runnable() {
544                    public void run()
545                    {
546                        BuddyList.getInstance().getStatusMenu().stopBlinkTimer();
547                        BuddyList.getInstance().getBuddiesMenu().logOn();
548    
549                        // otherwise, set up and display the buddy list
550                        com.valhalla.Logger.debug("Connected");
551    
552                        BuddyList.getInstance().resetAwayTimer();
553    
554                        BuddyList.getInstance().getStatusMenu().setModeChecked(
555                                connectMode);
556                        BuddyList.getInstance().initBuddies();
557    
558                        // display the buddies
559                        BuddyList.getInstance().setStatus(connectMode, statusString, false);
560                        messageListener.startTimer();
561    
562                        if (away) {
563                            BuddyList.getInstance().getAwayHandler().actionPerformed(
564                                    new ActionEvent(BuddyList.getInstance(), 1, "away"));
565    
566                        }
567                    }
568            } );
569    
570            return;
571        }
572    }
573