1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.geronimo.javamail.transport.nntp;
21
22 import java.io.BufferedReader;
23 import java.io.BufferedOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.io.PrintStream;
29 import java.io.PrintWriter;
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32 import java.net.InetAddress;
33 import java.net.Socket;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.StringTokenizer;
38
39 import javax.mail.Message;
40 import javax.mail.MessagingException;
41 import javax.mail.Session;
42
43 import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
44 import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
45 import org.apache.geronimo.javamail.util.MailConnection;
46 import org.apache.geronimo.javamail.util.MIMEOutputStream;
47 import org.apache.geronimo.javamail.util.ProtocolProperties;
48 import org.apache.geronimo.mail.util.Base64;
49 import org.apache.geronimo.mail.util.SessionUtil;
50
51
52
53
54
55
56 public class NNTPConnection extends MailConnection {
57
58
59
60
61 protected static final char CR = '\r';
62
63 protected static final char LF = '\n';
64
65
66
67
68 protected static final int DEFAULT_NNTP_PORT = 119;
69
70 protected boolean postingAllowed = true;
71
72
73 protected boolean authInfoUserAllowed = false;
74 protected boolean authInfoSaslAllowed = false;
75
76
77 protected NNTPReply lastServerResponse = null;
78
79
80 protected String welcomeString = null;
81
82
83 protected BufferedReader reader;
84
85 protected PrintWriter writer;
86
87
88
89
90
91
92 public NNTPConnection(ProtocolProperties props) {
93 super(props);
94 }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
110 super.protocolConnect(host, port, username, password);
111
112 getConnection();
113
114
115 getWelcome();
116
117 return true;
118 }
119
120
121
122
123
124
125
126
127 protected void getConnection() throws MessagingException
128 {
129 try {
130
131
132 super.getConnection();
133 } catch (IOException e) {
134 throw new MessagingException("Unable to obtain a connection to the NNTP server", e);
135 }
136
137
138
139 reader = new BufferedReader(new InputStreamReader(inputStream));
140 writer = new PrintWriter(new BufferedOutputStream(outputStream));
141 }
142
143
144
145
146
147
148
149
150 public void close() throws MessagingException {
151
152 if (socket == null) {
153 return;
154 }
155 try {
156
157 sendQuit();
158 } finally {
159
160
161
162 closeServerConnection();
163
164 reader = null;
165 writer = null;
166 }
167 }
168
169 public String toString() {
170 return "NNTPConnection host: " + serverHost + " port: " + serverPort;
171 }
172
173
174
175
176
177 public void getWelcome() throws MessagingException {
178 NNTPReply line = getReply();
179
180
181 if (line.isError()) {
182 throw new MessagingException("Error connecting to news server: " + line.getMessage());
183 }
184
185
186 if (line.getCode() == NNTPReply.POSTING_ALLOWED) {
187 postingAllowed = true;
188 } else {
189 postingAllowed = false;
190 }
191
192
193 welcomeString = line.getMessage();
194
195
196 getExtensions();
197 }
198
199
200
201
202
203 public void sendQuit() throws MessagingException {
204 sendLine("QUIT");
205 }
206
207
208
209
210
211
212
213
214
215
216 public NNTPReply selectGroup(String name) throws MessagingException {
217
218 return sendCommand("GROUP " + name);
219 }
220
221
222
223
224
225
226
227
228 protected void getExtensions() throws MessagingException {
229 NNTPReply reply = sendCommand("LIST EXTENSIONS", NNTPReply.EXTENSIONS_SUPPORTED);
230
231
232
233
234 if (reply.getCode() != NNTPReply.EXTENSIONS_SUPPORTED) {
235 return;
236 }
237
238
239 capabilities = new HashMap();
240 authentications = new ArrayList();
241
242
243 List extensions = reply.getData();
244
245
246 for (int i = 0; i < extensions.size(); i++) {
247
248 processExtension((String) extensions.get(i));
249 }
250 }
251
252
253
254
255
256
257
258
259
260 protected void processExtension(String extension) {
261 String extensionName = extension.toUpperCase();
262 String argument = "";
263
264 int delimiter = extension.indexOf(' ');
265
266
267 if (delimiter != -1) {
268 extensionName = extension.substring(0, delimiter).toUpperCase();
269 argument = extension.substring(delimiter + 1);
270 }
271
272
273 capabilities.put(extensionName, argument);
274
275
276 if (extensionName.equals("AUTHINFO")) {
277 StringTokenizer tokenizer = new StringTokenizer(argument);
278
279 while (tokenizer.hasMoreTokens()) {
280
281 String mechanism = tokenizer.nextToken().toUpperCase();
282 if (mechanism.equals("SASL")) {
283 authInfoSaslAllowed = true;
284 }
285 else if (mechanism.equals("USER")) {
286 authInfoUserAllowed = true;
287 }
288 }
289 }
290
291 else if (extensionName.equals("SASL")) {
292
293 StringTokenizer tokenizer = new StringTokenizer(argument);
294
295 while (tokenizer.hasMoreTokens()) {
296 String mechanism = tokenizer.nextToken().toUpperCase();
297 authentications.add(mechanism);
298 }
299 }
300 }
301
302
303
304
305
306
307
308
309
310
311
312
313
314 public String extensionParameter(String name) {
315 if (capabilities != null) {
316 return (String) capabilities.get(name);
317 }
318 return null;
319 }
320
321
322
323
324
325
326
327
328
329
330
331 public boolean supportsExtension(String name) {
332
333 return extensionParameter(name) != null;
334 }
335
336
337
338
339
340
341
342 public synchronized void sendPost(Message msg) throws MessagingException {
343
344
345 NNTPReply line = sendCommand("POST");
346
347 if (line.getCode() != NNTPReply.SEND_ARTICLE) {
348 throw new MessagingException("Server rejected POST command: " + line);
349 }
350
351
352
353 try {
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
369
370 msg.writeTo(mimeOut);
371
372
373 mimeOut.writeSMTPTerminator();
374
375 mimeOut.flush();
376 } catch (IOException e) {
377 throw new MessagingException("I/O error posting message", e);
378 } catch (MessagingException e) {
379 throw new MessagingException("Exception posting message", e);
380 }
381
382
383
384 line = new NNTPReply(receiveLine());
385
386 if (line.getCode() != NNTPReply.POSTED_OK) {
387 throw new MessagingException("Server rejected POST command: " + line);
388 }
389 }
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 public synchronized NNTPReply sendCommand(String command, int success) throws MessagingException {
405 NNTPReply reply = sendCommand(command);
406 if (reply.getCode() == success) {
407 reply.retrieveData(reader);
408 }
409 return reply;
410 }
411
412
413
414
415
416
417
418
419
420
421
422 public NNTPReply sendCommand(String data) throws MessagingException {
423 sendLine(data);
424 NNTPReply reply = getReply();
425
426
427
428
429 if (reply.getCode() == NNTPReply.AUTHINFO_REQUIRED || reply.getCode() == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
430 debugOut("Authentication required received from server.");
431
432 processAuthentication(reply.getCode());
433
434
435 sendLine(data);
436 reply = getReply();
437 }
438 return reply;
439 }
440
441
442
443
444
445
446
447
448
449
450
451 public NNTPReply sendAuthCommand(String data) throws MessagingException {
452 sendLine(data);
453 return getReply();
454 }
455
456
457
458
459 public void sendLine(String data) throws MessagingException {
460 if (socket == null || !socket.isConnected()) {
461 throw new MessagingException("no connection");
462 }
463 try {
464 outputStream.write(data.getBytes());
465 outputStream.write(CR);
466 outputStream.write(LF);
467 outputStream.flush();
468 } catch (IOException e) {
469 throw new MessagingException(e.toString());
470 }
471 }
472
473
474
475
476
477
478 public NNTPReply getReply() throws MessagingException {
479 lastServerResponse = new NNTPReply(receiveLine());
480 return lastServerResponse;
481 }
482
483
484
485
486
487
488
489 public String getLastServerResponse() {
490 if (lastServerResponse == null) {
491 return "";
492 }
493 return lastServerResponse.getReply();
494 }
495
496
497
498
499
500
501
502 public String receiveLine() throws MessagingException {
503 if (socket == null || !socket.isConnected()) {
504 throw new MessagingException("no connection");
505 }
506
507 try {
508 String line = reader.readLine();
509 if (line == null) {
510 throw new MessagingException("Unexpected end of stream");
511 }
512 return line;
513 } catch (IOException e) {
514 throw new MessagingException("Error reading from server", e);
515 }
516 }
517
518
519
520
521
522 protected void processAuthentication(int request) throws MessagingException {
523
524
525
526 if (username == null || password == null) {
527 throw new MessagingException("Server requires user authentication");
528 }
529
530 if (request == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
531 processAuthinfoSimple();
532 } else {
533 if (!processSaslAuthentication()) {
534 processAuthinfoUser();
535 }
536 }
537 }
538
539
540
541
542
543
544
545 protected void processAuthinfoSimple() throws MessagingException {
546 NNTPReply reply = sendAuthCommand("AUTHINFO SIMPLE");
547 if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
548 throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
549 }
550 reply = sendAuthCommand(username + " " + password);
551 if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
552 throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
553 }
554 }
555
556
557
558
559
560
561
562
563
564 protected boolean processSaslAuthentication() throws MessagingException {
565
566 if (!authInfoSaslAllowed) {
567 return false;
568 }
569
570 ClientAuthenticator authenticator = getSaslAuthenticator();
571 if (authenticator == null) {
572 throw new MessagingException("Unable to obtain SASL authenticator");
573 }
574
575
576 return processLogin(authenticator);
577 }
578
579
580
581
582
583
584
585
586 protected ClientAuthenticator getSaslAuthenticator() {
587 return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
588 }
589
590
591
592
593
594
595
596
597
598
599
600
601 protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
602 debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
603
604
605
606 if (authenticator.hasInitialResponse()) {
607 StringBuffer command = new StringBuffer();
608
609 command.append("AUTHINFO SASL ");
610
611 command.append(authenticator.getMechanismName());
612 command.append(" ");
613
614 command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
615
616 sendLine(command.toString());
617 }
618
619 else {
620 StringBuffer command = new StringBuffer();
621
622 command.append("AUTHINFO SASL");
623
624 command.append(authenticator.getMechanismName());
625
626 sendLine(command.toString());
627 }
628
629
630
631
632 while (true) {
633
634 NNTPReply line = getReply();
635
636
637
638 if (line.getCode() == NNTPReply.AUTHINFO_ACCEPTED || line.getCode() == NNTPReply.AUTHINFO_ACCEPTED_FINAL) {
639 debugOut("Successful SMTP authentication");
640 return true;
641 }
642
643 else if (line.getCode() == NNTPReply.AUTHINFO_CHALLENGE) {
644
645
646
647 if (authenticator.isComplete()) {
648 debugOut("Extra authentication challenge " + line);
649 return false;
650 }
651
652
653 byte[] challenge = Base64.decode(line.getMessage().getBytes());
654
655
656
657 sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
658 }
659
660
661
662 else {
663 debugOut("Authentication failure " + line);
664 return false;
665 }
666 }
667 }
668
669
670
671
672
673
674
675
676 protected void processAuthinfoUser() throws MessagingException {
677
678 if (!authInfoUserAllowed) {
679 return;
680 }
681 NNTPReply reply = sendAuthCommand("AUTHINFO USER " + username);
682
683 if (reply.getCode() == NNTPReply.AUTHINFO_ACCEPTED) {
684 return;
685 }
686
687 if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
688 throw new MessagingException("Error authenticating with server using AUTHINFO USER: " + reply);
689 }
690
691 reply = sendAuthCommand("AUTHINFO PASS " + password);
692 if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
693 throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
694 }
695 }
696
697
698
699
700
701
702
703
704 public boolean isPostingAllowed() {
705 return postingAllowed;
706 }
707
708
709
710
711
712
713 public String getWelcomeString() {
714 return welcomeString;
715 }
716 }