1 /**
2 *
3 * Copyright 2006 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.geronimo.javamail.transport.nntp;
19
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.OutputStream;
25 import java.io.PrintStream;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.net.InetAddress;
29 import java.net.Socket;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.StringTokenizer;
33
34 import javax.mail.Message;
35 import javax.mail.MessagingException;
36 import javax.mail.Session;
37
38 import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
39 import org.apache.geronimo.javamail.authentication.CramMD5Authenticator;
40 import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator;
41 import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
42 import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
43 import org.apache.geronimo.javamail.util.MIMEOutputStream;
44 import org.apache.geronimo.javamail.util.TraceInputStream;
45 import org.apache.geronimo.javamail.util.TraceOutputStream;
46 import org.apache.geronimo.mail.util.Base64;
47 import org.apache.geronimo.mail.util.SessionUtil;
48
49 /**
50 * Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
51 * <p/> There is no way to indicate failure for a given recipient (it's possible
52 * to have a recipient address rejected). The sun impl throws exceptions even if
53 * others successful), but maybe we do a different way... <p/>
54 *
55 * @version $Rev: 432884 $ $Date: 2006-08-19 14:53:20 -0700 (Sat, 19 Aug 2006) $
56 */
57 public class NNTPConnection {
58
59 /**
60 * constants for EOL termination
61 */
62 protected static final char CR = '\r';
63
64 protected static final char LF = '\n';
65
66 /**
67 * property keys for protocol properties.
68 */
69 protected static final String MAIL_NNTP_AUTH = "auth";
70
71 protected static final String MAIL_NNTP_PORT = "port";
72
73 protected static final String MAIL_NNTP_TIMEOUT = "timeout";
74
75 protected static final String MAIL_NNTP_SASL_REALM = "sasl.realm";
76
77 protected static final String MAIL_NNTP_FACTORY_CLASS = "socketFactory.class";
78
79 protected static final String MAIL_NNTP_FACTORY_FALLBACK = "fallback";
80
81 protected static final String MAIL_NNTP_LOCALADDRESS = "localaddress";
82
83 protected static final String MAIL_NNTP_LOCALPORT = "localport";
84
85 protected static final String MAIL_NNTP_QUITWAIT = "quitwait";
86
87 protected static final String MAIL_NNTP_FACTORY_PORT = "socketFactory.port";
88
89 protected static final String MAIL_NNTP_ENCODE_TRACE = "encodetrace";
90
91 protected static final int MIN_MILLIS = 1000 * 60;
92
93 protected static final int TIMEOUT = MIN_MILLIS * 5;
94
95 protected static final String DEFAULT_MAIL_HOST = "localhost";
96
97 protected static final int DEFAULT_NNTP_PORT = 119;
98
99 protected static final String AUTHENTICATION_PLAIN = "PLAIN";
100
101 protected static final String AUTHENTICATION_LOGIN = "LOGIN";
102
103 protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
104
105 protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
106
107
108 String protocol;
109
110
111 protected String host;
112
113
114 protected int port;
115
116
117
118 protected Socket socket;
119
120
121
122
123 protected InputStream inputStream;
124
125
126 protected BufferedReader in;
127
128
129 protected OutputStream outputStream;
130
131
132 protected boolean postingAllowed = true;
133
134
135 protected String username;
136
137
138 protected String password;
139
140
141
142
143 protected String realm;
144
145
146 protected NNTPReply lastServerResponse = null;
147
148
149 protected Session session;
150
151
152 protected PrintStream debugStream;
153
154
155 protected boolean debug;
156
157
158 protected HashMap serverAuthenticationMechanisms;
159
160
161 protected HashMap serverExtensionArgs;
162
163
164 protected String welcomeString = null;
165
166 /**
167 * Normal constructor for an NNTPConnection() object.
168 *
169 * @param session
170 * The attached session.
171 * @param host
172 * The target host name of the NNTP server.
173 * @param port
174 * The target listening port of the server. Defaults to 119 if
175 * the port is specified as -1.
176 * @param username
177 * The login user name (can be null unless authentication is
178 * required).
179 * @param password
180 * Password associated with the userid account. Can be null if
181 * authentication is not required.
182 * @param debug
183 * The session debug flag.
184 */
185 public NNTPConnection(String protocol, Session session, String host, int port, String username, String password,
186 boolean debug) {
187 this.protocol = protocol;
188 this.session = session;
189 this.host = host;
190 this.port = port;
191 this.username = username;
192 this.password = password;
193 this.debug = debug;
194
195
196 debugStream = session.getDebugOut();
197 }
198
199 /**
200 * Connect to the server and do the initial handshaking.
201 *
202 * @exception MessagingException
203 */
204 public void connect() throws MessagingException {
205 try {
206
207
208 getConnection();
209
210
211 getWelcome();
212
213 } catch (IOException e) {
214 if (debug) {
215 debugOut("I/O exception establishing connection", e);
216 }
217 throw new MessagingException("Connection error", e);
218 }
219 }
220
221 /**
222 * Close the connection. On completion, we'll be disconnected from the
223 * server and unable to send more data.
224 *
225 * @exception MessagingException
226 */
227 public void close() throws MessagingException {
228
229 if (socket == null) {
230 return;
231 }
232 try {
233
234 sendQuit();
235 } finally {
236
237
238
239 closeServerConnection();
240 }
241 }
242
243 /**
244 * Create a transport connection object and connect it to the target server.
245 *
246 * @exception MessagingException
247 */
248 protected void getConnection() throws IOException {
249
250
251 if (socket == null) {
252 getConnectedSocket();
253 }
254
255
256 else {
257 port = socket.getPort();
258 host = socket.getInetAddress().getHostName();
259 }
260
261
262 inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug, getBooleanProperty(
263 MAIL_NNTP_ENCODE_TRACE, false));
264 ;
265 outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug, getBooleanProperty(
266 MAIL_NNTP_ENCODE_TRACE, false));
267
268
269 in = new BufferedReader(new InputStreamReader(inputStream));
270 }
271
272 /**
273 * Close the server connection at termination.
274 */
275 public void closeServerConnection() {
276 try {
277 socket.close();
278 } catch (IOException ignored) {
279 }
280
281 socket = null;
282 inputStream = null;
283 outputStream = null;
284 in = null;
285 }
286
287 /**
288 * Creates a connected socket
289 *
290 * @exception MessagingException
291 */
292 public void getConnectedSocket() throws IOException {
293 if (debug) {
294 debugOut("Attempting plain socket connection to server " + host + ":" + port);
295 }
296
297
298
299
300 String socketFactory = getProperty(MAIL_NNTP_FACTORY_CLASS);
301
302
303
304
305 int timeout = getIntProperty(MAIL_NNTP_TIMEOUT, -1);
306 InetAddress localAddress = null;
307
308 String localAddrProp = getProperty(MAIL_NNTP_LOCALADDRESS);
309 if (localAddrProp != null) {
310 localAddress = InetAddress.getByName(localAddrProp);
311 }
312
313
314 int localPort = getIntProperty(MAIL_NNTP_LOCALPORT, 0);
315
316 socket = null;
317
318
319
320 if (socketFactory == null) {
321 socket = new Socket(host, port, localAddress, localPort);
322 }
323
324 else {
325 try {
326 int socketFactoryPort = getIntProperty(MAIL_NNTP_FACTORY_PORT, -1);
327
328
329 Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
330
331
332 ClassLoader loader = Thread.currentThread().getContextClassLoader();
333 Class factoryClass = loader.loadClass(socketFactory);
334
335
336
337
338 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
339 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
340
341
342
343
344
345 if (localAddress != null) {
346
347
348 Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
349 Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
350
351 Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
352 socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
353 } else {
354
355 Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
356 Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
357
358 Object[] createSocketArgs = new Object[] { host, portArg };
359 socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
360 }
361 } catch (Throwable e) {
362
363
364
365 if (getBooleanProperty(MAIL_NNTP_FACTORY_FALLBACK, false)) {
366 if (debug) {
367 debugOut("First plain socket attempt faile, falling back to default factory", e);
368 }
369 socket = new Socket(host, port, localAddress, localPort);
370 }
371
372
373
374 else {
375
376
377 if (e instanceof InvocationTargetException) {
378 e = ((InvocationTargetException) e).getTargetException();
379 }
380
381 if (debug) {
382 debugOut("Plain socket creation failure", e);
383 }
384
385
386
387 IOException ioe = new IOException("Error connecting to " + host + ", " + port);
388 ioe.initCause(e);
389 throw ioe;
390 }
391 }
392 }
393
394 if (timeout >= 0) {
395 socket.setSoTimeout(timeout);
396 }
397 }
398
399 /**
400 * Get the servers welcome blob from the wire....
401 */
402 public void getWelcome() throws MessagingException {
403 NNTPReply line = getReply();
404
405
406 if (line.isError()) {
407 throw new MessagingException("Error connecting to news server: " + line.getMessage());
408 }
409
410
411 if (line.getCode() == NNTPReply.POSTING_ALLOWED) {
412 postingAllowed = true;
413 } else {
414 postingAllowed = false;
415 }
416
417
418 welcomeString = line.getMessage();
419
420
421 getExtensions();
422 }
423
424 /**
425 * Sends the QUIT message and receieves the response
426 */
427 public void sendQuit() throws MessagingException {
428
429
430
431
432 if (getBooleanProperty(MAIL_NNTP_QUITWAIT, false)) {
433 sendLine("QUIT");
434 } else {
435
436 sendCommand("QUIT");
437 }
438 }
439
440 /**
441 * Tell the server to switch to a named group.
442 *
443 * @param name
444 * The name of the target group.
445 *
446 * @return The server response to the GROUP command.
447 */
448 public NNTPReply selectGroup(String name) throws MessagingException {
449
450 return sendCommand("GROUP " + name);
451 }
452
453 /**
454 * Ask the server what extensions it supports.
455 *
456 * @return True if the command was accepted ok, false for any errors.
457 * @exception MessagingException
458 */
459 protected void getExtensions() throws MessagingException {
460 NNTPReply reply = sendCommand("LIST EXTENSIONS", NNTPReply.EXTENSIONS_SUPPORTED);
461
462
463
464
465 if (reply.getCode() != NNTPReply.EXTENSIONS_SUPPORTED) {
466 return;
467 }
468
469
470 serverExtensionArgs = new HashMap();
471 serverAuthenticationMechanisms = new HashMap();
472
473
474 List extensions = reply.getData();
475
476
477 for (int i = 0; i < extensions.size(); i++) {
478
479 processExtension((String) extensions.get(i));
480 }
481 }
482
483 /**
484 * Process an extension string passed back as the EHLP response.
485 *
486 * @param extension
487 * The string value of the extension (which will be of the form
488 * "NAME arguments").
489 */
490 protected void processExtension(String extension) {
491 String extensionName = extension.toUpperCase();
492 String argument = "";
493
494 int delimiter = extension.indexOf(' ');
495
496
497 if (delimiter != -1) {
498 extensionName = extension.substring(0, delimiter).toUpperCase();
499 argument = extension.substring(delimiter + 1);
500 }
501
502
503 serverExtensionArgs.put(extensionName, argument);
504
505
506
507 if (extensionName.equals("AUTHINFO")) {
508 serverAuthenticationMechanisms.put("AUTHINFO", "AUTHINFO");
509 }
510
511 else if (extensionName.equals("SASL")) {
512
513 StringTokenizer tokenizer = new StringTokenizer(argument);
514
515 while (tokenizer.hasMoreTokens()) {
516 String mechanism = tokenizer.nextToken().toUpperCase();
517 serverAuthenticationMechanisms.put(mechanism, mechanism);
518 }
519 }
520 }
521
522 /**
523 * Retrieve any argument information associated with a extension reported
524 * back by the server on the EHLO command.
525 *
526 * @param name
527 * The name of the target server extension.
528 *
529 * @return Any argument passed on a server extension. Returns null if the
530 * extension did not include an argument or the extension was not
531 * supported.
532 */
533 public String extensionParameter(String name) {
534 if (serverExtensionArgs != null) {
535 return (String) serverExtensionArgs.get(name);
536 }
537 return null;
538 }
539
540 /**
541 * Tests whether the target server supports a named extension.
542 *
543 * @param name
544 * The target extension name.
545 *
546 * @return true if the target server reported on the EHLO command that is
547 * supports the targer server, false if the extension was not
548 * supported.
549 */
550 public boolean supportsExtension(String name) {
551
552 return extensionParameter(name) != null;
553 }
554
555 /**
556 * Determine if the target server supports a given authentication mechanism.
557 *
558 * @param mechanism
559 * The mechanism name.
560 *
561 * @return true if the server EHLO response indicates it supports the
562 * mechanism, false otherwise.
563 */
564 protected boolean supportsAuthentication(String mechanism) {
565 return serverAuthenticationMechanisms.get(mechanism) != null;
566 }
567
568 /**
569 * Sends the data in the message down the socket. This presumes the server
570 * is in the right place and ready for getting the DATA message and the data
571 * right place in the sequence
572 */
573 public synchronized void sendPost(Message msg) throws MessagingException {
574
575
576 NNTPReply line = sendCommand("POST");
577
578 if (line.getCode() != NNTPReply.SEND_ARTICLE) {
579 throw new MessagingException("Server rejected POST command: " + line);
580 }
581
582
583
584 try {
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599 OutputStream mimeOut = new MIMEOutputStream(outputStream);
600
601 msg.writeTo(mimeOut);
602 mimeOut.flush();
603 } catch (IOException e) {
604 throw new MessagingException("I/O error posting message", e);
605 } catch (MessagingException e) {
606 throw new MessagingException("Exception posting message", e);
607 }
608
609
610 sendLine("");
611 sendLine(".");
612
613
614
615 line = new NNTPReply(receiveLine());
616
617 if (line.getCode() != NNTPReply.POSTED_OK) {
618 throw new MessagingException("Server rejected POST command: " + line);
619 }
620 }
621
622 /**
623 * Issue a command and retrieve the response. If the given success indicator
624 * is received, the command is returning a longer response, terminated by a
625 * "crlf.crlf" sequence. These lines are attached to the reply.
626 *
627 * @param command
628 * The command to issue.
629 * @param success
630 * The command reply that indicates additional data should be
631 * retrieved.
632 *
633 * @return The command reply.
634 */
635 public synchronized NNTPReply sendCommand(String command, int success) throws MessagingException {
636 NNTPReply reply = sendCommand(command);
637 if (reply.getCode() == success) {
638 reply.retrieveData(in);
639 }
640 return reply;
641 }
642
643 /**
644 * Send a command to the server, returning the first response line back as a
645 * reply.
646 *
647 * @param data
648 * The data to send.
649 *
650 * @return A reply object with the reply line.
651 * @exception MessagingException
652 */
653 public NNTPReply sendCommand(String data) throws MessagingException {
654 sendLine(data);
655 NNTPReply reply = getReply();
656
657
658
659
660 if (reply.getCode() == NNTPReply.AUTHINFO_REQUIRED || reply.getCode() == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
661 if (debug) {
662 debugOut("Authentication required received from server.");
663 }
664
665 processAuthentication(reply.getCode());
666
667
668 sendLine(data);
669 reply = getReply();
670 }
671 return reply;
672 }
673
674 /**
675 * Send a command to the server, returning the first response line back as a
676 * reply.
677 *
678 * @param data
679 * The data to send.
680 *
681 * @return A reply object with the reply line.
682 * @exception MessagingException
683 */
684 public NNTPReply sendAuthCommand(String data) throws MessagingException {
685 sendLine(data);
686 return getReply();
687 }
688
689 /**
690 * Sends a message down the socket and terminates with the appropriate CRLF
691 */
692 public void sendLine(String data) throws MessagingException {
693 if (socket == null || !socket.isConnected()) {
694 throw new MessagingException("no connection");
695 }
696 try {
697 outputStream.write(data.getBytes());
698 outputStream.write(CR);
699 outputStream.write(LF);
700 outputStream.flush();
701 } catch (IOException e) {
702 throw new MessagingException(e.toString());
703 }
704 }
705
706 /**
707 * Get a reply line for an NNTP command.
708 *
709 * @return An NNTP reply object from the stream.
710 */
711 public NNTPReply getReply() throws MessagingException {
712 lastServerResponse = new NNTPReply(receiveLine());
713 return lastServerResponse;
714 }
715
716 /**
717 * Retrieve the last response received from the NNTP server.
718 *
719 * @return The raw response string (including the error code) returned from
720 * the NNTP server.
721 */
722 public String getLastServerResponse() {
723 if (lastServerResponse == null) {
724 return "";
725 }
726 return lastServerResponse.getReply();
727 }
728
729 /**
730 * Receives one line from the server. A line is a sequence of bytes
731 * terminated by a CRLF
732 *
733 * @return the line from the server as String
734 */
735 public String receiveLine() throws MessagingException {
736 if (socket == null || !socket.isConnected()) {
737 throw new MessagingException("no connection");
738 }
739
740 try {
741 String line = in.readLine();
742 if (line == null) {
743 throw new MessagingException("Unexpected end of stream");
744 }
745 return line;
746 } catch (IOException e) {
747 throw new MessagingException("Error reading from server", e);
748 }
749 }
750
751 /**
752 * Retrieve the SASL realm used for DIGEST-MD5 authentication. This will
753 * either be explicitly set, or retrieved using the mail.nntp.sasl.realm
754 * session property.
755 *
756 * @return The current realm information (which can be null).
757 */
758 public String getSASLRealm() {
759
760 if (realm == null) {
761 realm = getProperty(MAIL_NNTP_SASL_REALM);
762 }
763 return realm;
764 }
765
766 /**
767 * Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton.
768 *
769 * @param name
770 * The new realm name.
771 */
772 public void setSASLRealm(String name) {
773 realm = name;
774 }
775
776 /**
777 * Authenticate with the server, if necessary (or possible).
778 */
779 protected void processAuthentication(int request) throws MessagingException {
780
781
782
783 if (username == null || password == null) {
784 throw new MessagingException("Server requires user authentication");
785 }
786
787 if (request == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
788 processAuthinfoSimple();
789 } else {
790 if (!processAuthinfoSasl()) {
791 processAuthinfoUser();
792 }
793 }
794 }
795
796 /**
797 * Process an AUTHINFO SIMPLE command. Not widely used, but if the server
798 * asks for it, we can respond.
799 *
800 * @exception MessagingException
801 */
802 protected void processAuthinfoSimple() throws MessagingException {
803 NNTPReply reply = sendAuthCommand("AUTHINFO SIMPLE");
804 if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
805 throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
806 }
807 reply = sendAuthCommand(username + " " + password);
808 if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
809 throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
810 }
811 }
812
813 /**
814 * Process AUTHINFO GENERIC. Right now, this appears not to be widely used
815 * and information on how the conversations are handled for different auth
816 * types is lacking, so right now, this just returns false to force the
817 * userid/password form to be used.
818 *
819 * @return Always returns false.
820 * @exception MessagingException
821 */
822 protected boolean processAuthinfoGeneric() throws MessagingException {
823 return false;
824 }
825
826 /**
827 * Process AUTHINFO SASL.
828 *
829 * @return Returns true if the server support a SASL authentication
830 * mechanism and accepted reponse challenges.
831 * @exception MessagingException
832 */
833 protected boolean processAuthinfoSasl() throws MessagingException {
834 ClientAuthenticator authenticator = null;
835
836
837
838
839
840 if (supportsAuthentication(AUTHENTICATION_DIGESTMD5)) {
841 authenticator = new DigestMD5Authenticator(host, username, password, getSASLRealm());
842 } else if (supportsAuthentication(AUTHENTICATION_CRAMMD5)) {
843 authenticator = new CramMD5Authenticator(username, password);
844 } else if (supportsAuthentication(AUTHENTICATION_LOGIN)) {
845 authenticator = new LoginAuthenticator(username, password);
846 } else if (supportsAuthentication(AUTHENTICATION_PLAIN)) {
847 authenticator = new PlainAuthenticator(username, password);
848 } else {
849
850 return false;
851 }
852
853 if (debug) {
854 debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
855 }
856
857
858
859 if (authenticator.hasInitialResponse()) {
860 StringBuffer command = new StringBuffer();
861
862 command.append("AUTHINFO SASL ");
863
864 command.append(authenticator.getMechanismName());
865 command.append(" ");
866
867 command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
868
869 sendLine(command.toString());
870 }
871
872 else {
873 StringBuffer command = new StringBuffer();
874
875 command.append("AUTHINFO SASL");
876
877 command.append(authenticator.getMechanismName());
878
879 sendLine(command.toString());
880 }
881
882
883
884
885 while (true) {
886
887 NNTPReply line = getReply();
888
889
890
891 if (line.getCode() == NNTPReply.AUTHINFO_ACCEPTED || line.getCode() == NNTPReply.AUTHINFO_ACCEPTED_FINAL) {
892 if (debug) {
893 debugOut("Successful SMTP authentication");
894 }
895 return true;
896 }
897
898 else if (line.getCode() == NNTPReply.AUTHINFO_CHALLENGE) {
899
900
901
902 if (authenticator.isComplete()) {
903 if (debug) {
904 debugOut("Extra authentication challenge " + line);
905 }
906 return false;
907 }
908
909
910 byte[] challenge = Base64.decode(line.getMessage().getBytes());
911
912
913
914 sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
915 }
916
917
918
919 else {
920 if (debug) {
921 debugOut("Authentication failure " + line);
922 }
923 return false;
924 }
925 }
926 }
927
928 /**
929 * Process an AUTHINFO USER command. Most common form of NNTP
930 * authentication.
931 *
932 * @exception MessagingException
933 */
934 protected void processAuthinfoUser() throws MessagingException {
935 NNTPReply reply = sendAuthCommand("AUTHINFO USER " + username);
936
937 if (reply.getCode() == NNTPReply.AUTHINFO_ACCEPTED) {
938 return;
939 }
940
941 if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
942 throw new MessagingException("Error authenticating with server using AUTHINFO USER: " + reply);
943 }
944
945 reply = sendAuthCommand("AUTHINFO PASS " + password);
946 if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
947 throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
948 }
949 }
950
951 /**
952 * Internal debug output routine.
953 *
954 * @param value
955 * The string value to output.
956 */
957 protected void debugOut(String message) {
958 debugStream.println("NNTPTransport DEBUG: " + message);
959 }
960
961 /**
962 * Internal debugging routine for reporting exceptions.
963 *
964 * @param message
965 * A message associated with the exception context.
966 * @param e
967 * The received exception.
968 */
969 protected void debugOut(String message, Throwable e) {
970 debugOut("Received exception -> " + message);
971 debugOut("Exception message -> " + e.getMessage());
972 e.printStackTrace(debugStream);
973 }
974
975 /**
976 * Indicate whether posting is allowed for a given server.
977 *
978 * @return True if the server allows posting, false if the server is
979 * read-only.
980 */
981 public boolean isPostingAllowed() {
982 return postingAllowed;
983 }
984
985 /**
986 * Retrieve the welcome string sent back from the server.
987 *
988 * @return The server provided welcome string.
989 */
990 public String getWelcomeString() {
991 return welcomeString;
992 }
993
994 /**
995 * Return the server host for this connection.
996 *
997 * @return The String name of the server host.
998 */
999 public String getHost() {
1000 return host;
1001 }
1002
1003 /**
1004 * Get a property associated with this mail protocol.
1005 *
1006 * @param name
1007 * The name of the property.
1008 *
1009 * @return The property value (returns null if the property has not been
1010 * set).
1011 */
1012 String getProperty(String name) {
1013
1014
1015
1016 String fullName = "mail." + protocol + "." + name;
1017 return session.getProperty(fullName);
1018 }
1019
1020 /**
1021 * Get a property associated with this mail session. Returns the provided
1022 * default if it doesn't exist.
1023 *
1024 * @param name
1025 * The name of the property.
1026 * @param defaultValue
1027 * The default value to return if the property doesn't exist.
1028 *
1029 * @return The property value (returns defaultValue if the property has not
1030 * been set).
1031 */
1032 String getProperty(String name, String defaultValue) {
1033
1034
1035
1036 String fullName = "mail." + protocol + "." + name;
1037 return SessionUtil.getProperty(session, fullName, defaultValue);
1038 }
1039
1040 /**
1041 * Get a property associated with this mail session as an integer value.
1042 * Returns the default value if the property doesn't exist or it doesn't
1043 * have a valid int value.
1044 *
1045 * @param name
1046 * The name of the property.
1047 * @param defaultValue
1048 * The default value to return if the property doesn't exist.
1049 *
1050 * @return The property value converted to an int.
1051 */
1052 int getIntProperty(String name, int defaultValue) {
1053
1054
1055
1056 String fullName = "mail." + protocol + "." + name;
1057 return SessionUtil.getIntProperty(session, fullName, defaultValue);
1058 }
1059
1060 /**
1061 * Get a property associated with this mail session as an boolean value.
1062 * Returns the default value if the property doesn't exist or it doesn't
1063 * have a valid int value.
1064 *
1065 * @param name
1066 * The name of the property.
1067 * @param defaultValue
1068 * The default value to return if the property doesn't exist.
1069 *
1070 * @return The property value converted to a boolean
1071 */
1072 boolean getBooleanProperty(String name, boolean defaultValue) {
1073
1074
1075
1076 String fullName = "mail." + protocol + "." + name;
1077 return SessionUtil.getBooleanProperty(session, fullName, defaultValue);
1078 }
1079 }