KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > james > imapserver > SingleThreadedConnectionHandler


1 /***********************************************************************
2  * Copyright (c) 2000-2004 The Apache Software Foundation. *
3  * All rights reserved. *
4  * ------------------------------------------------------------------- *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you *
6  * may not use this file except in compliance with the License. You *
7  * may obtain a copy of the License at: *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14  * implied. See the License for the specific language governing *
15  * permissions and limitations under the License. *
16  ***********************************************************************/

17
18 package org.apache.james.imapserver;
19
20 import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
21 import org.apache.avalon.cornerstone.services.scheduler.PeriodicTimeTrigger;
22 import org.apache.avalon.cornerstone.services.scheduler.Target;
23 import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
24 import org.apache.avalon.framework.activity.Disposable;
25 import org.apache.avalon.framework.activity.Initializable;
26 import org.apache.avalon.framework.component.ComponentException;
27 import org.apache.avalon.framework.component.ComponentManager;
28 import org.apache.avalon.framework.component.Composable;
29 import org.apache.avalon.framework.configuration.Configurable;
30 import org.apache.avalon.framework.logger.Logger;
31 import org.apache.james.imapserver.AccessControlException;
32 import org.apache.james.imapserver.AuthorizationException;
33 import org.apache.james.Constants;
34 import org.apache.james.imapserver.commands.ImapCommand;
35 import org.apache.james.imapserver.commands.ImapCommandFactory;
36 import org.apache.james.services.MailServer;
37 import org.apache.james.services.UsersRepository;
38 import org.apache.james.services.UsersStore;
39 import org.apache.james.util.InternetPrintWriter;
40
41 import java.io.*;
42 import java.net.Socket JavaDoc;
43 import java.util.List JavaDoc;
44 import java.util.StringTokenizer JavaDoc;
45
46 /**
47  * An IMAP Handler handles one IMAP connection. TBC - it may spawn worker
48  * threads someday.
49  *
50  * <p> Based on SMTPHandler and POP3Handler by Federico Barbieri <scoobie@systemy.it>
51  *
52  * @version 0.3 on 08 Aug 2002
53  */

54 public class SingleThreadedConnectionHandler
55         extends BaseCommand
56         implements ConnectionHandler, Composable, Configurable,
57         Initializable, Disposable, Target, MailboxEventListener,
58         ImapSession, ImapConstants
59 {
60
61     private Logger securityLogger;
62     private MailServer mailServer;
63     private UsersRepository users;
64     private TimeScheduler scheduler;
65
66     private ImapSession _session;
67     private MailboxEventListener _mailboxListener;
68
69     private ImapCommandFactory _imapCommands;
70
71     private Socket JavaDoc socket;
72     private BufferedReader in;
73     private PrintWriter out;
74     private OutputStream outs;
75     private String JavaDoc remoteHost;
76     private String JavaDoc remoteIP;
77     private String JavaDoc softwaretype = "JAMES IMAP4rev1 Server " + Constants.SOFTWARE_VERSION;
78     private ImapSessionState state;
79     private String JavaDoc user;
80
81     private IMAPSystem imapSystem;
82     private Host imapHost;
83     private String JavaDoc namespaceToken;
84     private String JavaDoc currentNamespace = null;
85     private String JavaDoc currentSeperator = null;
86     private String JavaDoc commandRaw;
87     
88     //currentFolder holds the client-dependent absolute address of the current
89
//folder, that is current Namespace and full mailbox hierarchy.
90
private String JavaDoc currentFolder = null;
91     private ACLMailbox currentMailbox = null;
92     private boolean currentIsReadOnly = false;
93     private boolean connectionClosed = false;
94     private String JavaDoc tag;
95     private boolean checkMailboxFlag = false;
96     private int exists;
97     private int recent;
98     private List JavaDoc sequence;
99     
100     private boolean canParseCommand = true;
101     
102     public SingleThreadedConnectionHandler()
103     {
104         _session = this;
105         _mailboxListener = this;
106
107         _imapCommands = new ImapCommandFactory();
108     }
109
110     /**
111      * Set the components logger.
112      *
113      * @param logger the logger
114      */

115     public void enableLogging( Logger logger )
116     {
117         super.enableLogging( logger );
118         _imapCommands.enableLogging( logger );
119     }
120
121     public void compose( final ComponentManager componentManager )
122             throws ComponentException
123     {
124
125         mailServer = (MailServer) componentManager.
126                 lookup( "org.apache.james.services.MailServer" );
127         UsersStore usersStore = (UsersStore) componentManager.
128                 lookup( "org.apache.james.services.UsersStore" );
129         users = usersStore.getRepository( "LocalUsers" );
130         scheduler = (TimeScheduler) componentManager.
131                 lookup( "org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" );
132         imapSystem = (IMAPSystem) componentManager.
133                 lookup( "org.apache.james.imapserver.IMAPSystem" );
134         imapHost = (Host) componentManager.
135                 lookup( "org.apache.james.imapserver.Host" );
136     }
137
138     public void initialize() throws Exception JavaDoc
139     {
140         getLogger().info( "SingleThreadedConnectionHandler starting ..." );
141         securityLogger = getLogger().getChildLogger( "security" );
142         getLogger().info( "SingleThreadedConnectionHandler initialized" );
143     }
144
145     /**
146      * Handle a connection.
147      * This handler is responsible for processing connections as they occur.
148      *
149      * @param connection the connection
150      * @exception IOException if an error reading from socket occurs
151      * @exception ProtocolException if an error handling connection occurs
152      */

153     public void handleConnection( final Socket JavaDoc connection )
154             throws IOException
155     {
156
157         try {
158             this.socket = connection;
159             setIn( new BufferedReader( new
160                     InputStreamReader( socket.getInputStream() ) ) );
161             outs = socket.getOutputStream();
162             setOut( new InternetPrintWriter( outs, true ) );
163             remoteHost = socket.getInetAddress().getHostName();
164             remoteIP = socket.getInetAddress().getHostAddress();
165         }
166         catch ( Exception JavaDoc e ) {
167             getLogger().error( "Cannot open connection from " + getRemoteHost() + " ("
168                                + getRemoteIP() + "): " + e.getMessage() );
169         }
170         getLogger().info( "Connection from " + getRemoteHost() + " (" + getRemoteIP() + ")" );
171
172         try {
173             final PeriodicTimeTrigger trigger = new PeriodicTimeTrigger( timeout, -1 );
174             scheduler.addTrigger( this.toString(), trigger, this );
175
176             if ( false ) { // arbitrary rejection of connection
177
// could screen connections by IP or host or implement
178
// connection pool management
179
setConnectionClosed( closeConnection( UNTAGGED_BYE,
180                                                       " connection rejected.",
181                                                       "" ) );
182             }
183             else {
184                 if ( false ) { // connection is pre-authenticated
185
untaggedResponse( "PREAUTH" + SP + VERSION + SP
186                                       + "server" + SP + this.helloName + SP
187                                       + "logged in as" + SP + _session.getCurrentUser() );
188                     _session.setState( ImapSessionState.AUTHENTICATED );
189                     _session.setCurrentUser( "preauth user" );
190                     getSecurityLogger().info( "Pre-authenticated connection from "
191                                               + getRemoteHost() + "(" + getRemoteIP()
192                                               + ") received by SingleThreadedConnectionHandler" );
193                 }
194                 else {
195                     _session.getOut().println( UNTAGGED + SP + OK + SP + VERSION + SP
196                                                + "Server " + this.helloName + SP + "ready" );
197                     _session.setState( ImapSessionState.NON_AUTHENTICATED );
198                     _session.setCurrentUser( "unknown" );
199                     getSecurityLogger().info( "Non-authenticated connection from "
200                                               + getRemoteHost() + "(" + getRemoteIP()
201                                               + ") received by SingleThreadedConnectionHandler" );
202                 }
203                 
204                 while ( true ) {
205                     if (this.getCanParseCommand()) {
206                        if(! parseCommand( in.readLine())) break;
207                     }
208                     scheduler.resetTrigger( this.toString() );
209                 }
210             }
211
212             if ( !isConnectionClosed() ) {
213                 setConnectionClosed( closeConnection( UNTAGGED_BYE,
214                                                       "Server error, closing connection", "" ) );
215             }
216
217         }
218         catch ( Exception JavaDoc e ) {
219             // This should never happen once code is debugged
220
getLogger().error( "Exception during connection from " + getRemoteHost()
221                                + " (" + getRemoteIP() + ") : " + e.getMessage() );
222             e.printStackTrace();
223             setConnectionClosed( closeConnection( UNTAGGED_BYE,
224                                                   "Error processing command.", "" ) );
225         }
226
227         scheduler.removeTrigger( this.toString() );
228     }
229
230     public void targetTriggered( final String JavaDoc triggerName )
231     {
232         getLogger().info( "Connection timeout on socket" );
233         setConnectionClosed( closeConnection( UNTAGGED_BYE,
234                                               "Autologout. Idle too long.", "" ) );
235     }
236
237     public boolean closeConnection( int exitStatus,
238                                     String JavaDoc message1,
239                                     String JavaDoc message2 )
240     {
241         scheduler.removeTrigger( this.toString() );
242         if ( _session.getState() == ImapSessionState.SELECTED ) {
243             getCurrentMailbox().removeMailboxEventListener( this );
244             getImapHost().releaseMailbox( _session.getCurrentUser(), getCurrentMailbox() );
245         }
246
247         try {
248             switch ( exitStatus ) {
249                 case 0:
250                     untaggedResponse( "BYE" + SP + "Server logging out" );
251                     okResponse( "LOGOUT" );
252                     break;
253                 case 1:
254                     untaggedResponse( "BYE" + SP + message1 );
255                     okResponse( message2 );
256                     break;
257                 case 2:
258                     untaggedResponse( "BYE" + SP + message1 );
259                     break;
260                 case 3:
261                     noResponse( message1 );
262                     break;
263                 case 4:
264                     untaggedResponse( "BYE" + SP + message1 );
265                     noResponse( message2 );
266                     break;
267             }
268             _session.getOut().flush();
269             socket.close();
270             getLogger().info( "Connection closed" + SP + exitStatus + SP + message1
271                               + SP + message2 );
272         }
273         catch ( IOException ioe ) {
274             getLogger().error( "Exception while closing connection from " + getRemoteHost()
275                                + " (" + getRemoteIP() + ") : " + ioe.getMessage() );
276             try {
277                 socket.close();
278             }
279             catch ( IOException ioe2 ) {
280             }
281         }
282         return true;
283     }
284
285     private boolean parseCommand( String JavaDoc next )
286     {
287         commandRaw = next;
288         String JavaDoc folder = null;
289         String JavaDoc command = null;
290         boolean subscribeOnly = false;
291         System.out.println("PARSING COMMAND FROM CILENT: "+next);
292         if ( commandRaw == null ) return false;
293         StringTokenizer JavaDoc commandLine = new StringTokenizer JavaDoc( commandRaw.trim(), " " );
294         int arguments = commandLine.countTokens();
295         if ( arguments == 0 ) {
296             return true;
297         }else {
298             tag = commandLine.nextToken();
299             if ( tag.length() > 10 ) {
300                 // this stops overlong junk.
301
// Should do more validation
302
badResponse( "tag too long" );
303                 return true;
304             }
305         }
306         if ( arguments > 1 ) {
307             command = commandLine.nextToken();
308             if ( command.length() > 13 ) {// this stops overlong junk.
309
// we could validate the command contents,
310
// but may not be worth it
311
badResponse( "overlong command attempted" );
312                 return true;
313             }
314         } else {
315             badResponse( "no command sent" );
316             return true;
317         }
318         
319         // Create ImapRequestImpl object here - is this the right stage?
320
ImapRequestImpl request = new ImapRequestImpl( this, command );
321         request.setCommandLine( commandLine );
322         request.setUseUIDs( false );
323         request.setCurrentMailbox( getCurrentMailbox() );
324         request.setCommandRaw( commandRaw );
325         request.setTag( tag );
326         request.setCurrentFolder( getCurrentFolder() );
327
328         // At this stage we have a tag and a string which may be a command
329
// Start with commands that are valid in any state
330
// CAPABILITY, NOOP, LOGOUT
331

332         // Commands only valid in NON_AUTHENTICATED state
333
// AUTHENTICATE, LOGIN
334

335
336         // Commands valid in both Authenticated and Selected states
337
// NAMESPACE, GETACL, SETACL, DELETEACL, LISTRIGHTS, MYRIGHTS, SELECT
338

339         // Commands valid only in Authenticated State
340
// None
341

342         // Commands valid only in Selected state
343
// CHECK CLOSE COPY EXPUNGE FETCH STORE UID
344

345         ImapCommand cmd = getImapCommand( command );
346         
347         if ( ! cmd.validForState( state ) ) {
348             badResponse( command + " not valid in this state" );
349             return true;
350         }
351         return cmd.process( request, this );
352     }
353
354     public ImapCommand getImapCommand( String JavaDoc command )
355     {
356         return _imapCommands.getCommand( command );
357     }
358
359     private void invalidStateResponse( String JavaDoc command )
360     {
361         badResponse( command + " not valid in this state" );
362     }
363
364     public void okResponse( String JavaDoc command )
365     {
366         taggedResponse( OK + SP + command + " completed" );
367     }
368
369     public void noResponse( String JavaDoc command )
370     {
371         noResponse( command, "failed" );
372     }
373
374     public void noResponse( String JavaDoc command, String JavaDoc msg )
375     {
376         taggedResponse( NO + SP + command + SP + msg );
377     }
378
379     public void badResponse( String JavaDoc badMsg )
380     {
381         taggedResponse( BAD + SP + badMsg );
382     }
383
384     public void notImplementedResponse( String JavaDoc command )
385     {
386         badResponse( command + " not implemented." );
387     }
388
389     public void taggedResponse( String JavaDoc msg )
390     {
391         _session.getOut().println( tag + SP + msg );
392     }
393
394     public void untaggedResponse( String JavaDoc msg )
395     {
396         _session.getOut().println( UNTAGGED + SP + msg );
397     }
398
399     public void dispose()
400     {
401         // todo
402
getLogger().error( "Stop IMAPHandler" );
403     }
404
405     public void receiveEvent( MailboxEvent me )
406     {
407         if ( _session.getState() == ImapSessionState.SELECTED ) {
408             checkMailboxFlag = true;
409         }
410     }
411
412 // public ACLMailbox getBox( String user, String mailboxName ) throws MailboxException, AccessControlException
413
// {
414
// return
415
// ACLMailbox tempMailbox = null;
416
// try {
417
// tempMailbox = getImapHost().getMailbox( user, mailboxName );
418
// }
419
// catch ( MailboxException me ) {
420
// if ( me.isRemote() ) {
421
// _session.getOut().println( tag + SP + NO + SP + "[REFERRAL " + me.getRemoteServer() + "]" + SP + "Remote mailbox" );
422
// }
423
// else {
424
// _session.noResponse(
425
// _session.getOut().println( tag + SP + NO + SP + "Unknown mailbox" );
426
// getLogger().info( "MailboxException in method getBox for user: "
427
// + user + " mailboxName: " + mailboxName + " was "
428
// + me.getMessage() );
429
// }
430
//
431
// }
432
// catch ( AccessControlException e ) {
433
// _session.getOut().println( tag + SP + NO + SP + "Unknown mailbox" );
434
// }
435
// return tempMailbox;
436
// }
437

438     public void logACE( AccessControlException ace )
439     {
440         getSecurityLogger().error( "AccessControlException by user " + _session.getCurrentUser()
441                                    + " from " + getRemoteHost() + "(" + getRemoteIP()
442                                    + ") with " + commandRaw + " was "
443                                    + ace.getMessage() );
444     }
445
446     public void logAZE( AuthorizationException aze )
447     {
448         getSecurityLogger().error( "AuthorizationException by user " + _session.getCurrentUser()
449                                    + " from " + getRemoteHost() + "(" + getRemoteIP()
450                                    + ") with " + commandRaw + " was "
451                                    + aze.getMessage() );
452     }
453
454     public PrintWriter getPrintWriter()
455     {
456         return _session.getOut();
457     }
458
459     public OutputStream getOutputStream()
460     {
461         return outs;
462     }
463
464     public String JavaDoc getUser()
465     {
466         return _session.getCurrentUser();
467     }
468
469     public void checkSize()
470     {
471         int newExists = getCurrentMailbox().getExists();
472         if ( newExists != exists ) {
473             _session.getOut().println( UNTAGGED + SP + newExists + " EXISTS" );
474             exists = newExists;
475         }
476         int newRecent = getCurrentMailbox().getRecent();
477         if ( newRecent != recent ) {
478             _session.getOut().println( UNTAGGED + SP + newRecent + " RECENT" );
479             recent = newRecent;
480         }
481         return;
482     }
483
484     public void checkExpunge()
485     {
486         List JavaDoc newList = getCurrentMailbox().listUIDs( _session.getCurrentUser() );
487         for ( int k = 0; k < newList.size(); k++ ) {
488             getLogger().debug( "New List msn " + (k + 1) + " is uid " + newList.get( k ) );
489         }
490         for ( int i = sequence.size() - 1; i > -1; i-- ) {
491             Integer JavaDoc j = (Integer JavaDoc) sequence.get( i );
492             getLogger().debug( "Looking for old msn " + (i + 1) + " was uid " + j );
493             if ( !newList.contains( (Integer JavaDoc) sequence.get( i ) ) ) {
494                 _session.getOut().println( UNTAGGED + SP + (i + 1) + " EXPUNGE" );
495             }
496         }
497         sequence = newList;
498         //newList = null;
499
return;
500     }
501
502     public ImapSessionState getState()
503     {
504         return state;
505     }
506
507     public void setState( ImapSessionState state )
508     {
509         this.state = state;
510         exists = -1;
511         recent = -1;
512     }
513
514     public BufferedReader getIn()
515     {
516         return in;
517     }
518
519     public void setIn( BufferedReader in )
520     {
521         this.in = in;
522     }
523
524     public PrintWriter getOut()
525     {
526         return out;
527     }
528
529     public void setOut( PrintWriter out )
530     {
531         this.out = out;
532     }
533
534     public String JavaDoc getRemoteHost()
535     {
536         return remoteHost;
537     }
538
539     public String JavaDoc getRemoteIP()
540     {
541         return remoteIP;
542     }
543
544     public Logger getDebugLogger()
545     {
546         return getLogger();
547     }
548
549     public Logger getSecurityLogger()
550     {
551         return securityLogger;
552     }
553
554     public UsersRepository getUsers()
555     {
556         return users;
557     }
558
559     public IMAPSystem getImapSystem()
560     {
561         return imapSystem;
562     }
563
564     public Host getImapHost()
565     {
566         return imapHost;
567     }
568
569     public String JavaDoc getCurrentNamespace()
570     {
571         return currentNamespace;
572     }
573
574     public void setCurrentNamespace( String JavaDoc currentNamespace )
575     {
576         this.currentNamespace = currentNamespace;
577     }
578
579     public String JavaDoc getCurrentSeperator()
580     {
581         return currentSeperator;
582     }
583
584     public void setCurrentSeperator( String JavaDoc currentSeperator )
585     {
586         this.currentSeperator = currentSeperator;
587     }
588
589     public String JavaDoc getCurrentFolder()
590     {
591         return currentFolder;
592     }
593
594     public void setCurrentFolder( String JavaDoc currentFolder )
595     {
596         this.currentFolder = currentFolder;
597     }
598
599     public ACLMailbox getCurrentMailbox()
600     {
601         return currentMailbox;
602     }
603
604     public void setCurrentMailbox( ACLMailbox currentMailbox )
605     {
606         this.currentMailbox = currentMailbox;
607     }
608
609     public boolean isCurrentIsReadOnly()
610     {
611         return currentIsReadOnly;
612     }
613
614     public void setCurrentIsReadOnly( boolean currentIsReadOnly )
615     {
616         this.currentIsReadOnly = currentIsReadOnly;
617     }
618
619     public boolean isConnectionClosed()
620     {
621         return connectionClosed;
622     }
623
624     public void setConnectionClosed( boolean connectionClosed )
625     {
626         this.connectionClosed = connectionClosed;
627     }
628
629     public String JavaDoc getCurrentUser()
630     {
631         return user;
632     }
633
634     public void setCurrentUser( String JavaDoc user )
635     {
636         this.user = user;
637     }
638
639     public void setSequence( List JavaDoc sequence )
640     {
641         this.sequence = sequence;
642     }
643
644     public List JavaDoc decodeSet( String JavaDoc rawSet, int exists ) throws IllegalArgumentException JavaDoc
645     {
646         return super.decodeSet( rawSet, exists );
647     }
648     
649     public void setCanParseCommand(boolean canParseCommand) {
650         this.canParseCommand = canParseCommand;
651     }
652     public boolean getCanParseCommand() {
653         return this.canParseCommand;
654     }
655 }
656
Popular Tags