1 21 package org.jsmtpd.plugins.smtpExtension; 22 23 import java.io.BufferedWriter ; 24 import java.io.IOException ; 25 import java.io.OutputStreamWriter ; 26 import java.util.Iterator ; 27 import java.util.List ; 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 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 login, byte[] password); 57 58 public boolean smtpTrigger(String command, IProtocolHandler protocol) throws SmtpExtensionException, InputSizeToBig, IOException , BareLFException { 59 60 BufferedWriter wr = new BufferedWriter (new OutputStreamWriter (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 tmp = command.substring(0, 4).toUpperCase(); 68 if (!tmp.equals("AUTH")) 69 return false; 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 [] 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 [] 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 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]); 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 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 (Base64Helper.decode(login)); 130 send(wr, MSG_AUTH_GO_ON + " UGFzc3dvcmQ6"); 131 String 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 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 getWelcome() { 158 return "AUTH PLAIN LOGIN"; 159 } 160 161 public String 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 wr, String message) throws IOException { 173 wr.write(message + "\r\n"); 174 wr.flush(); 175 log.debug("sent " + message + "<CR><LF>"); 176 } 177 178 private static final String MSG_INVALID_CMD = "500 Invalid auth command"; 179 private static final String MSG_REQUIRE_SEC_CHANNEL = "538 Encryption required for requested authentication mechanism"; 180 private static final String MSG_AUTH_OK = "235 OK Authenticated"; 181 private static final String MSG_AUTH_FAILED = "535 authentication failed"; 182 private static final String MSG_AUTH_GO_ON = "334"; 183 184 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 o = new Object (); 192 synchronized (o) { 193 try { 194 195 o.wait(5000); 196 } catch (InterruptedException e) { 197 } 198 } 199 } 200 201 private String [] parseAuthData(byte[] input) { 202 List res = ByteArrayTool.split(input, ByteArrayTool.NULL); 203 String [] ret = new String [res.size()]; 204 int i = 0; 205 for (Iterator iter = res.iterator(); iter.hasNext();) { 206 byte[] element = (byte[]) iter.next(); 207 ret[i] = new String (element); 208 i++; 209 } 210 return ret; 211 } 212 213 public boolean smtpPreTrigger(String command, IProtocolHandler protocol) throws SmtpExtensionException, IOException , InputSizeToBig, IOException , BareLFException { 214 return false; 215 } 216 } | Popular Tags |