/** * Read a list of space-separated "flag_extension" sequences and return the list as a array of * Strings. An empty list is returned as null. This is an IMAP-ism, and perhaps this method should * moved into the IMAP layer. */ public String[] readSimpleList() { skipSpaces(); if (buffer[index] != '(') // not what we expected return null; index++; // skip '(' Vector v = new Vector(); int start; for (start = index; buffer[index] != ')'; index++) { if (buffer[index] == ' ') { // got one item v.addElement(ASCIIUtility.toString(buffer, start, index)); start = index + 1; // index gets incremented at the top } } if (index > start) // get the last item v.addElement(ASCIIUtility.toString(buffer, start, index)); index++; // skip ')' int size = v.size(); if (size > 0) { String[] s = new String[size]; v.copyInto(s); return s; } else // empty list return null; }
/** * Generic parsing routine that can parse out a Quoted-String, Literal or Atom and return the * parsed token as a String or a ByteArray. Errors or NIL data will return null. */ private Object parseString(boolean parseAtoms, boolean returnString) { byte b; // Skip leading spaces skipSpaces(); b = buffer[index]; if (b == '"') { // QuotedString index++; // skip the quote int start = index; int copyto = index; while ((b = buffer[index]) != '"') { if (b == '\\') // skip escaped byte index++; if (index != copyto) { // only copy if we need to // Beware: this is a destructive copy. I'm // pretty sure this is OK, but ... ;> buffer[copyto] = buffer[index]; } copyto++; index++; } index++; // skip past the terminating quote if (returnString) return ASCIIUtility.toString(buffer, start, copyto); else return new ByteArray(buffer, start, copyto - start); } else if (b == '{') { // Literal int start = ++index; // note the start position while (buffer[index] != '}') index++; int count = 0; try { count = ASCIIUtility.parseInt(buffer, start, index); } catch (NumberFormatException nex) { // throw new ParsingException(); return null; } start = index + 3; // skip "}\r\n" index = start + count; // position index to beyond the literal if (returnString) // return as String return ASCIIUtility.toString(buffer, start, start + count); else return new ByteArray(buffer, start, count); } else if (parseAtoms) { // parse as an ATOM int start = index; // track this, so that we can use to // creating ByteArrayInputStream below. String s = readAtom(); if (returnString) return s; else // *very* unlikely return new ByteArray(buffer, start, index); } else if (b == 'N' || b == 'n') { // the only valid value is 'NIL' index += 3; // skip past NIL return null; } return null; // Error }
/** * Extract an ATOM, but stop at the additional delimiter (if not NUL). Used to parse a response * code inside []. */ public String readAtom(char delim) { skipSpaces(); if (index >= size) // already at end of response return null; /* * An ATOM is any CHAR delimited by : * SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\' */ byte b; int start = index; while (index < size && ((b = buffer[index]) > ' ') && b != '(' && b != ')' && b != '%' && b != '*' && b != '"' && b != '\\' && b != 0x7f && (delim == '\0' || b != delim)) index++; return ASCIIUtility.toString(buffer, start, index); }
/** * Read a string as an arbitrary sequence of characters, stopping at the delimiter Used to read * part of a response code inside []. */ public String readString(char delim) { skipSpaces(); if (index >= size) // already at end of response return null; int start = index; while (index < size && buffer[index] != delim) index++; return ASCIIUtility.toString(buffer, start, index); }
/** * Read a list of space-separated "flag_extension" sequences and return the list as a array of * Strings. An empty list is returned as null. This is an IMAP-ism, and perhaps this method should * moved into the IMAP layer. */ public String[] readSimpleList() { skipSpaces(); if (buffer[index] != '(') // not what we expected return null; index++; // skip '(' List<String> v = new ArrayList<String>(); int start; for (start = index; buffer[index] != ')'; index++) { if (buffer[index] == ' ') { // got one item v.add(ASCIIUtility.toString(buffer, start, index)); start = index + 1; // index gets incremented at the top } } if (index > start) // get the last item v.add(ASCIIUtility.toString(buffer, start, index)); index++; // skip ')' int size = v.size(); if (size > 0) return v.toArray(new String[size]); else // empty list return null; }
/** * Extract a long number, starting at the current position. Updates the internal index to beyond * the number. Returns -1 if a long number was not found. * * @return a long */ public long readLong() { // Skip leading spaces skipSpaces(); int start = index; while (index < size && Character.isDigit((char) buffer[index])) index++; if (index > start) { try { return ASCIIUtility.parseLong(buffer, start, index); } catch (NumberFormatException nex) { } } return -1; }
public Response(String s) { buffer = ASCIIUtility.getBytes(s); size = buffer.length; parse(); }
public String toString() { return ASCIIUtility.toString(buffer, 0, size); }
/** * Return the rest of the response as a string, usually used to return the arbitrary message text * after a NO response. */ public String getRest() { skipSpaces(); return ASCIIUtility.toString(buffer, index, size); }
public boolean authenticate( String[] mechs, final String realm, final String authzid, final String u, final String p) throws ProtocolException { synchronized (pr) { // authenticate method should be synchronized List<Response> v = new ArrayList<Response>(); String tag = null; Response r = null; boolean done = false; if (logger.isLoggable(Level.FINE)) { logger.fine("SASL Mechanisms:"); for (int i = 0; i < mechs.length; i++) logger.fine(" " + mechs[i]); logger.fine(""); } SaslClient sc; CallbackHandler cbh = new CallbackHandler() { public void handle(Callback[] callbacks) { if (logger.isLoggable(Level.FINE)) logger.fine("SASL callback length: " + callbacks.length); for (int i = 0; i < callbacks.length; i++) { if (logger.isLoggable(Level.FINE)) logger.fine("SASL callback " + i + ": " + callbacks[i]); if (callbacks[i] instanceof NameCallback) { NameCallback ncb = (NameCallback) callbacks[i]; ncb.setName(u); } else if (callbacks[i] instanceof PasswordCallback) { PasswordCallback pcb = (PasswordCallback) callbacks[i]; pcb.setPassword(p.toCharArray()); } else if (callbacks[i] instanceof RealmCallback) { RealmCallback rcb = (RealmCallback) callbacks[i]; rcb.setText(realm != null ? realm : rcb.getDefaultText()); } else if (callbacks[i] instanceof RealmChoiceCallback) { RealmChoiceCallback rcb = (RealmChoiceCallback) callbacks[i]; if (realm == null) rcb.setSelectedIndex(rcb.getDefaultChoice()); else { // need to find specified realm in list String[] choices = rcb.getChoices(); for (int k = 0; k < choices.length; k++) { if (choices[k].equals(realm)) { rcb.setSelectedIndex(k); break; } } } } } } }; try { sc = Sasl.createSaslClient(mechs, authzid, name, host, (Map) props, cbh); } catch (SaslException sex) { logger.log(Level.FINE, "Failed to create SASL client", sex); throw new UnsupportedOperationException(sex.getMessage(), sex); } if (sc == null) { logger.fine("No SASL support"); throw new UnsupportedOperationException("No SASL support"); } if (logger.isLoggable(Level.FINE)) logger.fine("SASL client " + sc.getMechanismName()); try { Argument args = new Argument(); args.writeAtom(sc.getMechanismName()); if (pr.hasCapability("SASL-IR") && sc.hasInitialResponse()) { String irs; byte[] ba = sc.evaluateChallenge(new byte[0]); if (ba.length > 0) { ba = BASE64EncoderStream.encode(ba); irs = ASCIIUtility.toString(ba, 0, ba.length); } else irs = "="; args.writeAtom(irs); } tag = pr.writeCommand("AUTHENTICATE", args); } catch (Exception ex) { logger.log(Level.FINE, "SASL AUTHENTICATE Exception", ex); return false; } OutputStream os = pr.getIMAPOutputStream(); // stream to IMAP server /* * Wrap a BASE64Encoder around a ByteArrayOutputstream * to craft b64 encoded username and password strings * * Note that the encoded bytes should be sent "as-is" to the * server, *not* as literals or quoted-strings. * * Also note that unlike the B64 definition in MIME, CRLFs * should *not* be inserted during the encoding process. So, I * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine, * which should be sufficiently large ! */ ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] CRLF = {(byte) '\r', (byte) '\n'}; // Hack for Novell GroupWise XGWTRUSTEDAPP authentication mechanism // http://www.novell.com/developer/documentation/gwimap/? // page=/developer/documentation/gwimap/gwimpenu/data/al7te9j.html boolean isXGWTRUSTEDAPP = sc.getMechanismName().equals("XGWTRUSTEDAPP") && PropUtil.getBooleanProperty( props, "mail." + name + ".sasl.xgwtrustedapphack.enable", true); while (!done) { // loop till we are done try { r = pr.readResponse(); if (r.isContinuation()) { byte[] ba = null; if (!sc.isComplete()) { ba = r.readByteArray().getNewBytes(); if (ba.length > 0) ba = BASE64DecoderStream.decode(ba); if (logger.isLoggable(Level.FINE)) logger.fine("SASL challenge: " + ASCIIUtility.toString(ba, 0, ba.length) + " :"); ba = sc.evaluateChallenge(ba); } if (ba == null) { logger.fine("SASL no response"); os.write(CRLF); // write out empty line os.flush(); // flush the stream bos.reset(); // reset buffer } else { if (logger.isLoggable(Level.FINE)) logger.fine("SASL response: " + ASCIIUtility.toString(ba, 0, ba.length) + " :"); ba = BASE64EncoderStream.encode(ba); if (isXGWTRUSTEDAPP) bos.write(ASCIIUtility.getBytes("XGWTRUSTEDAPP ")); bos.write(ba); bos.write(CRLF); // CRLF termination os.write(bos.toByteArray()); // write out line os.flush(); // flush the stream bos.reset(); // reset buffer } } else if (r.isTagged() && r.getTag().equals(tag)) // Ah, our tagged response done = true; else if (r.isBYE()) // outta here done = true; else // hmm .. unsolicited response here ?! v.add(r); } catch (Exception ioex) { logger.log(Level.FINE, "SASL Exception", ioex); // convert this into a BYE response r = Response.byeResponse(ioex); done = true; // XXX - ultimately return true??? } } if (sc.isComplete() /*&& res.status == SUCCESS*/) { String qop = (String) sc.getNegotiatedProperty(Sasl.QOP); if (qop != null && (qop.equalsIgnoreCase("auth-int") || qop.equalsIgnoreCase("auth-conf"))) { // XXX - NOT SUPPORTED!!! logger.fine("SASL Mechanism requires integrity or confidentiality"); return false; } } /* Dispatch untagged responses. * NOTE: in our current upper level IMAP classes, we add the * responseHandler to the Protocol object only *after* the * connection has been authenticated. So, for now, the below * code really ends up being just a no-op. */ Response[] responses = v.toArray(new Response[v.size()]); pr.notifyResponseHandlers(responses); // Handle the final OK, NO, BAD or BYE response pr.handleResult(r); pr.setCapabilities(r); /* * If we're using the Novell Groupwise XGWTRUSTEDAPP mechanism * to run as a specified authorization ID, we have to issue a * LOGIN command to select the user we want to operate as. */ if (isXGWTRUSTEDAPP && authzid != null) { Argument args = new Argument(); args.writeString(authzid); responses = pr.command("LOGIN", args); // dispatch untagged responses pr.notifyResponseHandlers(responses); // Handle result of this command pr.handleResult(responses[responses.length - 1]); // If the response includes a CAPABILITY response code, process it pr.setCapabilities(responses[responses.length - 1]); } return true; } }
/** Return the next 20 characters in the buffer, for exception messages. */ private String next20() { if (index + 20 > size) return ASCIIUtility.toString(buffer, index, index + size); else return ASCIIUtility.toString(buffer, index, index + 20) + "..."; }