/projects/james-2.2.0/proposals/imap/java/org/apache/james/imapserver/SingleThreadedConnectionHandler.java
Java | 655 lines | 483 code | 87 blank | 85 comment | 29 complexity | 36abd32fdaf346d0279d5c327534323d MD5 | raw file
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
18package org.apache.james.imapserver;
19
20import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
21import org.apache.avalon.cornerstone.services.scheduler.PeriodicTimeTrigger;
22import org.apache.avalon.cornerstone.services.scheduler.Target;
23import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
24import org.apache.avalon.framework.activity.Disposable;
25import org.apache.avalon.framework.activity.Initializable;
26import org.apache.avalon.framework.component.ComponentException;
27import org.apache.avalon.framework.component.ComponentManager;
28import org.apache.avalon.framework.component.Composable;
29import org.apache.avalon.framework.configuration.Configurable;
30import org.apache.avalon.framework.logger.Logger;
31import org.apache.james.imapserver.AccessControlException;
32import org.apache.james.imapserver.AuthorizationException;
33import org.apache.james.Constants;
34import org.apache.james.imapserver.commands.ImapCommand;
35import org.apache.james.imapserver.commands.ImapCommandFactory;
36import org.apache.james.services.MailServer;
37import org.apache.james.services.UsersRepository;
38import org.apache.james.services.UsersStore;
39import org.apache.james.util.InternetPrintWriter;
40
41import java.io.*;
42import java.net.Socket;
43import java.util.List;
44import java.util.StringTokenizer;
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 */
54public 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 socket;
72 private BufferedReader in;
73 private PrintWriter out;
74 private OutputStream outs;
75 private String remoteHost;
76 private String remoteIP;
77 private String softwaretype = "JAMES IMAP4rev1 Server " + Constants.SOFTWARE_VERSION;
78 private ImapSessionState state;
79 private String user;
80
81 private IMAPSystem imapSystem;
82 private Host imapHost;
83 private String namespaceToken;
84 private String currentNamespace = null;
85 private String currentSeperator = null;
86 private String 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 currentFolder = null;
91 private ACLMailbox currentMailbox = null;
92 private boolean currentIsReadOnly = false;
93 private boolean connectionClosed = false;
94 private String tag;
95 private boolean checkMailboxFlag = false;
96 private int exists;
97 private int recent;
98 private List 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
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 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 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 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 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 message1,
239 String 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 next )
286 {
287 commandRaw = next;
288 String folder = null;
289 String command = null;
290 boolean subscribeOnly = false;
291 System.out.println("PARSING COMMAND FROM CILENT: "+next);
292 if ( commandRaw == null ) return false;
293 StringTokenizer commandLine = new StringTokenizer( 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 command )
355 {
356 return _imapCommands.getCommand( command );
357 }
358
359 private void invalidStateResponse( String command )
360 {
361 badResponse( command + " not valid in this state" );
362 }
363
364 public void okResponse( String command )
365 {
366 taggedResponse( OK + SP + command + " completed" );
367 }
368
369 public void noResponse( String command )
370 {
371 noResponse( command, "failed" );
372 }
373
374 public void noResponse( String command, String msg )
375 {
376 taggedResponse( NO + SP + command + SP + msg );
377 }
378
379 public void badResponse( String badMsg )
380 {
381 taggedResponse( BAD + SP + badMsg );
382 }
383
384 public void notImplementedResponse( String command )
385 {
386 badResponse( command + " not implemented." );
387 }
388
389 public void taggedResponse( String msg )
390 {
391 _session.getOut().println( tag + SP + msg );
392 }
393
394 public void untaggedResponse( String 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 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 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 j = (Integer) sequence.get( i );
492 getLogger().debug( "Looking for old msn " + (i + 1) + " was uid " + j );
493 if ( !newList.contains( (Integer) 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 getRemoteHost()
535 {
536 return remoteHost;
537 }
538
539 public String 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 getCurrentNamespace()
570 {
571 return currentNamespace;
572 }
573
574 public void setCurrentNamespace( String currentNamespace )
575 {
576 this.currentNamespace = currentNamespace;
577 }
578
579 public String getCurrentSeperator()
580 {
581 return currentSeperator;
582 }
583
584 public void setCurrentSeperator( String currentSeperator )
585 {
586 this.currentSeperator = currentSeperator;
587 }
588
589 public String getCurrentFolder()
590 {
591 return currentFolder;
592 }
593
594 public void setCurrentFolder( String 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 getCurrentUser()
630 {
631 return user;
632 }
633
634 public void setCurrentUser( String user )
635 {
636 this.user = user;
637 }
638
639 public void setSequence( List sequence )
640 {
641 this.sequence = sequence;
642 }
643
644 public List decodeSet( String rawSet, int exists ) throws IllegalArgumentException
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}