KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jsmtpd > plugins > smtpExtension > SmtpAuthenticator


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.plugins.smtpExtension;
22
23 import java.io.BufferedWriter JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.OutputStreamWriter JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.jsmtpd.core.common.PluginInitException;
32 import org.jsmtpd.core.common.io.BareLFException;
33 import org.jsmtpd.core.common.io.InputSizeToBig;
34 import org.jsmtpd.core.common.io.commandStream.CommandStreamParser;
35 import org.jsmtpd.core.common.smtpExtension.IProtocolHandler;
36 import org.jsmtpd.core.common.smtpExtension.ISmtpExtension;
37 import org.jsmtpd.core.common.smtpExtension.SmtpExtensionException;
38 import org.jsmtpd.tools.Base64Helper;
39 import org.jsmtpd.tools.ByteArrayTool;
40
41 /**
42  * Provides a base class to extend to provide SMTP authentication
43  * Not fully RFC compliant at the momment
44  * TODO: Add support for cancel authentication
45  * TODO: Correctly reset states.
46  * TODO: When authentified, if client issues a RSET, the state is cleared. is it the correct behavior ?
47  * TODO: auth plain doest not works with 3 params as it should
48  * RFCS: http://www.fehcom.de/rfc/rfc2595.txt
49  * @author Jean-Francois POUX
50  */

51 public abstract class SmtpAuthenticator implements ISmtpExtension {
52
53     private boolean requireSecuredChannel = false;
54     private Log log = LogFactory.getLog(SmtpAuthenticator.class);
55
56     protected abstract boolean performAuth(String JavaDoc login, byte[] password);
57
58     public boolean smtpTrigger(String JavaDoc command, IProtocolHandler protocol) throws SmtpExtensionException, InputSizeToBig, IOException JavaDoc, BareLFException {
59
60         BufferedWriter JavaDoc wr = new BufferedWriter JavaDoc(new OutputStreamWriter JavaDoc(protocol.getSock().getOutputStream()));
61         CommandStreamParser csp = new CommandStreamParser(protocol.getSock().getInputStream(), 512, false);
62
63         if ((command == null) || (command.length() < 8)) {
64             return false;
65         }
66
67         String JavaDoc tmp = command.substring(0, 4).toUpperCase();
68         if (!tmp.equals("AUTH"))
69             return false; // cmd not for this plugin
70

71         if (requireSecuredChannel && (!protocol.isSecured())) {
72             send(wr, MSG_REQUIRE_SEC_CHANNEL);
73             log.error("client wants to auth, but is not on secured channel.");
74             throw new SmtpExtensionException();
75         }
76
77         String JavaDoc[] args = command.split(" ");
78         if ((args == null) || (args.length < 1)) {
79             send(wr, MSG_INVALID_CMD);
80             log.error("ESMTP:AUHT> Invalid or empty command");
81             throw new SmtpExtensionException();
82         }
83
84         if ("PLAIN".equals(args[1].toUpperCase())) {
85             if ((args.length < 2) || (args[2] == null)) {
86                 send(wr, MSG_INVALID_CMD);
87                 log.error("ESMTP:AUHT> Invalid or empty PLAIN command");
88                 throw new SmtpExtensionException();
89             }
90
91             byte[] lpData = Base64Helper.decode(args[2]);
92
93             String JavaDoc[] lp = parseAuthData(lpData);
94             if ((lp.length < 2) || (args[0] == null) || (args[1] == null)) {
95                 send(wr, MSG_INVALID_CMD);
96                 log.error("ESMTP:AUHT> Invalid or empty PLAIN command");
97                 throw new SmtpExtensionException();
98             }
99
100             // 2 kind of responses (2 or 3 params).
101
boolean authRes;
102             if (lp.length==2)
103                 authRes=performAuth(lp[0], lp[1].getBytes());
104             else
105                 authRes=performAuth(lp[1], lp[2].getBytes());
106             
107             if (authRes) {
108                 protocol.setRelayed(true);
109                 log.info("Remote host is now relayed (PLAIN authentication successfull)");
110                 protocol.setAuthContext(lp[1]); // who sent the authenticaded mail
111
send(wr, MSG_AUTH_OK);
112                 return true;
113             } else {
114                 log.info("Remote host authentication failed");
115                 send(wr, MSG_AUTH_FAILED);
116                 throw new SmtpExtensionException();
117             }
118         }
119
120         if ("LOGIN".equals((args[1]))) {
121             send(wr, MSG_AUTH_GO_ON + " VXNlcm5hbWU6");
122             String JavaDoc login = csp.readLine();
123             if (login == null) {
124                 log.info("LOGIN Remote host issued a null login");
125                 throttle();
126                 send(wr, MSG_INVALID_CMD);
127                 throw new SmtpExtensionException();
128             }
129             login = new String JavaDoc(Base64Helper.decode(login));
130             send(wr, MSG_AUTH_GO_ON + " UGFzc3dvcmQ6");
131             String JavaDoc pass = csp.readLine();
132             if (pass == null) {
133                 log.info("LOGIN Remote host issued a null password");
134                 throttle();
135                 send(wr, MSG_INVALID_CMD);
136                 throw new SmtpExtensionException();
137             }
138             byte[] pw = Base64Helper.decode(pass);
139             //pw = md5.digest(pw); // removed: let the underlying class decide what to do with it.
140
if (performAuth(login, pw)) {
141                 protocol.setRelayed(true);
142                 log.info("Remote host is now relayed (LOGIN authentication successfull)");
143                 protocol.setAuthContext(login);
144                 send(wr, MSG_AUTH_OK);
145                 return true;
146             } else {
147                 log.info("Remote host authentication failed");
148                 throttle();
149                 send(wr, MSG_AUTH_FAILED);
150                 throw new SmtpExtensionException();
151             }
152         }
153
154         throw new SmtpExtensionException();
155     }
156
157     public String JavaDoc getWelcome() {
158         return "AUTH PLAIN LOGIN";
159     }
160
161     public String JavaDoc getPluginName() {
162         return "PLAIN/LOGIN authentication SMTP Extension for Jsmtpd";
163     }
164
165     public void initPlugin() throws PluginInitException {
166     }
167
168     public void shutdownPlugin() {
169
170     }
171
172     private void send(BufferedWriter JavaDoc wr, String JavaDoc message) throws IOException JavaDoc {
173         wr.write(message + "\r\n");
174         wr.flush();
175         log.debug("sent " + message + "<CR><LF>");
176     }
177
178     private static final String JavaDoc MSG_INVALID_CMD = "500 Invalid auth command";
179     private static final String JavaDoc MSG_REQUIRE_SEC_CHANNEL = "538 Encryption required for requested authentication mechanism";
180     private static final String JavaDoc MSG_AUTH_OK = "235 OK Authenticated";
181     private static final String JavaDoc MSG_AUTH_FAILED = "535 authentication failed";
182     private static final String JavaDoc MSG_AUTH_GO_ON = "334";
183
184     // Autoconf
185
public void setRequireSecuredChannel(boolean requireSecuredChannel) {
186         this.requireSecuredChannel = requireSecuredChannel;
187     }
188
189     private void throttle() {
190         log.info("Waiting 5 secs before retry");
191         Object JavaDoc o = new Object JavaDoc();
192         synchronized (o) {
193             try {
194
195                 o.wait(5000);
196             } catch (InterruptedException JavaDoc e) {
197             }
198         }
199     }
200
201     private String JavaDoc[] parseAuthData(byte[] input) {
202         List JavaDoc res = ByteArrayTool.split(input, ByteArrayTool.NULL);
203         String JavaDoc[] ret = new String JavaDoc[res.size()];
204         int i = 0;
205         for (Iterator JavaDoc iter = res.iterator(); iter.hasNext();) {
206             byte[] element = (byte[]) iter.next();
207             ret[i] = new String JavaDoc(element);
208             i++;
209         }
210         return ret;
211     }
212
213     public boolean smtpPreTrigger(String JavaDoc command, IProtocolHandler protocol) throws SmtpExtensionException, IOException JavaDoc, InputSizeToBig, IOException JavaDoc, BareLFException {
214         return false;
215     }
216 }
Popular Tags