1919package com .mongodb ;
2020
2121import com .mongodb .util .ThreadUtil ;
22+ import com .mongodb .util .Util ;
2223import org .ietf .jgss .GSSCredential ;
2324import org .ietf .jgss .GSSException ;
2425import org .ietf .jgss .GSSManager ;
2526import org .ietf .jgss .GSSName ;
2627import org .ietf .jgss .Oid ;
2728
29+ import javax .security .auth .callback .Callback ;
30+ import javax .security .auth .callback .CallbackHandler ;
31+ import javax .security .auth .callback .NameCallback ;
32+ import javax .security .auth .callback .PasswordCallback ;
33+ import javax .security .auth .callback .UnsupportedCallbackException ;
2834import javax .security .sasl .Sasl ;
2935import javax .security .sasl .SaslClient ;
3036import javax .security .sasl .SaslException ;
3137import java .io .BufferedInputStream ;
38+ import java .io .ByteArrayOutputStream ;
3239import java .io .IOException ;
3340import java .io .InputStream ;
3441import java .io .OutputStream ;
@@ -308,10 +315,15 @@ protected void close(){
308315 }
309316
310317 void checkAuth (DB db ) throws IOException {
311- // TODO: add support for retry when credentials ticket times out
312318 if (db .getMongo ().getCredentials () != null ) {
313319 if (_saslAuthenticator == null ) {
314- _saslAuthenticator = new GSSAPIAuthenticator (db .getMongo ());
320+ if (db .getMongo ().getCredentials ().getMechanism ().equals (MongoClientCredentials .GSSAPI_MECHANISM )) {
321+ _saslAuthenticator = new GSSAPIAuthenticator (db .getMongo ());
322+ } else if (db .getMongo ().getCredentials ().getMechanism ().equals (MongoClientCredentials .CRAM_MD5_MECHANISM )) {
323+ _saslAuthenticator = new CRAMMD5Authenticator (db .getMongo ());
324+ } else {
325+ throw new MongoException ("Unsupported authentication mechanism: " + db .getMongo ().getCredentials ().getMechanism ());
326+ }
315327 }
316328 _saslAuthenticator .acquirePrivilegeForDatabase (db );
317329 }
@@ -376,16 +388,78 @@ class ActiveState {
376388 final String threadName ;
377389 }
378390
391+ class CRAMMD5Authenticator extends SaslAuthenticator {
392+ public static final String CRAM_MD5_MECHANISM = "CRAM-MD5" ;
393+
394+ CRAMMD5Authenticator (final Mongo mongo ) {
395+ super (mongo );
396+
397+ if (!mongo .getCredentials ().getMechanism ().equals (MongoClientCredentials .CRAM_MD5_MECHANISM )) {
398+ throw new MongoException ("Incorrect mechanism: " + mongo .getCredentials ().getMechanism ());
399+ }
400+ }
401+
402+ @ Override
403+ protected SaslClient createSaslClient () {
404+ try {
405+ return Sasl .createSaslClient (new String []{CRAM_MD5_MECHANISM }, mongo .getCredentials ().getUserName (), MONGODB_PROTOCOL ,
406+ serverAddress ().getHost (), null , new CredentialsHandlingCallbackHandler ());
407+ } catch (SaslException e ) {
408+ throw new MongoException ("Exception initializing SASL client" , e );
409+ }
410+ }
411+
412+ @ Override
413+ protected Object getMechanism () {
414+ return CRAM_MD5_MECHANISM ;
415+ }
416+
417+ class CredentialsHandlingCallbackHandler implements CallbackHandler {
418+
419+ public void handle (final Callback [] callbacks ) throws IOException , UnsupportedCallbackException {
420+ for (Callback callback : callbacks ) {
421+ if (callback instanceof NameCallback ) {
422+ NameCallback nameCallback = (NameCallback ) callback ;
423+ nameCallback .setName (mongo .getCredentials ().getDatabase () + "$" + mongo .getCredentials ().getUserName ());
424+ }
425+ if (callback instanceof PasswordCallback ) {
426+ PasswordCallback passwordCallback = (PasswordCallback ) callback ;
427+ String hashedPassword = new String (createHash (mongo .getCredentials ().getUserName (),
428+ mongo .getCredentials ().getPassword ()));
429+ passwordCallback .setPassword (hashedPassword .toCharArray ());
430+ }
431+ }
432+ }
433+ }
434+ // TODO: copoied from DB.AuthenticationCredentials
435+ byte [] createHash ( String userName , char [] password ){
436+ ByteArrayOutputStream bout = new ByteArrayOutputStream ( userName .length () + 20 + password .length );
437+ try {
438+ bout .write (userName .getBytes ());
439+ bout .write ( ":mongo:" .getBytes () );
440+ for (final char ch : password ) {
441+ if (ch >= 128 )
442+ throw new IllegalArgumentException ("can't handle non-ascii passwords yet" );
443+ bout .write ((byte ) ch );
444+ }
445+ }
446+ catch ( IOException ioe ){
447+ throw new RuntimeException ( "impossible" , ioe );
448+ }
449+ return Util .hexMD5 (bout .toByteArray ()).getBytes ();
450+ }
451+
452+ }
453+
379454 class GSSAPIAuthenticator extends SaslAuthenticator {
380455 public static final String GSSAPI_OID = "1.2.840.113554.1.2.2" ;
381456 public static final String GSSAPI_MECHANISM = "GSSAPI" ;
382- public static final String MONGODB_PROTOCOL = "mongodb" ;
383457
384458 GSSAPIAuthenticator (final Mongo mongo ) {
385459 super (mongo );
386460
387461 if (!mongo .getCredentials ().getMechanism ().equals (MongoClientCredentials .GSSAPI_MECHANISM )) {
388- throw new MongoException ("Unsupported authentication mechanism: " + mongo .getCredentials ().getMechanism ());
462+ throw new MongoException ("Incorrect mechanism: " + mongo .getCredentials ().getMechanism ());
389463 }
390464 }
391465
@@ -404,6 +478,11 @@ protected SaslClient createSaslClient() {
404478 }
405479 }
406480
481+ @ Override
482+ protected Object getMechanism () {
483+ return GSSAPI_MECHANISM ;
484+ }
485+
407486 private GSSCredential getGSSCredential (String userName ) throws GSSException {
408487 Oid krb5Mechanism = new Oid (GSSAPI_OID );
409488 GSSManager manager = GSSManager .getInstance ();
@@ -414,6 +493,8 @@ private GSSCredential getGSSCredential(String userName) throws GSSException {
414493 }
415494
416495 abstract class SaslAuthenticator {
496+ public static final String MONGODB_PROTOCOL = "mongodb" ;
497+
417498 protected final Mongo mongo ;
418499 private final Map <DB , Boolean > authorizeDatabases = new ConcurrentHashMap <DB , Boolean >();
419500 private volatile boolean authenticated ;
@@ -471,15 +552,15 @@ void authenticate() {
471552
472553 protected abstract SaslClient createSaslClient ();
473554
555+ protected abstract Object getMechanism ();
556+
474557 private CommandResult sendSaslStart (final byte [] outToken ) throws IOException {
475- // System.out.println("Sending saslStart with num bytes: " + outToken.length);
476- DB adminDB = mongo .getDB ("admin" );
477- DBObject cmd = new BasicDBObject ("saslStart" , 1 ).append ("mechanism" , "GSSAPI" ).append ("payload" , outToken );
478- return runCommand (adminDB , cmd );
558+ DBObject cmd = new BasicDBObject ("saslStart" , 1 ).append ("mechanism" , getMechanism ()).append ("payload" ,
559+ outToken != null ? outToken : new byte [0 ]);
560+ return runCommand (mongo .getDB ("admin" ), cmd );
479561 }
480562
481563 private CommandResult sendSaslContinue (final int conversationId , final byte [] outToken ) throws IOException {
482- // System.out.println("Sending saslContinue with num bytes: " + outToken.length);
483564 DB adminDB = mongo .getDB ("admin" );
484565 DBObject cmd = new BasicDBObject ("saslContinue" , 1 ).append ("conversationId" , conversationId ).
485566 append ("payload" , outToken );
0 commit comments