KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jsmtpd > core > receive > ProtocolHandler


1 /*
2  *
3  * Jsmtpd, Java SMTP daemon
4  * Copyright (C) 2005 Jean-Francois POUX, jf.poux@laposte.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  */

21 package org.jsmtpd.core.receive;
22
23 import java.io.BufferedWriter JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.OutputStreamWriter JavaDoc;
26 import java.net.InetSocketAddress JavaDoc;
27 import java.net.Socket JavaDoc;
28 import java.util.Date JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.LinkedList JavaDoc;
31 import java.util.List JavaDoc;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.jsmtpd.config.ReadConfig;
36 import org.jsmtpd.core.common.PluginStore;
37 import org.jsmtpd.core.common.acl.IACL;
38 import org.jsmtpd.core.common.io.BareLFException;
39 import org.jsmtpd.core.common.io.InputSizeToBig;
40 import org.jsmtpd.core.common.io.InvalidStreamParserInitialisation;
41 import org.jsmtpd.core.common.io.commandStream.CommandStreamParser;
42 import org.jsmtpd.core.common.io.dataStream.DataStreamParser;
43 import org.jsmtpd.core.common.smtpExtension.IProtocolHandler;
44 import org.jsmtpd.core.common.smtpExtension.ISmtpExtension;
45 import org.jsmtpd.core.common.smtpExtension.SmtpExtensionException;
46 import org.jsmtpd.core.mail.Email;
47 import org.jsmtpd.core.mail.EmailAddress;
48 import org.jsmtpd.core.mail.InvalidAddress;
49 import org.jsmtpd.core.mail.Rcpt;
50 import org.jsmtpd.core.send.QueueService;
51 import org.jsmtpd.tools.DateUtil;
52
53 /**
54  * @author Jean-Francois POUX
55  * Jsmtp<BR>
56  * 02/04/2005<br>
57  * Problems with some mailing list managers, when trying to suscribe, so :<br>
58  * Increased limit of user size to 512o<br>
59  * Increased limit of domain size to 512o<br>
60  *
61  *
62  * 16/03/2005 <br>
63  * Fixed a bugg that could cause the server to crash by consuming all its memory
64  * while parsing random ascii injection. (removed buffered reader) <br>
65  * Check for MAX RCPT and adresses sizes.
66  *
67  *
68  * Chat with smtp sender
69  * Checks acl
70  * Write message to delivery service
71  *
72  * <br>
73  * RFC 821 :
74  * user : 64 byte => add to emailaddress parser [done]
75  * domain : 64 byte => add to emailaddress parser [done]
76  * route : 256 byte, route NA in this implem => err 501
77  * command: 512 byte max => err 500 [done]
78  * response: 512 byte max OK
79  * dataline: 1000 max no problem
80  * max rcpt: 100 => err 552s [done]
81  * <br><br>
82  * 7/3/2005<br>
83  * Changed Email class, so changed the way to handle DATA cmd<br>
84  * raw byte reading<br>
85  *
86  */

87 public class ProtocolHandler implements IProtocolHandler {
88
89     /**
90      * The tcp socket where the exchange takes place
91      */

92     private Socket JavaDoc sock = null;
93
94     /**
95      * Writer, from the socket
96      */

97     private BufferedWriter JavaDoc wr = null;
98     /**
99      * Email instance to store data, rcpt & from mainly
100      */

101     private Email mail = new Email();
102
103     /**
104      * Maximum message size, in ko.
105      */

106     private int maxMessageSize = ReadConfig.getInstance().getMaxMessageSize();
107     /**
108      * Connection will fail after this timeout
109      */

110     private int timeout = ReadConfig.getInstance().getConnectionTimeout();
111     /**
112      * ACL pluggin to use to check if this mail is to be accepted or not
113      */

114     private IACL acl = PluginStore.getInstance().getAcl();
115
116     private Log log = LogFactory.getLog(ProtocolHandler.class);
117     /**
118      * Remote ip of the client connected to the smtp session
119      */

120     private String JavaDoc remote = "";
121     /**
122      * Our hostname
123      */

124     private String JavaDoc localHost = ReadConfig.getInstance().getLocalDomain();
125
126     /**
127      * Once a mail is received and validated, it is placed in this service
128      * that will deliver it
129      */

130     private QueueService dsvc = QueueService.getInstance();
131
132     /**
133      * relay remote host ?
134      */

135     private boolean relayed=false;
136     
137     /**
138      * is communication layer secured by any means ?
139      */

140     private boolean secured=false;
141     
142     private CommandStreamParser csp;
143     
144     private InputIPFilterChecker checker = new InputIPFilterChecker();
145     
146     private List JavaDoc<ISmtpExtension> smtpExtensions=PluginStore.getInstance().getSmtpExtensions();
147     
148     private List JavaDoc<String JavaDoc> commandHistory;
149     /**
150      * Listening thread queries the thread pool to receive an instance of this class,
151      * then passes it the socket resulting from the accept method
152      * This methods invoques the chat protocol with the client.
153      * @param sock the socket to chat with the connected client
154      */

155     
156     private String JavaDoc authContext = null;
157     
158     private int maxRcpt =ReadConfig.getInstance().getMaxRcpt();
159     
160     public void init(Socket JavaDoc sock) {
161         commandHistory=new LinkedList JavaDoc<String JavaDoc>();
162         remote = ((InetSocketAddress JavaDoc) sock.getRemoteSocketAddress()).getAddress().getHostAddress(); // get client hostname
163

164         //Ensure any previous state is cleared
165
closeConnection();
166         reset();
167         this.sock = sock;
168         
169         try {
170             sock.setSoTimeout(timeout * 1000); // Timeout comes from the config file
171
wr = new BufferedWriter JavaDoc(new OutputStreamWriter JavaDoc(sock.getOutputStream()));
172             // Run the chat with the client
173
runDialog();
174         } catch (IOException JavaDoc e) {
175             log.error("Network error for " + remote);
176         } catch (EndofProtocol e) {
177             log.info("End service for " + remote);
178         } finally {
179             closeConnection();
180             reset();
181         }
182     }
183
184     /**
185      * Chats with the client
186      * @throws IOException Network error
187      * @throws EndofProtocol Client is disconnecting
188      */

189     private void runDialog() throws IOException JavaDoc, EndofProtocol {
190         // Count errors, if it exceeds a threshold, the connection is droped because someone
191
// is probably trying to do something not allowed
192
int errors = 0;
193         // The last command issued
194
int lastCommand = -1;
195         // Current command
196
int command;
197
198         if (!checker.checkIPAgainstFilters(sock.getInetAddress())) {
199             send(MSG_BLACKLISTED);
200             return;
201         }
202
203         log.info( "Service for " + remote + " running");
204         send(MSG_HELLO_CODE + localHost + MSG_HELLO_MESG); // Send the : HELO serverHostname.com Welcome to jsmtpd
205
mail.setReceivedFrom(remote); // Email type records where the connection came from (for filtering later)
206

207         csp = new CommandStreamParser(sock.getInputStream(), 4096, true);
208         String JavaDoc commandString;
209         try {
210             while ((commandString = csp.readLine()) != null)
211             {
212                 command = getCommand(commandString);
213                 log.debug("Command: " + commandString);
214                 try {
215                     if (executePreExtensions(commandString,this))
216                         continue;
217                 } catch (SmtpExtensionException e1) {
218                    log.error("Failed to execute smtp extension");
219                    break;
220                 }
221                 switch (command) {
222                 case CMD_EHLO:
223                     if (smtpExtensions.size()<=0) {
224                         send("250 Command ok");
225                     } else {
226                         send("250-ok");
227                         for (ISmtpExtension ext : smtpExtensions) {
228                             if ( (ext.getWelcome()!=null) && (!ext.getWelcome().equals("")))
229                                 send ("250-"+ext.getWelcome());
230                         }
231                         send ("250 HELP");
232                     }
233                     lastCommand = CMD_HELLO;
234                     break;
235                 case CMD_HELLO:
236                     send("250 Command ok");
237                     lastCommand = CMD_HELLO;
238                     break;
239                 case CMD_QUIT:
240                     lastCommand = CMD_RESET;
241                     throw new EndofProtocol();
242                 case CMD_NOOP:
243                     send(MSG_OK);
244                     break;
245                 case CMD_RESET:
246                     mail = new Email();
247                     mail.setReceivedFrom(remote);
248                     mail.setAuthContext(authContext); // If we have a auth context, transmit it;
249
send(MSG_OK);
250                     lastCommand = CMD_RESET;
251                     break;
252                 case CMD_MAIL_FROM:
253                     if (lastCommand == CMD_RESET || lastCommand == CMD_HELLO) {
254                         if (decodeFrom(commandString))
255                             lastCommand = CMD_MAIL_FROM;
256                     } else
257                         send(MSG_CMD_NOT_ALLOWED);
258                     break;
259
260                 case CMD_RCPT:
261                     if (lastCommand == CMD_MAIL_FROM || lastCommand == CMD_RCPT) {
262                         if (decodeRcpt(commandString))
263                             lastCommand = CMD_RCPT;
264                     } else {
265                         send(MSG_CMD_NOT_ALLOWED);
266                     }
267                     break;
268                     
269                 case CMD_HELP:
270                         send ("214 See rfc 2821");
271                         lastCommand=CMD_HELP;
272                     break;
273                     
274                 case CMD_DATA:
275                     if (lastCommand == CMD_RCPT) {
276                         parseData();
277                         mail = new Email();
278                         mail.setReceivedFrom(remote);
279                         mail.setAuthContext(authContext); // transmit auth ctx
280
lastCommand = CMD_RESET;
281                     } else {
282                         send(MSG_CMD_NOT_ALLOWED);
283                     }
284                     break;
285                     
286                 default:
287                     try {
288                             if (!executeExtensions(commandString,this)) {
289                                 send(MSG_INVALID_CMD);
290                                 log.error("Invalid command: " + commandString + " from " + remote);
291                                 errors++;
292                                 if (errors > 10)
293                                     return;
294                                 break;
295                             }
296                         } catch (IOException JavaDoc e) {
297                             throw new EndofProtocol();
298                         } catch (SmtpExtensionException e) {
299                             log.error("Error executing SMTP Extensions");
300                             errors++;
301                         }
302                 }
303             }
304
305         } catch (InputSizeToBig e) {
306             send(MSG_CMD_TO_BIG);
307             return;
308         } catch (BareLFException e) {
309             send(MSG_ERROR_LF);
310             return;
311         }
312     }
313
314     private boolean executeExtensions (String JavaDoc cmd, IProtocolHandler protocol) throws SmtpExtensionException, IOException JavaDoc, InputSizeToBig, BareLFException {
315         for (ISmtpExtension extension : smtpExtensions) {
316             if (extension.smtpTrigger(cmd,protocol))
317                 return true;
318         }
319         return false;
320     }
321     
322     private boolean executePreExtensions (String JavaDoc cmd, IProtocolHandler protocol) throws SmtpExtensionException, IOException JavaDoc, InputSizeToBig, BareLFException {
323         boolean over=false;
324         for (ISmtpExtension extension : smtpExtensions) {
325             if (extension.smtpPreTrigger(cmd,protocol))
326                 over=true;
327         }
328         return over;
329     }
330     
331     private void parseData() throws IOException JavaDoc {
332         send(MSG_DATA_BEGIN);
333         mail.setArrival(new Date JavaDoc());
334
335         try {
336             DataStreamParser dsp = new DataStreamParser(1024 * 512, 1024 * 1024 * maxMessageSize);
337             List JavaDoc<Rcpt> rcpts = mail.getRcpt();
338             String JavaDoc rc = "";
339             for (Iterator JavaDoc iter = rcpts.iterator(); iter.hasNext();) {
340                 Rcpt element = (Rcpt) iter.next();
341                 rc += "<" + element.getEmailAddress().toString() + ">;";
342             }
343             dsp.appendString("Received: from " + remote + " by " + localHost + " (Jsmtpd) for " + rc + " " + DateUtil.currentRFCDate());
344
345             try {
346                 dsp.parseInputStream(sock.getInputStream());
347                 mail.setDataBuffer(dsp.getData());
348                 if (!dsvc.queueMail(mail))
349                     send(MSG_NO_SPACE_LEFT);
350                 else
351                     send(MSG_OK);
352             } catch (InputSizeToBig e1) {
353                 send(MSG_ERROR_SIZE);
354             } catch (IOException JavaDoc e1) {
355                 throw e1;
356             } catch (BareLFException e1) {
357                 send(MSG_ERROR_LF);
358             }
359
360         } catch (InvalidStreamParserInitialisation e) {
361             send(MSG_SAVE_ERROR);
362         }
363
364     }
365
366     private boolean decodeRcpt(String JavaDoc rcpt) throws IOException JavaDoc {
367         if ((rcpt == null) || (rcpt.length() < 10))
368             return false;
369
370         String JavaDoc rc = rcpt.substring(9).trim().replace("<", "").replace(">", "");
371         EmailAddress e = null;
372         try {
373             e = EmailAddress.parseAddress(rc);
374
375         } catch (InvalidAddress e1) {
376             send(MSG_USER_INVALID);
377             return false;
378         }
379         if (isAcceptable(e)) {
380             // Todo : check user ?
381
if (mail.getRcpt().size() >= maxRcpt) {
382                 send(MSG_TO_MANY_RCPT);
383                 return false;
384             } else {
385                 mail.addRcpt(e);
386                 send(MSG_OK);
387                 return true;
388             }
389         } else {
390             send(MSG_USER_UNKOWN); // you are not for local domains and connection is not to be relayed.
391
return false;
392         }
393
394     }
395
396
397     
398     private boolean decodeFrom(String JavaDoc from) throws IOException JavaDoc {
399         if ((from == null) || (from.length() < 11))
400             return false;
401         String JavaDoc fr = from.substring(10).trim();
402         if (fr.contains("<>")) {
403             EmailAddress e = new EmailAddress();
404             e.setUser("<>");
405             mail.setFrom(e);
406             send(MSG_OK);
407             return true;
408         }
409
410         fr = fr.replace("<", "").replace(">", "");
411         EmailAddress e = null;
412         try {
413             e = EmailAddress.parseAddress(fr);
414             mail.setFrom(e);
415             send(MSG_OK);
416             return true;
417
418         } catch (InvalidAddress ia) {
419             send(MSG_USER_INVALID);
420             return false;
421         }
422
423     }
424
425     private boolean isAcceptable(EmailAddress addr) {
426         //user is authentified by external mecanism
427
if (relayed) {
428             log.debug("RCPT: " + addr + " is valid for relayed host :" + mail.getReceivedFrom()+" allowed by SMTP extension(s)");
429             return true;
430         }
431         
432         
433         // Mail is sent from our local domain
434
if (acl.isValidRelay(mail.getReceivedFrom())) {
435             log.debug("RCPT: " + addr + " is valid for relayed host :" + mail.getReceivedFrom());
436             return true;
437         }
438         //reject mails not from open relay and for localhost ?
439
if (addr.getHost().equals("localhost") || addr.getHost().equals("127.0.0.1")) {
440             log.debug("RCPT is not valid for " + mail.getReceivedFrom() + ", this is for localhost but sender is not to be relayed");
441             return false;
442         }
443
444         // Mail is not send from local domain, so we only accept messages for our domain(s).
445
if (acl.isValidAddress(addr)) {
446             log.debug("RCPT " + addr + " is valid (local domain)");
447             return true;
448         }
449         log.debug("RCPT is not valid for " + mail.getReceivedFrom() + ", this is not from relayed host and not for local domain");
450         return false;
451     }
452
453     private void closeConnection() {
454         if (wr != null) {
455             try {
456                 wr.write("221 Closing channel. Good Bye " + remote + "\r\n");
457                 wr.flush();
458                 log.debug("Closing connection ");
459             } catch (IOException JavaDoc e) {
460                 //error closing channel (sending 221)
461
}
462         }
463     }
464
465     private void reset() {
466         relayed=false;
467         secured=false;
468         mail = new Email();
469         commandHistory=new LinkedList JavaDoc<String JavaDoc>();
470         authContext=null;
471
472         if (wr != null) {
473             try {
474                 wr.close();
475                 wr = null;
476             } catch (IOException JavaDoc e) {
477                 e.printStackTrace();
478             }
479         }
480         if (sock != null) {
481             try {
482                 sock.close();
483                 sock = null;
484             } catch (IOException JavaDoc e) {
485                 e.printStackTrace();
486             }
487         }
488     }
489
490     private void send(String JavaDoc msg) throws IOException JavaDoc {
491         wr.write(msg + "\r\n");
492         wr.flush();
493         log.debug("Sent: " + msg);
494     }
495
496     private int getCommand(String JavaDoc input) {
497         if ((input == null) || (input.length() < 4))
498             return CMD_UNKW;
499         String JavaDoc tmp = input.substring(0, 4).toUpperCase();
500
501         if ("HELO".equals(tmp))
502             return CMD_HELLO;
503
504         if ("EHLO".equals(tmp))
505             return CMD_EHLO;
506         
507         if ("QUIT".equals(tmp))
508             return CMD_QUIT;
509
510         if ("MAIL".equals(tmp))
511             return CMD_MAIL_FROM;
512
513         if ("RCPT".equals(tmp))
514             return CMD_RCPT;
515
516         if ("DATA".equals(tmp))
517             return CMD_DATA;
518
519         if ("RSET".equals(tmp))
520             return CMD_RESET;
521
522         if ("NOOP".equals(tmp))
523             return CMD_NOOP;
524         
525         if ("HELP".equals(tmp))
526             return CMD_NOOP;
527         
528         return CMD_UNKW;
529     }
530     
531     public void setRelayed (boolean value) {
532         this.relayed=value;
533     }
534     public Socket JavaDoc getSock() {
535         return sock;
536     }
537     public void setSock(Socket JavaDoc sock) throws IOException JavaDoc {
538         this.sock = sock;
539         //When setting a socket (by a SMTP extension), rewire reader/writers objects
540
csp = new CommandStreamParser(sock.getInputStream(), 4096, true);
541         wr=new BufferedWriter JavaDoc(new OutputStreamWriter JavaDoc(sock.getOutputStream()));
542     }
543     public boolean isSecured() {
544         return secured;
545     }
546     public void setSecured(boolean secured) {
547         this.secured = secured;
548     }
549
550
551     public List JavaDoc getCommandHistory() {
552         return commandHistory;
553     }
554
555     public void addCommandHistory(String JavaDoc command) {
556         if (commandHistory.size()>50)
557             commandHistory.remove(commandHistory.size());
558         commandHistory.add(0,command);
559     }
560     
561     public void setAuthContext(String JavaDoc context) {
562         this.authContext=context;
563         mail.setAuthContext(context);
564     }
565     
566     private static final String JavaDoc MSG_HELLO_CODE = "220 ";
567     private static final String JavaDoc MSG_HELLO_MESG = " Welcome to Jsmtpd.";
568     private static final String JavaDoc MSG_OK = "250 Command OK";
569     private static final String JavaDoc MSG_CMD_NOT_ALLOWED = "503 Command not allowed";
570     private static final String JavaDoc MSG_USER_UNKOWN = "550 User does not exists, and you are not relayed";
571     private static final String JavaDoc MSG_USER_INVALID = "501 Address is not valid"; // was 451
572
private static final String JavaDoc MSG_DATA_BEGIN = "354 Listening for data input";
573     private static final String JavaDoc MSG_SAVE_ERROR = "500 Error processing message";
574     private static final String JavaDoc MSG_INVALID_CMD = "500 Invalid command";
575     private static final String JavaDoc MSG_ERROR_SIZE = "552 Message size is to big";
576     private static final String JavaDoc MSG_ERROR_LF = "551 Bare LF in data";
577     private static final String JavaDoc MSG_CMD_TO_BIG = "500 Line size is to big";
578     private static final String JavaDoc MSG_TO_MANY_RCPT = "552 To many RCPT";
579     private static final String JavaDoc MSG_BLACKLISTED = "421 Your IP is blacklisted"; //RFC responses expects S: 250 or E: 421. 5xx ?
580
private static final String JavaDoc MSG_NO_SPACE_LEFT = "552 No space left on storage";
581     
582     private static final int CMD_HELLO = 0;
583     private static final int CMD_EHLO = 10;
584     private static final int CMD_QUIT = 1;
585     private static final int CMD_MAIL_FROM = 2;
586     private static final int CMD_RCPT = 3;
587     private static final int CMD_DATA = 4;
588     private static final int CMD_RESET = 6;
589     private static final int CMD_NOOP = 7;
590     private static final int CMD_UNKW = -1;
591     private static final int CMD_HELP = 8;
592
593     public boolean isRelayed() {
594         return relayed;
595     }
596
597     
598     public Email getMail() {
599         return mail;
600     }
601     
602
603     
604 }
Popular Tags