Source Code Cross Referenced for NNTPHandler.java in  » Net » james-2.3.1 » org » apache » james » nntpserver » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Net » james 2.3.1 » org.apache.james.nntpserver 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /****************************************************************
0002:         * Licensed to the Apache Software Foundation (ASF) under one   *
0003:         * or more contributor license agreements.  See the NOTICE file *
0004:         * distributed with this work for additional information        *
0005:         * regarding copyright ownership.  The ASF licenses this file   *
0006:         * to you under the Apache License, Version 2.0 (the            *
0007:         * "License"); you may not use this file except in compliance   *
0008:         * with the License.  You may obtain a copy of the License at   *
0009:         *                                                              *
0010:         *   http://www.apache.org/licenses/LICENSE-2.0                 *
0011:         *                                                              *
0012:         * Unless required by applicable law or agreed to in writing,   *
0013:         * software distributed under the License is distributed on an  *
0014:         * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
0015:         * KIND, either express or implied.  See the License for the    *
0016:         * specific language governing permissions and limitations      *
0017:         * under the License.                                           *
0018:         ****************************************************************/package org.apache.james.nntpserver;
0019:
0020:        import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
0021:        import org.apache.avalon.excalibur.pool.Poolable;
0022:        import org.apache.avalon.framework.container.ContainerUtil;
0023:        import org.apache.avalon.framework.logger.AbstractLogEnabled;
0024:        import org.apache.james.core.MailHeaders;
0025:        import org.apache.james.nntpserver.repository.NNTPArticle;
0026:        import org.apache.james.nntpserver.repository.NNTPGroup;
0027:        import org.apache.james.util.CharTerminatedInputStream;
0028:        import org.apache.james.util.DotStuffingInputStream;
0029:        import org.apache.james.util.ExtraDotOutputStream;
0030:        import org.apache.james.util.InternetPrintWriter;
0031:        import org.apache.mailet.dates.RFC977DateFormat;
0032:        import org.apache.mailet.dates.RFC2980DateFormat;
0033:        import org.apache.mailet.dates.SimplifiedDateFormat;
0034:        import org.apache.james.util.watchdog.Watchdog;
0035:        import org.apache.james.util.watchdog.WatchdogTarget;
0036:
0037:        import java.io.BufferedInputStream;
0038:        import java.io.BufferedOutputStream;
0039:        import java.io.BufferedReader;
0040:        import java.io.ByteArrayInputStream;
0041:        import java.io.IOException;
0042:        import java.io.InputStream;
0043:        import java.io.InputStreamReader;
0044:        import java.io.OutputStream;
0045:        import java.io.PrintWriter;
0046:        import java.io.SequenceInputStream;
0047:        import java.net.Socket;
0048:        import java.text.ParseException;
0049:        import java.util.ArrayList;
0050:        import java.util.Calendar;
0051:        import java.util.Date;
0052:        import java.util.Iterator;
0053:        import java.util.List;
0054:        import java.util.Locale;
0055:        import java.util.StringTokenizer;
0056:        import javax.mail.MessagingException;
0057:
0058:        /**
0059:         * The NNTP protocol is defined by RFC 977.
0060:         * This implementation is based on IETF draft 15, posted on 15th July '2002.
0061:         * URL: http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-15.txt
0062:         *
0063:         * Common NNTP extensions are in RFC 2980.
0064:         */
0065:        public class NNTPHandler extends AbstractLogEnabled implements 
0066:                ConnectionHandler, Poolable {
0067:
0068:            /**
0069:             * used to calculate DATE from - see 11.3
0070:             */
0071:            private static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
0072:
0073:            /**
0074:             * Date format for the DATE keyword - see 11.1.1
0075:             */
0076:            private static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
0077:
0078:            /**
0079:             * The UTC offset for this time zone.
0080:             */
0081:            public static final long UTC_OFFSET = Calendar.getInstance().get(
0082:                    Calendar.ZONE_OFFSET);
0083:
0084:            /**
0085:             * The text string for the NNTP MODE command.
0086:             */
0087:            private final static String COMMAND_MODE = "MODE";
0088:
0089:            /**
0090:             * The text string for the NNTP LIST command.
0091:             */
0092:            private final static String COMMAND_LIST = "LIST";
0093:
0094:            /**
0095:             * The text string for the NNTP GROUP command.
0096:             */
0097:            private final static String COMMAND_GROUP = "GROUP";
0098:
0099:            /**
0100:             * The text string for the NNTP NEXT command.
0101:             */
0102:            private final static String COMMAND_NEXT = "NEXT";
0103:
0104:            /**
0105:             * The text string for the NNTP LAST command.
0106:             */
0107:            private final static String COMMAND_LAST = "LAST";
0108:
0109:            /**
0110:             * The text string for the NNTP ARTICLE command.
0111:             */
0112:            private final static String COMMAND_ARTICLE = "ARTICLE";
0113:
0114:            /**
0115:             * The text string for the NNTP HEAD command.
0116:             */
0117:            private final static String COMMAND_HEAD = "HEAD";
0118:
0119:            /**
0120:             * The text string for the NNTP BODY command.
0121:             */
0122:            private final static String COMMAND_BODY = "BODY";
0123:
0124:            /**
0125:             * The text string for the NNTP STAT command.
0126:             */
0127:            private final static String COMMAND_STAT = "STAT";
0128:
0129:            /**
0130:             * The text string for the NNTP POST command.
0131:             */
0132:            private final static String COMMAND_POST = "POST";
0133:
0134:            /**
0135:             * The text string for the NNTP IHAVE command.
0136:             */
0137:            private final static String COMMAND_IHAVE = "IHAVE";
0138:
0139:            /**
0140:             * The text string for the NNTP QUIT command.
0141:             */
0142:            private final static String COMMAND_QUIT = "QUIT";
0143:
0144:            /**
0145:             * The text string for the NNTP SLAVE command.
0146:             */
0147:            private final static String COMMAND_SLAVE = "SLAVE";
0148:
0149:            /**
0150:             * The text string for the NNTP DATE command.
0151:             */
0152:            private final static String COMMAND_DATE = "DATE";
0153:
0154:            /**
0155:             * The text string for the NNTP HELP command.
0156:             */
0157:            private final static String COMMAND_HELP = "HELP";
0158:
0159:            /**
0160:             * The text string for the NNTP NEWGROUPS command.
0161:             */
0162:            private final static String COMMAND_NEWGROUPS = "NEWGROUPS";
0163:
0164:            /**
0165:             * The text string for the NNTP NEWNEWS command.
0166:             */
0167:            private final static String COMMAND_NEWNEWS = "NEWNEWS";
0168:
0169:            /**
0170:             * The text string for the NNTP LISTGROUP command.
0171:             */
0172:            private final static String COMMAND_LISTGROUP = "LISTGROUP";
0173:
0174:            /**
0175:             * The text string for the NNTP OVER command.
0176:             */
0177:            private final static String COMMAND_OVER = "OVER";
0178:
0179:            /**
0180:             * The text string for the NNTP XOVER command.
0181:             */
0182:            private final static String COMMAND_XOVER = "XOVER";
0183:
0184:            /**
0185:             * The text string for the NNTP HDR command.
0186:             */
0187:            private final static String COMMAND_HDR = "HDR";
0188:
0189:            /**
0190:             * The text string for the NNTP XHDR command.
0191:             */
0192:            private final static String COMMAND_XHDR = "XHDR";
0193:
0194:            /**
0195:             * The text string for the NNTP AUTHINFO command.
0196:             */
0197:            private final static String COMMAND_AUTHINFO = "AUTHINFO";
0198:
0199:            /**
0200:             * The text string for the NNTP PAT command.
0201:             */
0202:            private final static String COMMAND_PAT = "PAT";
0203:
0204:            /**
0205:             * The text string for the NNTP MODE READER parameter.
0206:             */
0207:            private final static String MODE_TYPE_READER = "READER";
0208:
0209:            /**
0210:             * The text string for the NNTP MODE STREAM parameter.
0211:             */
0212:            private final static String MODE_TYPE_STREAM = "STREAM";
0213:
0214:            /**
0215:             * The text string for the NNTP AUTHINFO USER parameter.
0216:             */
0217:            private final static String AUTHINFO_PARAM_USER = "USER";
0218:
0219:            /**
0220:             * The text string for the NNTP AUTHINFO PASS parameter.
0221:             */
0222:            private final static String AUTHINFO_PARAM_PASS = "PASS";
0223:
0224:            /**
0225:             * The character array that indicates termination of an NNTP message
0226:             */
0227:            private final static char[] NNTPTerminator = { '\r', '\n', '.',
0228:                    '\r', '\n' };
0229:
0230:            /**
0231:             * The thread executing this handler 
0232:             */
0233:            private Thread handlerThread;
0234:
0235:            /**
0236:             * The remote host name obtained by lookup on the socket.
0237:             */
0238:            private String remoteHost;
0239:
0240:            /**
0241:             * The remote IP address of the socket.
0242:             */
0243:            private String remoteIP;
0244:
0245:            /**
0246:             * The TCP/IP socket over which the POP3 interaction
0247:             * is occurring
0248:             */
0249:            private Socket socket;
0250:
0251:            /**
0252:             * The incoming stream of bytes coming from the socket.
0253:             */
0254:            private InputStream in;
0255:
0256:            /**
0257:             * The reader associated with incoming characters.
0258:             */
0259:            private BufferedReader reader;
0260:
0261:            /**
0262:             * The socket's output stream
0263:             */
0264:            private OutputStream outs;
0265:
0266:            /**
0267:             * The writer to which outgoing messages are written.
0268:             */
0269:            private PrintWriter writer;
0270:
0271:            /**
0272:             * The current newsgroup.
0273:             */
0274:            private NNTPGroup group;
0275:
0276:            /**
0277:             * The current newsgroup.
0278:             */
0279:            private int currentArticleNumber = -1;
0280:
0281:            /**
0282:             * Per-service configuration data that applies to all handlers
0283:             * associated with the service.
0284:             */
0285:            private NNTPHandlerConfigurationData theConfigData;
0286:
0287:            /**
0288:             * The user id associated with the NNTP dialogue
0289:             */
0290:            private String user = null;
0291:
0292:            /**
0293:             * The password associated with the NNTP dialogue
0294:             */
0295:            private String password = null;
0296:
0297:            /**
0298:             * Whether the user for this session has already authenticated.
0299:             * Used to optimize authentication checks
0300:             */
0301:            boolean isAlreadyAuthenticated = false;
0302:
0303:            /**
0304:             * The watchdog being used by this handler to deal with idle timeouts.
0305:             */
0306:            private Watchdog theWatchdog;
0307:
0308:            /**
0309:             * The watchdog target that idles out this handler.
0310:             */
0311:            private WatchdogTarget theWatchdogTarget = new NNTPWatchdogTarget();
0312:
0313:            /**
0314:             * Set the configuration data for the handler
0315:             *
0316:             * @param theData configuration data for the handler
0317:             */
0318:            void setConfigurationData(NNTPHandlerConfigurationData theData) {
0319:                theConfigData = theData;
0320:            }
0321:
0322:            /**
0323:             * Set the Watchdog for use by this handler.
0324:             *
0325:             * @param theWatchdog the watchdog
0326:             */
0327:            void setWatchdog(Watchdog theWatchdog) {
0328:                this .theWatchdog = theWatchdog;
0329:            }
0330:
0331:            /**
0332:             * Gets the Watchdog Target that should be used by Watchdogs managing
0333:             * this connection.
0334:             *
0335:             * @return the WatchdogTarget
0336:             */
0337:            WatchdogTarget getWatchdogTarget() {
0338:                return theWatchdogTarget;
0339:            }
0340:
0341:            /**
0342:             * Idle out this connection
0343:             */
0344:            void idleClose() {
0345:                if (getLogger() != null) {
0346:                    getLogger().error("NNTP Connection has idled out.");
0347:                }
0348:                try {
0349:                    if (socket != null) {
0350:                        socket.close();
0351:                    }
0352:                } catch (Exception e) {
0353:                    // ignored
0354:                } finally {
0355:                    socket = null;
0356:                }
0357:
0358:                synchronized (this ) {
0359:                    // Interrupt the thread to recover from internal hangs
0360:                    if (handlerThread != null) {
0361:                        handlerThread.interrupt();
0362:                        handlerThread = null;
0363:                    }
0364:                }
0365:            }
0366:
0367:            /**
0368:             * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
0369:             */
0370:            public void handleConnection(Socket connection) throws IOException {
0371:                try {
0372:                    this .socket = connection;
0373:                    synchronized (this ) {
0374:                        handlerThread = Thread.currentThread();
0375:                    }
0376:                    remoteIP = socket.getInetAddress().getHostAddress();
0377:                    remoteHost = socket.getInetAddress().getHostName();
0378:                    in = new BufferedInputStream(socket.getInputStream(), 1024);
0379:                    // An ASCII encoding can be used because all transmissions other
0380:                    // that those in the message body command are guaranteed
0381:                    // to be ASCII
0382:                    reader = new BufferedReader(new InputStreamReader(in,
0383:                            "ASCII"), 512);
0384:                    outs = new BufferedOutputStream(socket.getOutputStream(),
0385:                            1024);
0386:                    writer = new InternetPrintWriter(outs, true);
0387:                } catch (Exception e) {
0388:                    StringBuffer exceptionBuffer = new StringBuffer(256)
0389:                            .append("Cannot open connection from ").append(
0390:                                    remoteHost).append(" (").append(remoteIP)
0391:                            .append("): ").append(e.getMessage());
0392:                    String exceptionString = exceptionBuffer.toString();
0393:                    getLogger().error(exceptionString, e);
0394:                }
0395:
0396:                try {
0397:                    // section 7.1
0398:                    if (theConfigData.getNNTPRepository().isReadOnly()) {
0399:                        StringBuffer respBuffer = new StringBuffer(128)
0400:                                .append("201 ")
0401:                                .append(theConfigData.getHelloName())
0402:                                .append(
0403:                                        " NNTP Service Ready, posting prohibited");
0404:                        writeLoggedFlushedResponse(respBuffer.toString());
0405:                    } else {
0406:                        StringBuffer respBuffer = new StringBuffer(128)
0407:                                .append("200 ")
0408:                                .append(theConfigData.getHelloName())
0409:                                .append(
0410:                                        " NNTP Service Ready, posting permitted");
0411:                        writeLoggedFlushedResponse(respBuffer.toString());
0412:                    }
0413:
0414:                    theWatchdog.start();
0415:                    while (parseCommand(reader.readLine())) {
0416:                        theWatchdog.reset();
0417:                    }
0418:                    theWatchdog.stop();
0419:
0420:                    getLogger().info("Connection closed");
0421:                } catch (Exception e) {
0422:                    // If the connection has been idled out, the
0423:                    // socket will be closed and null.  Do NOT
0424:                    // log the exception or attempt to send the
0425:                    // closing connection message
0426:                    if (socket != null) {
0427:                        try {
0428:                            doQUIT(null);
0429:                        } catch (Throwable t) {
0430:                        }
0431:                        getLogger()
0432:                                .error(
0433:                                        "Exception during connection:"
0434:                                                + e.getMessage(), e);
0435:                    }
0436:                } finally {
0437:                    resetHandler();
0438:                }
0439:            }
0440:
0441:            /**
0442:             * Resets the handler data to a basic state.
0443:             */
0444:            private void resetHandler() {
0445:
0446:                // Clear the Watchdog
0447:                if (theWatchdog != null) {
0448:                    ContainerUtil.dispose(theWatchdog);
0449:                    theWatchdog = null;
0450:                }
0451:
0452:                // Clear the streams
0453:                try {
0454:                    if (reader != null) {
0455:                        reader.close();
0456:                    }
0457:                } catch (IOException ioe) {
0458:                    getLogger().warn(
0459:                            "NNTPHandler: Unexpected exception occurred while closing reader: "
0460:                                    + ioe);
0461:                } finally {
0462:                    reader = null;
0463:                }
0464:
0465:                in = null;
0466:
0467:                if (writer != null) {
0468:                    writer.close();
0469:                    writer = null;
0470:                }
0471:                outs = null;
0472:
0473:                remoteHost = null;
0474:                remoteIP = null;
0475:                try {
0476:                    if (socket != null) {
0477:                        socket.close();
0478:                    }
0479:                } catch (IOException ioe) {
0480:                    getLogger().warn(
0481:                            "NNTPHandler: Unexpected exception occurred while closing socket: "
0482:                                    + ioe);
0483:                } finally {
0484:                    socket = null;
0485:                }
0486:
0487:                synchronized (this ) {
0488:                    handlerThread = null;
0489:                }
0490:
0491:                // Clear the selected group, article info
0492:                group = null;
0493:                currentArticleNumber = -1;
0494:
0495:                // Clear the authentication info
0496:                user = null;
0497:                password = null;
0498:                isAlreadyAuthenticated = false;
0499:
0500:                // Clear the config data
0501:                theConfigData = null;
0502:            }
0503:
0504:            /**
0505:             * This method parses NNTP commands read off the wire in handleConnection.
0506:             * Actual processing of the command (possibly including additional back and
0507:             * forth communication with the client) is delegated to one of a number of
0508:             * command specific handler methods.  The primary purpose of this method is
0509:             * to parse the raw command string to determine exactly which handler should
0510:             * be called.  It returns true if expecting additional commands, false otherwise.
0511:             *
0512:             * @param commandRaw the raw command string passed in over the socket
0513:             *
0514:             * @return whether additional commands are expected.
0515:             */
0516:            private boolean parseCommand(String commandRaw) {
0517:                if (commandRaw == null) {
0518:                    return false;
0519:                }
0520:                if (getLogger().isDebugEnabled()) {
0521:                    getLogger().debug("Command received: " + commandRaw);
0522:                }
0523:
0524:                String command = commandRaw.trim();
0525:                String argument = null;
0526:                int spaceIndex = command.indexOf(" ");
0527:                if (spaceIndex >= 0) {
0528:                    argument = command.substring(spaceIndex + 1);
0529:                    command = command.substring(0, spaceIndex);
0530:                }
0531:                command = command.toUpperCase(Locale.US);
0532:
0533:                boolean returnValue = true;
0534:                if (!isAuthorized(command)) {
0535:                    writeLoggedFlushedResponse("480 User is not authenticated");
0536:                    getLogger().debug("Command not allowed.");
0537:                    return returnValue;
0538:                }
0539:                if ((command.equals(COMMAND_MODE)) && (argument != null)) {
0540:                    if (argument.toUpperCase(Locale.US)
0541:                            .equals(MODE_TYPE_READER)) {
0542:                        doMODEREADER(argument);
0543:                    } else if (argument.toUpperCase(Locale.US).equals(
0544:                            MODE_TYPE_STREAM)) {
0545:                        doMODESTREAM(argument);
0546:                    } else {
0547:                        writeLoggedFlushedResponse("500 Command not understood");
0548:                    }
0549:                } else if (command.equals(COMMAND_LIST)) {
0550:                    doLIST(argument);
0551:                } else if (command.equals(COMMAND_GROUP)) {
0552:                    doGROUP(argument);
0553:                } else if (command.equals(COMMAND_NEXT)) {
0554:                    doNEXT(argument);
0555:                } else if (command.equals(COMMAND_LAST)) {
0556:                    doLAST(argument);
0557:                } else if (command.equals(COMMAND_ARTICLE)) {
0558:                    doARTICLE(argument);
0559:                } else if (command.equals(COMMAND_HEAD)) {
0560:                    doHEAD(argument);
0561:                } else if (command.equals(COMMAND_BODY)) {
0562:                    doBODY(argument);
0563:                } else if (command.equals(COMMAND_STAT)) {
0564:                    doSTAT(argument);
0565:                } else if (command.equals(COMMAND_POST)) {
0566:                    doPOST(argument);
0567:                } else if (command.equals(COMMAND_IHAVE)) {
0568:                    doIHAVE(argument);
0569:                } else if (command.equals(COMMAND_QUIT)) {
0570:                    doQUIT(argument);
0571:                    returnValue = false;
0572:                } else if (command.equals(COMMAND_DATE)) {
0573:                    doDATE(argument);
0574:                } else if (command.equals(COMMAND_HELP)) {
0575:                    doHELP(argument);
0576:                } else if (command.equals(COMMAND_NEWGROUPS)) {
0577:                    doNEWGROUPS(argument);
0578:                } else if (command.equals(COMMAND_NEWNEWS)) {
0579:                    doNEWNEWS(argument);
0580:                } else if (command.equals(COMMAND_LISTGROUP)) {
0581:                    doLISTGROUP(argument);
0582:                } else if (command.equals(COMMAND_OVER)) {
0583:                    doOVER(argument);
0584:                } else if (command.equals(COMMAND_XOVER)) {
0585:                    doXOVER(argument);
0586:                } else if (command.equals(COMMAND_HDR)) {
0587:                    doHDR(argument);
0588:                } else if (command.equals(COMMAND_XHDR)) {
0589:                    doXHDR(argument);
0590:                } else if (command.equals(COMMAND_AUTHINFO)) {
0591:                    doAUTHINFO(argument);
0592:                } else if (command.equals(COMMAND_SLAVE)) {
0593:                    doSLAVE(argument);
0594:                } else if (command.equals(COMMAND_PAT)) {
0595:                    doPAT(argument);
0596:                } else {
0597:                    doUnknownCommand(command, argument);
0598:                }
0599:                return returnValue;
0600:            }
0601:
0602:            /**
0603:             * Handles an unrecognized command, logging that.
0604:             *
0605:             * @param command the command received from the client
0606:             * @param argument the argument passed in with the command
0607:             */
0608:            private void doUnknownCommand(String command, String argument) {
0609:                if (getLogger().isDebugEnabled()) {
0610:                    StringBuffer logBuffer = new StringBuffer(128).append(
0611:                            "Received unknown command ").append(command)
0612:                            .append(" with argument ").append(argument);
0613:                    getLogger().debug(logBuffer.toString());
0614:                }
0615:                writeLoggedFlushedResponse("500 Unknown command");
0616:            }
0617:
0618:            /**
0619:             * Implements only the originnal AUTHINFO.
0620:             * for simple and generic AUTHINFO, 501 is sent back. This is as
0621:             * per article 3.1.3 of RFC 2980
0622:             *
0623:             * @param argument the argument passed in with the AUTHINFO command
0624:             */
0625:            private void doAUTHINFO(String argument) {
0626:                String command = null;
0627:                String value = null;
0628:                if (argument != null) {
0629:                    int spaceIndex = argument.indexOf(" ");
0630:                    if (spaceIndex >= 0) {
0631:                        command = argument.substring(0, spaceIndex);
0632:                        value = argument.substring(spaceIndex + 1);
0633:                    }
0634:                }
0635:                if (command == null) {
0636:                    writeLoggedFlushedResponse("501 Syntax error");
0637:                    return;
0638:                }
0639:                command = command.toUpperCase(Locale.US);
0640:                if (command.equals(AUTHINFO_PARAM_USER)) {
0641:                    // Reject re-authentication
0642:                    if (isAlreadyAuthenticated) {
0643:                        writeLoggedFlushedResponse("482 Already authenticated - rejecting new credentials");
0644:                    }
0645:                    // Reject doubly sent user
0646:                    if (user != null) {
0647:                        user = null;
0648:                        password = null;
0649:                        isAlreadyAuthenticated = false;
0650:                        writeLoggedFlushedResponse("482 User already specified - rejecting new user");
0651:                        return;
0652:                    }
0653:                    user = value;
0654:                    writeLoggedFlushedResponse("381 More authentication information required");
0655:                } else if (command.equals(AUTHINFO_PARAM_PASS)) {
0656:                    // Reject password sent before user
0657:                    if (user == null) {
0658:                        writeLoggedFlushedResponse("482 User not yet specified.  Rejecting user.");
0659:                        return;
0660:                    }
0661:                    // Reject doubly sent password
0662:                    if (password != null) {
0663:                        user = null;
0664:                        password = null;
0665:                        isAlreadyAuthenticated = false;
0666:                        writeLoggedFlushedResponse("482 Password already specified - rejecting new password");
0667:                        return;
0668:                    }
0669:                    password = value;
0670:                    isAlreadyAuthenticated = isAuthenticated();
0671:                    if (isAlreadyAuthenticated) {
0672:                        writeLoggedFlushedResponse("281 Authentication accepted");
0673:                    } else {
0674:                        writeLoggedFlushedResponse("482 Authentication rejected");
0675:                        // Clear bad authentication
0676:                        user = null;
0677:                        password = null;
0678:                    }
0679:                } else {
0680:                    writeLoggedFlushedResponse("501 Syntax error");
0681:                    return;
0682:                }
0683:            }
0684:
0685:            /**
0686:             * Lists the articles posted since the date passed in as
0687:             * an argument.
0688:             *
0689:             * @param argument the argument passed in with the NEWNEWS command.
0690:             *                 Should be a wildmat followed by a date.
0691:             */
0692:            private void doNEWNEWS(String argument) {
0693:                // see section 11.4
0694:
0695:                String wildmat = "*";
0696:
0697:                if (argument != null) {
0698:                    int spaceIndex = argument.indexOf(" ");
0699:                    if (spaceIndex >= 0) {
0700:                        wildmat = argument.substring(0, spaceIndex);
0701:                        argument = argument.substring(spaceIndex + 1);
0702:                    } else {
0703:                        getLogger().error("NEWNEWS had an invalid argument");
0704:                        writeLoggedFlushedResponse("501 Syntax error");
0705:                        return;
0706:                    }
0707:                } else {
0708:                    getLogger().error("NEWNEWS had a null argument");
0709:                    writeLoggedFlushedResponse("501 Syntax error");
0710:                    return;
0711:                }
0712:
0713:                Date theDate = null;
0714:                try {
0715:                    theDate = getDateFrom(argument);
0716:                } catch (NNTPException nntpe) {
0717:                    getLogger().error("NEWNEWS had an invalid argument", nntpe);
0718:                    writeLoggedFlushedResponse("501 Syntax error");
0719:                    return;
0720:                }
0721:
0722:                writeLoggedFlushedResponse("230 list of new articles by message-id follows");
0723:                Iterator groupIter = theConfigData.getNNTPRepository()
0724:                        .getMatchedGroups(wildmat);
0725:                while (groupIter.hasNext()) {
0726:                    Iterator articleIter = ((NNTPGroup) (groupIter.next()))
0727:                            .getArticlesSince(theDate);
0728:                    while (articleIter.hasNext()) {
0729:                        StringBuffer iterBuffer = new StringBuffer(64)
0730:                                .append(((NNTPArticle) articleIter.next())
0731:                                        .getUniqueID());
0732:                        writeLoggedResponse(iterBuffer.toString());
0733:                    }
0734:                }
0735:                writeLoggedFlushedResponse(".");
0736:            }
0737:
0738:            /**
0739:             * Lists the groups added since the date passed in as
0740:             * an argument.
0741:             *
0742:             * @param argument the argument passed in with the NEWGROUPS command.
0743:             *                 Should be a date.
0744:             */
0745:            private void doNEWGROUPS(String argument) {
0746:                // see section 11.3
0747:                // both draft-ietf-nntpext-base-15.txt and rfc977 have only group names 
0748:                // in response lines, but INN sends 
0749:                // '<group name> <last article> <first article> <posting allowed>'
0750:                // NOTE: following INN over either document.
0751:                //
0752:                // TODO: Check this.  Audit at http://www.academ.com/pipermail/ietf-nntp/2001-July/002185.html
0753:                // doesn't mention the supposed discrepancy.  Consider changing code to 
0754:                // be in line with spec.
0755:                Date theDate = null;
0756:                try {
0757:                    theDate = getDateFrom(argument);
0758:                } catch (NNTPException nntpe) {
0759:                    getLogger().error("NEWGROUPS had an invalid argument",
0760:                            nntpe);
0761:                    writeLoggedFlushedResponse("501 Syntax error");
0762:                    return;
0763:                }
0764:                Iterator iter = theConfigData.getNNTPRepository()
0765:                        .getGroupsSince(theDate);
0766:                writeLoggedFlushedResponse("231 list of new newsgroups follows");
0767:                while (iter.hasNext()) {
0768:                    NNTPGroup currentGroup = (NNTPGroup) iter.next();
0769:                    StringBuffer iterBuffer = new StringBuffer(128).append(
0770:                            currentGroup.getName()).append(" ").append(
0771:                            currentGroup.getLastArticleNumber()).append(" ")
0772:                            .append(currentGroup.getFirstArticleNumber())
0773:                            .append(" ").append(
0774:                                    (currentGroup.isPostAllowed() ? "y" : "n"));
0775:                    writeLoggedResponse(iterBuffer.toString());
0776:                }
0777:                writeLoggedFlushedResponse(".");
0778:            }
0779:
0780:            /**
0781:             * Lists the help text for the service.
0782:             *
0783:             * @param argument the argument passed in with the HELP command.
0784:             */
0785:            private void doHELP(String argument) {
0786:                writeLoggedResponse("100 Help text follows");
0787:                writeLoggedFlushedResponse(".");
0788:            }
0789:
0790:            /**
0791:             * Acknowledges a SLAVE command.  No special preference is given
0792:             * to slave connections.
0793:             *
0794:             * @param argument the argument passed in with the SLAVE command.
0795:             */
0796:            private void doSLAVE(String argument) {
0797:                writeLoggedFlushedResponse("202 slave status noted");
0798:            }
0799:
0800:            /**
0801:             * Returns the current date according to the news server.
0802:             *
0803:             * @param argument the argument passed in with the DATE command
0804:             */
0805:            private void doDATE(String argument) {
0806:                Date dt = new Date(System.currentTimeMillis() - UTC_OFFSET);
0807:                String dtStr = DF_RFC2980.format(new Date(dt.getTime()
0808:                        - UTC_OFFSET));
0809:                writeLoggedFlushedResponse("111 " + dtStr);
0810:            }
0811:
0812:            /**
0813:             * Quits the transaction.
0814:             *
0815:             * @param argument the argument passed in with the QUIT command
0816:             */
0817:            private void doQUIT(String argument) {
0818:                writeLoggedFlushedResponse("205 closing connection");
0819:            }
0820:
0821:            /**
0822:             * Handles the LIST command and its assorted extensions.
0823:             *
0824:             * @param argument the argument passed in with the LIST command.
0825:             */
0826:            private void doLIST(String argument) {
0827:                // see section 9.4.1
0828:                String wildmat = "*";
0829:                boolean isListNewsgroups = false;
0830:
0831:                String extension = argument;
0832:                if (argument != null) {
0833:                    int spaceIndex = argument.indexOf(" ");
0834:                    if (spaceIndex >= 0) {
0835:                        wildmat = argument.substring(spaceIndex + 1);
0836:                        extension = argument.substring(0, spaceIndex);
0837:                    }
0838:                    extension = extension.toUpperCase(Locale.US);
0839:                }
0840:
0841:                if (extension != null) {
0842:                    if (extension.equals("ACTIVE")) {
0843:                        isListNewsgroups = false;
0844:                    } else if (extension.equals("NEWSGROUPS")) {
0845:                        isListNewsgroups = true;
0846:                    } else if (extension.equals("EXTENSIONS")) {
0847:                        doLISTEXTENSIONS();
0848:                        return;
0849:                    } else if (extension.equals("OVERVIEW.FMT")) {
0850:                        doLISTOVERVIEWFMT();
0851:                        return;
0852:                    } else if (extension.equals("ACTIVE.TIMES")) {
0853:                        // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
0854:                        writeLoggedFlushedResponse("503 program error, function not performed");
0855:                        return;
0856:                    } else if (extension.equals("DISTRIBUTIONS")) {
0857:                        // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
0858:                        writeLoggedFlushedResponse("503 program error, function not performed");
0859:                        return;
0860:                    } else if (extension.equals("DISTRIB.PATS")) {
0861:                        // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
0862:                        writeLoggedFlushedResponse("503 program error, function not performed");
0863:                        return;
0864:                    } else {
0865:                        writeLoggedFlushedResponse("501 Syntax error");
0866:                        return;
0867:                    }
0868:                }
0869:
0870:                Iterator iter = theConfigData.getNNTPRepository()
0871:                        .getMatchedGroups(wildmat);
0872:                writeLoggedFlushedResponse("215 list of newsgroups follows");
0873:                while (iter.hasNext()) {
0874:                    NNTPGroup theGroup = (NNTPGroup) iter.next();
0875:                    if (isListNewsgroups) {
0876:                        writeLoggedResponse(theGroup.getListNewsgroupsFormat());
0877:                    } else {
0878:                        writeLoggedResponse(theGroup.getListFormat());
0879:                    }
0880:                }
0881:                writeLoggedFlushedResponse(".");
0882:            }
0883:
0884:            /**
0885:             * Informs the server that the client has an article with the specified
0886:             * message-ID.
0887:             *
0888:             * @param id the message id
0889:             */
0890:            private void doIHAVE(String id) {
0891:                // see section 9.3.2.1
0892:                if (!isMessageId(id)) {
0893:                    writeLoggedFlushedResponse("501 command syntax error");
0894:                    return;
0895:                }
0896:                NNTPArticle article = theConfigData.getNNTPRepository()
0897:                        .getArticleFromID(id);
0898:                if (article != null) {
0899:                    writeLoggedFlushedResponse("435 article not wanted - do not send it");
0900:                } else {
0901:                    writeLoggedFlushedResponse("335 send article to be transferred. End with <CR-LF>.<CR-LF>");
0902:                    try {
0903:                        createArticle();
0904:                    } catch (RuntimeException e) {
0905:                        writeLoggedFlushedResponse("436 transfer failed - try again later");
0906:                        throw e;
0907:                    }
0908:                    writeLoggedFlushedResponse("235 article received ok");
0909:                }
0910:            }
0911:
0912:            /**
0913:             * Posts an article to the news server.
0914:             *
0915:             * @param argument the argument passed in with the POST command
0916:             */
0917:            private void doPOST(String argument) {
0918:                // see section 9.3.1.1
0919:                if (argument != null) {
0920:                    writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
0921:                }
0922:                writeLoggedFlushedResponse("340 send article to be posted. End with <CR-LF>.<CR-LF>");
0923:                createArticle();
0924:                writeLoggedFlushedResponse("240 article received ok");
0925:            }
0926:
0927:            /**
0928:             * Executes the STAT command.  Sets the current article pointer,
0929:             * returns article information.
0930:             *
0931:             * @param the argument passed in to the STAT command,
0932:             *        which should be an article number or message id.
0933:             *        If no parameter is provided, the current selected
0934:             *        article is used.
0935:             */
0936:            private void doSTAT(String param) {
0937:                // section 9.2.4
0938:                NNTPArticle article = null;
0939:                if (isMessageId(param)) {
0940:                    article = theConfigData.getNNTPRepository()
0941:                            .getArticleFromID(param);
0942:                    if (article == null) {
0943:                        writeLoggedFlushedResponse("430 no such article");
0944:                        return;
0945:                    } else {
0946:                        StringBuffer respBuffer = new StringBuffer(64).append(
0947:                                "223 0 ").append(param);
0948:                        writeLoggedFlushedResponse(respBuffer.toString());
0949:                    }
0950:                } else {
0951:                    int newArticleNumber = currentArticleNumber;
0952:                    if (group == null) {
0953:                        writeLoggedFlushedResponse("412 no newsgroup selected");
0954:                        return;
0955:                    } else {
0956:                        if (param == null) {
0957:                            if (currentArticleNumber < 0) {
0958:                                writeLoggedFlushedResponse("420 no current article selected");
0959:                                return;
0960:                            } else {
0961:                                article = group
0962:                                        .getArticle(currentArticleNumber);
0963:                            }
0964:                        } else {
0965:                            newArticleNumber = Integer.parseInt(param);
0966:                            article = group.getArticle(newArticleNumber);
0967:                        }
0968:                        if (article == null) {
0969:                            writeLoggedFlushedResponse("423 no such article number in this group");
0970:                            return;
0971:                        } else {
0972:                            currentArticleNumber = newArticleNumber;
0973:                            String articleID = article.getUniqueID();
0974:                            if (articleID == null) {
0975:                                articleID = "<0>";
0976:                            }
0977:                            StringBuffer respBuffer = new StringBuffer(128)
0978:                                    .append("223 ").append(
0979:                                            article.getArticleNumber()).append(
0980:                                            " ").append(articleID);
0981:                            writeLoggedFlushedResponse(respBuffer.toString());
0982:                        }
0983:                    }
0984:                }
0985:            }
0986:
0987:            /**
0988:             * Executes the BODY command.  Sets the current article pointer,
0989:             * returns article information and body.
0990:             *
0991:             * @param the argument passed in to the BODY command,
0992:             *        which should be an article number or message id.
0993:             *        If no parameter is provided, the current selected
0994:             *        article is used.
0995:             */
0996:            private void doBODY(String param) {
0997:                // section 9.2.3
0998:                NNTPArticle article = null;
0999:                if (isMessageId(param)) {
1000:                    article = theConfigData.getNNTPRepository()
1001:                            .getArticleFromID(param);
1002:                    if (article == null) {
1003:                        writeLoggedFlushedResponse("430 no such article");
1004:                        return;
1005:                    } else {
1006:                        StringBuffer respBuffer = new StringBuffer(64).append(
1007:                                "222 0 ").append(param);
1008:                        writeLoggedFlushedResponse(respBuffer.toString());
1009:                    }
1010:                } else {
1011:                    int newArticleNumber = currentArticleNumber;
1012:                    if (group == null) {
1013:                        writeLoggedFlushedResponse("412 no newsgroup selected");
1014:                        return;
1015:                    } else {
1016:                        if (param == null) {
1017:                            if (currentArticleNumber < 0) {
1018:                                writeLoggedFlushedResponse("420 no current article selected");
1019:                                return;
1020:                            } else {
1021:                                article = group
1022:                                        .getArticle(currentArticleNumber);
1023:                            }
1024:                        } else {
1025:                            newArticleNumber = Integer.parseInt(param);
1026:                            article = group.getArticle(newArticleNumber);
1027:                        }
1028:                        if (article == null) {
1029:                            writeLoggedFlushedResponse("423 no such article number in this group");
1030:                            return;
1031:                        } else {
1032:                            currentArticleNumber = newArticleNumber;
1033:                            String articleID = article.getUniqueID();
1034:                            if (articleID == null) {
1035:                                articleID = "<0>";
1036:                            }
1037:                            StringBuffer respBuffer = new StringBuffer(128)
1038:                                    .append("222 ").append(
1039:                                            article.getArticleNumber()).append(
1040:                                            " ").append(articleID);
1041:                            writeLoggedFlushedResponse(respBuffer.toString());
1042:                        }
1043:                    }
1044:                }
1045:                if (article != null) {
1046:                    writer.flush();
1047:                    article.writeBody(new ExtraDotOutputStream(outs));
1048:                    writeLoggedFlushedResponse("\r\n.");
1049:                }
1050:            }
1051:
1052:            /**
1053:             * Executes the HEAD command.  Sets the current article pointer,
1054:             * returns article information and headers.
1055:             *
1056:             * @param the argument passed in to the HEAD command,
1057:             *        which should be an article number or message id.
1058:             *        If no parameter is provided, the current selected
1059:             *        article is used.
1060:             */
1061:            private void doHEAD(String param) {
1062:                // section 9.2.2
1063:                NNTPArticle article = null;
1064:                if (isMessageId(param)) {
1065:                    article = theConfigData.getNNTPRepository()
1066:                            .getArticleFromID(param);
1067:                    if (article == null) {
1068:                        writeLoggedFlushedResponse("430 no such article");
1069:                        return;
1070:                    } else {
1071:                        StringBuffer respBuffer = new StringBuffer(64).append(
1072:                                "221 0 ").append(param);
1073:                        writeLoggedFlushedResponse(respBuffer.toString());
1074:                    }
1075:                } else {
1076:                    int newArticleNumber = currentArticleNumber;
1077:                    if (group == null) {
1078:                        writeLoggedFlushedResponse("412 no newsgroup selected");
1079:                        return;
1080:                    } else {
1081:                        if (param == null) {
1082:                            if (currentArticleNumber < 0) {
1083:                                writeLoggedFlushedResponse("420 no current article selected");
1084:                                return;
1085:                            } else {
1086:                                article = group
1087:                                        .getArticle(currentArticleNumber);
1088:                            }
1089:                        } else {
1090:                            newArticleNumber = Integer.parseInt(param);
1091:                            article = group.getArticle(newArticleNumber);
1092:                        }
1093:                        if (article == null) {
1094:                            writeLoggedFlushedResponse("423 no such article number in this group");
1095:                            return;
1096:                        } else {
1097:                            currentArticleNumber = newArticleNumber;
1098:                            String articleID = article.getUniqueID();
1099:                            if (articleID == null) {
1100:                                articleID = "<0>";
1101:                            }
1102:                            StringBuffer respBuffer = new StringBuffer(128)
1103:                                    .append("221 ").append(
1104:                                            article.getArticleNumber()).append(
1105:                                            " ").append(articleID);
1106:                            writeLoggedFlushedResponse(respBuffer.toString());
1107:                        }
1108:                    }
1109:                }
1110:                if (article != null) {
1111:                    writer.flush();
1112:                    article.writeHead(new ExtraDotOutputStream(outs));
1113:                    writeLoggedFlushedResponse(".");
1114:                }
1115:            }
1116:
1117:            /**
1118:             * Executes the ARTICLE command.  Sets the current article pointer,
1119:             * returns article information and contents.
1120:             *
1121:             * @param the argument passed in to the ARTICLE command,
1122:             *        which should be an article number or message id.
1123:             *        If no parameter is provided, the current selected
1124:             *        article is used.
1125:             */
1126:            private void doARTICLE(String param) {
1127:                // section 9.2.1
1128:                NNTPArticle article = null;
1129:                if (isMessageId(param)) {
1130:                    article = theConfigData.getNNTPRepository()
1131:                            .getArticleFromID(param);
1132:                    if (article == null) {
1133:                        writeLoggedFlushedResponse("430 no such article");
1134:                        return;
1135:                    } else {
1136:                        StringBuffer respBuffer = new StringBuffer(64).append(
1137:                                "220 0 ").append(param);
1138:                        writeLoggedResponse(respBuffer.toString());
1139:                    }
1140:                } else {
1141:                    int newArticleNumber = currentArticleNumber;
1142:                    if (group == null) {
1143:                        writeLoggedFlushedResponse("412 no newsgroup selected");
1144:                        return;
1145:                    } else {
1146:                        if (param == null) {
1147:                            if (currentArticleNumber < 0) {
1148:                                writeLoggedFlushedResponse("420 no current article selected");
1149:                                return;
1150:                            } else {
1151:                                article = group
1152:                                        .getArticle(currentArticleNumber);
1153:                            }
1154:                        } else {
1155:                            newArticleNumber = Integer.parseInt(param);
1156:                            article = group.getArticle(newArticleNumber);
1157:                        }
1158:                        if (article == null) {
1159:                            writeLoggedFlushedResponse("423 no such article number in this group");
1160:                            return;
1161:                        } else {
1162:                            currentArticleNumber = newArticleNumber;
1163:                            String articleID = article.getUniqueID();
1164:                            if (articleID == null) {
1165:                                articleID = "<0>";
1166:                            }
1167:                            StringBuffer respBuffer = new StringBuffer(128)
1168:                                    .append("220 ").append(
1169:                                            article.getArticleNumber()).append(
1170:                                            " ").append(articleID);
1171:                            writeLoggedFlushedResponse(respBuffer.toString());
1172:                        }
1173:                    }
1174:                }
1175:                if (article != null) {
1176:                    writer.flush();
1177:                    article.writeArticle(new ExtraDotOutputStream(outs));
1178:                    // see jira JAMES-311 for an explanation of the "\r\n"
1179:                    writeLoggedFlushedResponse("\r\n.");
1180:                }
1181:            }
1182:
1183:            /**
1184:             * Advances the current article pointer to the next article in the group.
1185:             *
1186:             * @param argument the argument passed in with the NEXT command
1187:             */
1188:            private void doNEXT(String argument) {
1189:                // section 9.1.1.3.1
1190:                if (argument != null) {
1191:                    writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1192:                } else if (group == null) {
1193:                    writeLoggedFlushedResponse("412 no newsgroup selected");
1194:                } else if (currentArticleNumber < 0) {
1195:                    writeLoggedFlushedResponse("420 no current article has been selected");
1196:                } else if (currentArticleNumber >= group.getLastArticleNumber()) {
1197:                    writeLoggedFlushedResponse("421 no next article in this group");
1198:                } else {
1199:                    currentArticleNumber++;
1200:                    NNTPArticle article = group
1201:                            .getArticle(currentArticleNumber);
1202:                    StringBuffer respBuffer = new StringBuffer(64).append(
1203:                            "223 ").append(article.getArticleNumber()).append(
1204:                            " ").append(article.getUniqueID());
1205:                    writeLoggedFlushedResponse(respBuffer.toString());
1206:                }
1207:            }
1208:
1209:            /**
1210:             * Moves the currently selected article pointer to the article
1211:             * previous to the currently selected article in the selected group.
1212:             *
1213:             * @param argument the argument passed in with the LAST command
1214:             */
1215:            private void doLAST(String argument) {
1216:                // section 9.1.1.2.1
1217:                if (argument != null) {
1218:                    writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1219:                } else if (group == null) {
1220:                    writeLoggedFlushedResponse("412 no newsgroup selected");
1221:                } else if (currentArticleNumber < 0) {
1222:                    writeLoggedFlushedResponse("420 no current article has been selected");
1223:                } else if (currentArticleNumber <= group
1224:                        .getFirstArticleNumber()) {
1225:                    writeLoggedFlushedResponse("422 no previous article in this group");
1226:                } else {
1227:                    currentArticleNumber--;
1228:                    NNTPArticle article = group
1229:                            .getArticle(currentArticleNumber);
1230:                    StringBuffer respBuffer = new StringBuffer(64).append(
1231:                            "223 ").append(article.getArticleNumber()).append(
1232:                            " ").append(article.getUniqueID());
1233:                    writeLoggedFlushedResponse(respBuffer.toString());
1234:                }
1235:            }
1236:
1237:            /**
1238:             * Selects a group to be the current newsgroup.
1239:             *
1240:             * @param group the name of the group being selected.
1241:             */
1242:            private void doGROUP(String groupName) {
1243:                if (groupName == null) {
1244:                    writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1245:                    return;
1246:                }
1247:                NNTPGroup newGroup = theConfigData.getNNTPRepository()
1248:                        .getGroup(groupName);
1249:                // section 9.1.1.1
1250:                if (newGroup == null) {
1251:                    writeLoggedFlushedResponse("411 no such newsgroup");
1252:                } else {
1253:                    group = newGroup;
1254:                    // if the number of articles in group == 0
1255:                    // then the server may return this information in 3 ways, 
1256:                    // The clients must honor all those 3 ways.
1257:                    // our response is: 
1258:                    // highWaterMark == lowWaterMark and number of articles == 0
1259:                    int articleCount = group.getNumberOfArticles();
1260:                    int lowWaterMark = group.getFirstArticleNumber();
1261:                    int highWaterMark = group.getLastArticleNumber();
1262:
1263:                    // Set the current article pointer.  If no
1264:                    // articles are in the group, the current article
1265:                    // pointer should be explicitly unset.
1266:                    if (articleCount != 0) {
1267:                        currentArticleNumber = lowWaterMark;
1268:                    } else {
1269:                        currentArticleNumber = -1;
1270:                    }
1271:                    StringBuffer respBuffer = new StringBuffer(128).append(
1272:                            "211 ").append(articleCount).append(" ").append(
1273:                            lowWaterMark).append(" ").append(highWaterMark)
1274:                            .append(" ").append(group.getName()).append(
1275:                                    " group selected");
1276:                    writeLoggedFlushedResponse(respBuffer.toString());
1277:                }
1278:            }
1279:
1280:            /**
1281:             * Lists the extensions supported by this news server.
1282:             */
1283:            private void doLISTEXTENSIONS() {
1284:                // 8.1.1
1285:                writeLoggedResponse("202 Extensions supported:");
1286:                writeLoggedResponse("LISTGROUP");
1287:                writeLoggedResponse("AUTHINFO");
1288:                writeLoggedResponse("OVER");
1289:                writeLoggedResponse("XOVER");
1290:                writeLoggedResponse("HDR");
1291:                writeLoggedResponse("XHDR");
1292:                writeLoggedFlushedResponse(".");
1293:            }
1294:
1295:            /**
1296:             * Informs the server that the client is a newsreader.
1297:             *
1298:             * @param argument the argument passed in with the MODE READER command
1299:             */
1300:            private void doMODEREADER(String argument) {
1301:                // 7.2
1302:                writeLoggedFlushedResponse(theConfigData.getNNTPRepository()
1303:                        .isReadOnly() ? "201 Posting Not Permitted"
1304:                        : "200 Posting Permitted");
1305:            }
1306:
1307:            /**
1308:             * Informs the server that the client is a news server.
1309:             *
1310:             * @param argument the argument passed in with the MODE STREAM command
1311:             */
1312:            private void doMODESTREAM(String argument) {
1313:                // 7.2
1314:                writeLoggedFlushedResponse("500 Command not understood");
1315:            }
1316:
1317:            /**
1318:             * Gets a listing of article numbers in specified group name
1319:             * or in the already selected group if the groupName is null.
1320:             *
1321:             * @param groupName the name of the group to list
1322:             */
1323:            private void doLISTGROUP(String groupName) {
1324:                // 9.5.1.1.1
1325:                if (groupName == null) {
1326:                    if (group == null) {
1327:                        writeLoggedFlushedResponse("412 no news group currently selected");
1328:                        return;
1329:                    }
1330:                } else {
1331:                    group = theConfigData.getNNTPRepository().getGroup(
1332:                            groupName);
1333:                    if (group == null) {
1334:                        writeLoggedFlushedResponse("411 no such newsgroup");
1335:                        return;
1336:                    }
1337:                }
1338:                if (group != null) {
1339:                    // this.group = group;
1340:
1341:                    // Set the current article pointer.  If no
1342:                    // articles are in the group, the current article
1343:                    // pointer should be explicitly unset.
1344:                    if (group.getNumberOfArticles() > 0) {
1345:                        currentArticleNumber = group.getFirstArticleNumber();
1346:                    } else {
1347:                        currentArticleNumber = -1;
1348:                    }
1349:
1350:                    writeLoggedFlushedResponse("211 list of article numbers follow");
1351:
1352:                    Iterator iter = group.getArticles();
1353:                    while (iter.hasNext()) {
1354:                        NNTPArticle article = (NNTPArticle) iter.next();
1355:                        writeLoggedResponse(article.getArticleNumber() + "");
1356:                    }
1357:                    writeLoggedFlushedResponse(".");
1358:                }
1359:            }
1360:
1361:            /**
1362:             * Handles the LIST OVERVIEW.FMT command.  Not supported.
1363:             */
1364:            private void doLISTOVERVIEWFMT() {
1365:                // 9.5.3.1.1
1366:                writeLoggedFlushedResponse("215 Information follows");
1367:                String[] overviewHeaders = theConfigData.getNNTPRepository()
1368:                        .getOverviewFormat();
1369:                for (int i = 0; i < overviewHeaders.length; i++) {
1370:                    writeLoggedResponse(overviewHeaders[i]);
1371:                }
1372:                writeLoggedFlushedResponse(".");
1373:            }
1374:
1375:            /**
1376:             * Handles the PAT command.  Not supported.
1377:             *
1378:             * @param argument the argument passed in with the PAT command
1379:             */
1380:            private void doPAT(String argument) {
1381:                // 9.5.3.1.1 in draft-12
1382:                writeLoggedFlushedResponse("500 Command not recognized");
1383:            }
1384:
1385:            /**
1386:             * Get the values of the headers for the selected newsgroup, 
1387:             * with an optional range modifier.
1388:             *
1389:             * @param argument the argument passed in with the XHDR command.
1390:             */
1391:            private void doXHDR(String argument) {
1392:                doHDR(argument);
1393:            }
1394:
1395:            /**
1396:             * Get the values of the headers for the selected newsgroup, 
1397:             * with an optional range modifier.
1398:             *
1399:             * @param argument the argument passed in with the HDR command.
1400:             */
1401:            private void doHDR(String argument) {
1402:                // 9.5.3
1403:                if (argument == null) {
1404:                    writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1405:                    return;
1406:                }
1407:                String hdr = argument;
1408:                String range = null;
1409:                int spaceIndex = hdr.indexOf(" ");
1410:                if (spaceIndex >= 0) {
1411:                    range = hdr.substring(spaceIndex + 1);
1412:                    hdr = hdr.substring(0, spaceIndex);
1413:                }
1414:                if (group == null) {
1415:                    writeLoggedFlushedResponse("412 No news group currently selected.");
1416:                    return;
1417:                }
1418:                if ((range == null) && (currentArticleNumber < 0)) {
1419:                    writeLoggedFlushedResponse("420 No current article selected");
1420:                    return;
1421:                }
1422:                NNTPArticle[] article = getRange(range);
1423:                if (article == null) {
1424:                    writeLoggedFlushedResponse("412 no newsgroup selected");
1425:                } else if (article.length == 0) {
1426:                    writeLoggedFlushedResponse("430 no such article");
1427:                } else {
1428:                    writeLoggedFlushedResponse("221 Header follows");
1429:                    for (int i = 0; i < article.length; i++) {
1430:                        String val = article[i].getHeader(hdr);
1431:                        if (val == null) {
1432:                            val = "";
1433:                        }
1434:                        StringBuffer hdrBuffer = new StringBuffer(128).append(
1435:                                article[i].getArticleNumber()).append(" ")
1436:                                .append(val);
1437:                        writeLoggedResponse(hdrBuffer.toString());
1438:                    }
1439:                    writeLoggedFlushedResponse(".");
1440:                }
1441:            }
1442:
1443:            /**
1444:             * Returns information from the overview database regarding the
1445:             * current article, or a range of articles.
1446:             *
1447:             * @param range the optional article range.
1448:             */
1449:            private void doXOVER(String range) {
1450:                doOVER(range);
1451:            }
1452:
1453:            /**
1454:             * Returns information from the overview database regarding the
1455:             * current article, or a range of articles.
1456:             *
1457:             * @param range the optional article range.
1458:             */
1459:            private void doOVER(String range) {
1460:                // 9.5.2.2.1
1461:                if (group == null) {
1462:                    writeLoggedFlushedResponse("412 No newsgroup selected");
1463:                    return;
1464:                }
1465:                if ((range == null) && (currentArticleNumber < 0)) {
1466:                    writeLoggedFlushedResponse("420 No current article selected");
1467:                    return;
1468:                }
1469:                NNTPArticle[] article = getRange(range);
1470:                if (article.length == 0) {
1471:                    writeLoggedFlushedResponse("420 No article(s) selected");
1472:                } else {
1473:                    writeLoggedResponse("224 Overview information follows");
1474:                    for (int i = 0; i < article.length; i++) {
1475:                        article[i].writeOverview(outs);
1476:                        if (i % 100 == 0) {
1477:                            // Reset the watchdog every hundred headers or so
1478:                            // to ensure the connection doesn't timeout for slow
1479:                            // clients
1480:                            theWatchdog.reset();
1481:                        }
1482:                    }
1483:                    writeLoggedFlushedResponse(".");
1484:                }
1485:            }
1486:
1487:            /**
1488:             * Handles the transaction for getting the article data.
1489:             */
1490:            private void createArticle() {
1491:                try {
1492:                    InputStream msgIn = new CharTerminatedInputStream(in,
1493:                            NNTPTerminator);
1494:                    // Removes the dot stuffing
1495:                    msgIn = new DotStuffingInputStream(msgIn);
1496:                    MailHeaders headers = new MailHeaders(msgIn);
1497:                    processMessageHeaders(headers);
1498:                    processMessage(headers, msgIn);
1499:                } catch (MessagingException me) {
1500:                    throw new NNTPException(
1501:                            "MessagingException encountered when loading article.");
1502:                }
1503:            }
1504:
1505:            /**
1506:             * Processes the NNTP message headers coming in off the wire.
1507:             *
1508:             * @param headers the headers of the message being read
1509:             */
1510:            private MailHeaders processMessageHeaders(MailHeaders headers)
1511:                    throws MessagingException {
1512:                return headers;
1513:            }
1514:
1515:            /**
1516:             * Processes the NNTP message coming in off the wire.  Reads the
1517:             * content and delivers to the spool.
1518:             *
1519:             * @param headers the headers of the message being read
1520:             * @param msgIn the stream containing the message content
1521:             */
1522:            private void processMessage(MailHeaders headers, InputStream bodyIn)
1523:                    throws MessagingException {
1524:                InputStream messageIn = null;
1525:                try {
1526:                    messageIn = new SequenceInputStream(
1527:                            new ByteArrayInputStream(headers.toByteArray()),
1528:                            bodyIn);
1529:                    theConfigData.getNNTPRepository().createArticle(messageIn);
1530:                } finally {
1531:                    if (messageIn != null) {
1532:                        try {
1533:                            messageIn.close();
1534:                        } catch (IOException ioe) {
1535:                            // Ignore exception on close.
1536:                        }
1537:                        messageIn = null;
1538:                    }
1539:                }
1540:            }
1541:
1542:            /**
1543:             * Returns the date from @param input.
1544:             * The input tokens are assumed to be in format date time [GMT|UTC] .
1545:             * 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
1546:             * NOTE: This routine could do with some format checks.
1547:             *
1548:             * @param argument the date string
1549:             */
1550:            private Date getDateFrom(String argument) {
1551:                if (argument == null) {
1552:                    throw new NNTPException("Date argument was absent.");
1553:                }
1554:                StringTokenizer tok = new StringTokenizer(argument, " ");
1555:                if (tok.countTokens() < 2) {
1556:                    throw new NNTPException("Date argument was ill-formed.");
1557:                }
1558:                String date = tok.nextToken();
1559:                String time = tok.nextToken();
1560:                boolean utc = (tok.hasMoreTokens());
1561:                Date d = new Date();
1562:                try {
1563:                    StringBuffer dateStringBuffer = new StringBuffer(64)
1564:                            .append(date).append(" ").append(time);
1565:                    Date dt = DF_RFC977.parse(dateStringBuffer.toString());
1566:                    if (utc) {
1567:                        dt = new Date(dt.getTime() + UTC_OFFSET);
1568:                    }
1569:                    return dt;
1570:                } catch (ParseException pe) {
1571:                    StringBuffer exceptionBuffer = new StringBuffer(128)
1572:                            .append("Date extraction failed: ").append(date)
1573:                            .append(",").append(time).append(",").append(utc);
1574:                    throw new NNTPException(exceptionBuffer.toString());
1575:                }
1576:            }
1577:
1578:            /**
1579:             * Returns the list of articles that match the range.
1580:             *
1581:             * A precondition of this method is that the selected
1582:             * group be non-null.  The current article pointer must
1583:             * be valid if no range is explicitly provided.
1584:             *
1585:             * @return null indicates insufficient information to
1586:             * fetch the list of articles
1587:             */
1588:            private NNTPArticle[] getRange(String range) {
1589:                // check for msg id
1590:                if (isMessageId(range)) {
1591:                    NNTPArticle article = theConfigData.getNNTPRepository()
1592:                            .getArticleFromID(range);
1593:                    return (article == null) ? new NNTPArticle[0]
1594:                            : new NNTPArticle[] { article };
1595:                }
1596:
1597:                if (range == null) {
1598:                    range = "" + currentArticleNumber;
1599:                }
1600:
1601:                int start = -1;
1602:                int end = -1;
1603:                int idx = range.indexOf('-');
1604:                if (idx == -1) {
1605:                    start = Integer.parseInt(range);
1606:                    end = start;
1607:                } else {
1608:                    start = Integer.parseInt(range.substring(0, idx));
1609:                    if ((idx + 1) == range.length()) {
1610:                        end = group.getLastArticleNumber();
1611:                    } else {
1612:                        end = Integer.parseInt(range.substring(idx + 1));
1613:                    }
1614:                }
1615:                List list = new ArrayList();
1616:                for (int i = start; i <= end; i++) {
1617:                    NNTPArticle article = group.getArticle(i);
1618:                    if (article != null) {
1619:                        list.add(article);
1620:                    }
1621:                }
1622:                return (NNTPArticle[]) list.toArray(new NNTPArticle[0]);
1623:            }
1624:
1625:            /**
1626:             * Return whether the user associated with the connection (possibly no
1627:             * user) is authorized to execute the command.
1628:             *
1629:             * @param the command being tested
1630:             * @return whether the command is authorized
1631:             */
1632:            private boolean isAuthorized(String command) {
1633:                isAlreadyAuthenticated = isAlreadyAuthenticated
1634:                        || isAuthenticated();
1635:                if (isAlreadyAuthenticated) {
1636:                    return true;
1637:                }
1638:                // some commands are authorized, even if the user is not authenticated
1639:                boolean allowed = command.equals("AUTHINFO");
1640:                allowed = allowed || command.equals("MODE");
1641:                allowed = allowed || command.equals("QUIT");
1642:                return allowed;
1643:            }
1644:
1645:            /**
1646:             * Return whether the connection has been authenticated.
1647:             *
1648:             * @return whether the connection has been authenticated.
1649:             */
1650:            private boolean isAuthenticated() {
1651:                if (theConfigData.isAuthRequired()) {
1652:                    if ((user != null) && (password != null)
1653:                            && (theConfigData.getUsersRepository() != null)) {
1654:                        return theConfigData.getUsersRepository().test(user,
1655:                                password);
1656:                    } else {
1657:                        return false;
1658:                    }
1659:                } else {
1660:                    return true;
1661:                }
1662:            }
1663:
1664:            /**
1665:             * Tests a string to see whether it is formatted as a message
1666:             * ID.
1667:             *
1668:             * @param testString the string to test
1669:             *
1670:             * @return whether the string is a candidate message ID
1671:             */
1672:            private static boolean isMessageId(String testString) {
1673:                if ((testString != null) && (testString.startsWith("<"))
1674:                        && (testString.endsWith(">"))) {
1675:                    return true;
1676:                } else {
1677:                    return false;
1678:                }
1679:            }
1680:
1681:            /**
1682:             * This method logs at a "DEBUG" level the response string that 
1683:             * was sent to the SMTP client.  The method is provided largely
1684:             * as syntactic sugar to neaten up the code base.  It is declared
1685:             * private and final to encourage compiler inlining.
1686:             *
1687:             * @param responseString the response string sent to the client
1688:             */
1689:            private final void logResponseString(String responseString) {
1690:                if (getLogger().isDebugEnabled()) {
1691:                    getLogger().debug("Sent: " + responseString);
1692:                }
1693:            }
1694:
1695:            /**
1696:             * Write and flush a response string.  The response is also logged.
1697:             * Should be used for the last line of a multi-line response or
1698:             * for a single line response.
1699:             *
1700:             * @param responseString the response string sent to the client
1701:             */
1702:            final void writeLoggedFlushedResponse(String responseString) {
1703:                writer.println(responseString);
1704:                writer.flush();
1705:                logResponseString(responseString);
1706:            }
1707:
1708:            /**
1709:             * Write a response string.  The response is also logged. 
1710:             * Used for multi-line responses.
1711:             *
1712:             * @param responseString the response string sent to the client
1713:             */
1714:            final void writeLoggedResponse(String responseString) {
1715:                writer.println(responseString);
1716:                logResponseString(responseString);
1717:            }
1718:
1719:            /**
1720:             * A private inner class which serves as an adaptor
1721:             * between the WatchdogTarget interface and this
1722:             * handler class.
1723:             */
1724:            private class NNTPWatchdogTarget implements  WatchdogTarget {
1725:
1726:                /**
1727:                 * @see org.apache.james.util.watchdog.WatchdogTarget#execute()
1728:                 */
1729:                public void execute() {
1730:                    NNTPHandler.this.idleClose();
1731:                }
1732:
1733:            }
1734:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.