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: }
|