/* * ==================================================================== * Copyright (c) 2004-2010 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.util.jna; import java.util.Arrays; import java.util.List; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.wc.ISVNGnomeKeyringPasswordProvider; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; import com.sun.jna.Memory; import com.sun.jna.Pointer; import com.sun.jna.Structure; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.PointerByReference; /** * @author TMate Software Ltd. * @version 1.3 */ public class SVNGnomeKeyring { private static final Object keyringAccessMonitor = new Object(); private static final ISVNGnomeKeyringLibrary.GnomeKeyringOperationDoneCallback DONE_CALLBACK = new ISVNGnomeKeyringLibrary.GnomeKeyringOperationDoneCallback() { public void callback(int result, Pointer data) { if (data == null) { return; } final ISVNGLibrary gLibrary = JNALibraryLoader.getGLibrary(); synchronized (keyringAccessMonitor) { GnomeKeyringContext context = new GnomeKeyringContext(data); context.read(); gLibrary.g_main_loop_quit(context.loop); } } }; private static final ISVNGnomeKeyringLibrary.GnomeKeyringOperationGetKeyringInfoCallback GET_KEYRING_INFO_CALLBACK = new ISVNGnomeKeyringLibrary.GnomeKeyringOperationGetKeyringInfoCallback() { public void callback(int result, Pointer info, Pointer data) { if (data == null) { return; } final ISVNGnomeKeyringLibrary gnomeKeyringLibrary = JNALibraryLoader.getGnomeKeyringLibrary(); final ISVNGLibrary gLibrary = JNALibraryLoader.getGLibrary(); synchronized (keyringAccessMonitor) { GnomeKeyringContext context = new GnomeKeyringContext(data); context.read(); if (result == ISVNGnomeKeyringLibrary.GNOME_KEYRING_RESULT_OK && info != null) { context.keyringInfo = gnomeKeyringLibrary.gnome_keyring_info_copy(info); context.write(); } else { if (context.keyringInfo != null) { gnomeKeyringLibrary.gnome_keyring_info_free(context.keyringInfo); } context.keyringInfo = null; context.write(); } gLibrary.g_main_loop_quit(context.loop); } } }; private static final ISVNGnomeKeyringLibrary.GnomeKeyringOperationGetStringCallback DEFAULT_KEYRING_CALLBACK = new ISVNGnomeKeyringLibrary.GnomeKeyringOperationGetStringCallback() { public void callback(int result, Pointer value, Pointer data) { if (data == null) { return; } final ISVNGLibrary gLibrary = JNALibraryLoader.getGLibrary(); synchronized (keyringAccessMonitor) { GnomeKeyringContext context = new GnomeKeyringContext(data); context.read(); if (result == ISVNGnomeKeyringLibrary.GNOME_KEYRING_RESULT_OK && value != null) { String stringValue = value.getString(0); context.keyringName = stringValue; context.write(); } else { // if (key_info - > keyring_name != NULL) // free((void*)key_info - > keyring_name); // Does JNA free native memory referenced by keyringName or not? context.keyringName = null; context.write(); } gLibrary.g_main_loop_quit(context.loop); } } }; public static boolean isEnabled() { boolean gnomeSupported = SVNFileUtil.isOSX || SVNFileUtil.isLinux || SVNFileUtil.isBSD || SVNFileUtil.isSolaris; String gnomeKeyringOption = System.getProperty("svnkit.library.gnome-keyring.enabled", "true"); boolean gnomeKeyringEnabled = Boolean.TRUE.toString().equalsIgnoreCase(gnomeKeyringOption); boolean librariesLoaded = JNALibraryLoader.getGnomeKeyringLibrary() != null && JNALibraryLoader.getGLibrary() != null; final boolean enabled = gnomeSupported && gnomeKeyringEnabled && librariesLoaded; SVNDebugLog.getDefaultLog().logFine(SVNLogType.DEFAULT, enabled ? "Gnome Keyring enabled" : "Gnome Keyring disabled"); return enabled; } public static void initialize() { final ISVNGLibrary gLibrary = JNALibraryLoader.getGLibrary(); if (gLibrary == null) { return; } synchronized (keyringAccessMonitor) { String applicationName = gLibrary.g_get_application_name(); if (applicationName == null) { gLibrary.g_set_application_name("Subversion"); } } } private static String getDefaultKeyringName() { final ISVNGnomeKeyringLibrary gnomeKeyringLibrary = JNALibraryLoader.getGnomeKeyringLibrary(); final ISVNGLibrary gLibrary = JNALibraryLoader.getGLibrary(); synchronized (keyringAccessMonitor) { GnomeKeyringContext context = new GnomeKeyringContext(); context.keyringInfo = null; context.keyringName = null; context.loop = gLibrary.g_main_loop_new(null, false); context.write(); gnomeKeyringLibrary.gnome_keyring_get_default_keyring(DEFAULT_KEYRING_CALLBACK, context.getPointer(), null); gLibrary.g_main_loop_run(context.loop); context.read(); if (context.keyringName == null) { destroyKeyringContext(context); return null; } String defaultKeyringName = context.keyringName; destroyKeyringContext(context); return defaultKeyringName; } } private static boolean checkKeyringIsLocked(String keyringName) { ISVNGnomeKeyringLibrary gnomeKeyringLibrary = JNALibraryLoader.getGnomeKeyringLibrary(); ISVNGLibrary gLibrary = JNALibraryLoader.getGLibrary(); GnomeKeyringContext context = new GnomeKeyringContext(); context.keyringName = null; context.keyringName = null; context.loop = gLibrary.g_main_loop_new(null, false); context.write(); synchronized (keyringAccessMonitor) { gnomeKeyringLibrary.gnome_keyring_get_info(keyringName, GET_KEYRING_INFO_CALLBACK, context.getPointer(), null); gLibrary.g_main_loop_run(context.loop); context.read(); if (context.keyringInfo == null) { destroyKeyringContext(context); return false; } return gnomeKeyringLibrary.gnome_keyring_info_get_is_locked(context.keyringInfo); } } private static void unlockKeyring(String keyringName, char[] keyringPassword) { ISVNGnomeKeyringLibrary gnomeKeyringLibrary = JNALibraryLoader.getGnomeKeyringLibrary(); ISVNGLibrary gLibrary = JNALibraryLoader.getGLibrary(); GnomeKeyringContext context = new GnomeKeyringContext(); context.keyringInfo = null; context.keyringName = null; context.loop = gLibrary.g_main_loop_new(null, false); context.write(); synchronized (keyringAccessMonitor) { gnomeKeyringLibrary.gnome_keyring_get_info(keyringName, GET_KEYRING_INFO_CALLBACK, context.getPointer(), null); gLibrary.g_main_loop_run(context.loop); context.read(); if (context.keyringInfo == null) { destroyKeyringContext(context); return; } else { context.loop = gLibrary.g_main_loop_new(null, false); context.write(); final byte[] keyringUTF8Password = SVNEncodingUtil.getBytes(keyringPassword, "UTF-8"); Memory passwordData = null; try { passwordData = new Memory(keyringUTF8Password.length + 1); passwordData.write(0, keyringUTF8Password, 0, keyringUTF8Password.length); passwordData.setByte(keyringUTF8Password.length, (byte) 0); gnomeKeyringLibrary.gnome_keyring_unlock(keyringName, passwordData.getPointer(0), DONE_CALLBACK, context.getPointer(), null); gLibrary.g_main_loop_run(context.loop); context.read(); } finally { if (passwordData != null) { passwordData.clear(); } SVNEncodingUtil.clearArray(keyringUTF8Password); } } destroyKeyringContext(context); } } public static char[] getPassword(String realm, String userName, boolean nonInteractive, ISVNGnomeKeyringPasswordProvider keyringPasswordProvider) throws SVNException { String defaultKeyring = getDefaultKeyringName(); if (!nonInteractive) { if (checkKeyringIsLocked(defaultKeyring)) { if (keyringPasswordProvider != null) { char[] keyringPassword = keyringPasswordProvider.getKeyringPassword(defaultKeyring); unlockKeyring(defaultKeyring, keyringPassword); } } } if (checkKeyringIsLocked(defaultKeyring)) { SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.AUTHN_CREDS_UNAVAILABLE, "GNOME Keyring is locked and we are non-interactive"); SVNErrorManager.error(error, SVNLogType.CLIENT); return null; } else { return getPassword(realm, userName); } } private static char[] getPassword(String realm, String userName) { ISVNGnomeKeyringLibrary gnomeKeyringLibrary = JNALibraryLoader.getGnomeKeyringLibrary(); // Ensure Gnome Keyring access is initialized via dbus. // if (!dbus_bus_get(DBUS_BUS_SESSION, NULL)) { // return FALSE; // } synchronized (keyringAccessMonitor) { if (!gnomeKeyringLibrary.gnome_keyring_is_available()) { return null; } getDefaultKeyringName(); PointerByReference itemsReference = new PointerByReference(); int result = gnomeKeyringLibrary.gnome_keyring_find_network_password_sync(userName, realm, null, null, null, null, 0, itemsReference); byte[]password = null; if (result == ISVNGnomeKeyringLibrary.GNOME_KEYRING_RESULT_OK) { ISVNGLibrary.GList items = new ISVNGLibrary.GList(itemsReference.getValue()); items.read(); if (items.data != null) { ISVNGnomeKeyringLibrary.GnomeKeyringNetworkPasswordData item = new ISVNGnomeKeyringLibrary.GnomeKeyringNetworkPasswordData(items.data); item.read(); if (item.password != null) { int offset = 0; while(item.password.getByte(offset) != 0 && offset < item.size()) { offset++; } password = item.password.getByteArray(0, offset); } gnomeKeyringLibrary.gnome_keyring_network_password_list_free(items); } } // Subversion frees memory allocated for temporary defaultKeyring value. // Ensure we freed native memory from which we obtained corresponding String Object keyringName. // if (default_keyring) // free(default_keyring); try { return SVNEncodingUtil.getChars(password, "UTF-8"); } finally { SVNEncodingUtil.clearArray(password); } } } public static boolean setPassword(String realm, String userName, char[] password, boolean nonInteractive, ISVNGnomeKeyringPasswordProvider keyringPasswordProvider) throws SVNException { String defaultKeyring = getDefaultKeyringName(); if (!nonInteractive) { if (checkKeyringIsLocked(defaultKeyring)) { if (keyringPasswordProvider != null) { char[] keyringPassword = keyringPasswordProvider.getKeyringPassword(defaultKeyring); unlockKeyring(defaultKeyring, keyringPassword); } } } if (checkKeyringIsLocked(defaultKeyring)) { SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.AUTHN_CREDS_UNAVAILABLE, "GNOME Keyring is locked and we are non-interactive"); SVNErrorManager.error(error, SVNLogType.CLIENT); return false; } else { return setPassword(realm, userName, password); } } private static boolean setPassword(String realm, String userName, char[] password) { final ISVNGnomeKeyringLibrary gnomeKeyringLibrary = JNALibraryLoader.getGnomeKeyringLibrary(); // Ensure Gnome Keyring access is initialized via dbus. // if (!dbus_bus_get(DBUS_BUS_SESSION, NULL)) { // return FALSE; // } synchronized (keyringAccessMonitor) { if (!gnomeKeyringLibrary.gnome_keyring_is_available()) { return false; } getDefaultKeyringName(); IntByReference itemId = new IntByReference(); final byte[] utf8Password = SVNEncodingUtil.getBytes(password, "UTF-8"); Memory passwordData = null; int result; try { passwordData = new Memory(utf8Password.length + 1); passwordData.write(0, utf8Password, 0, utf8Password.length); passwordData.setByte(utf8Password.length, (byte) 0); result = gnomeKeyringLibrary.gnome_keyring_set_network_password_sync(null, userName, realm, null, null, null, null, 0, passwordData.getPointer(0), itemId); } finally { if (passwordData != null) { passwordData.clear(); } SVNEncodingUtil.clearArray(utf8Password); } // Subversion frees memory allocated for temporary defaultKeyring value. // Ensure we freed native memory from which we obtained corresponding String Object keyringName. // if (default_keyring) // free(default_keyring); return result == ISVNGnomeKeyringLibrary.GNOME_KEYRING_RESULT_OK; } } private static void destroyKeyringContext(GnomeKeyringContext context) { if (context == null) { return; } ISVNGnomeKeyringLibrary gnomeKeyringLibrary = JNALibraryLoader.getGnomeKeyringLibrary(); // if (key_info - > keyring_name != NULL) // free((void*)key_info - > keyring_name); // Does JNA free native memory referenced by keyringName or not? synchronized (keyringAccessMonitor) { context.keyringName = null; context.write(); if (context.keyringInfo != null) { gnomeKeyringLibrary.gnome_keyring_info_free(context.keyringInfo); context.keyringInfo = null; } } } // struct gnome_keyring_baton // { // const char*keyring_name; // GnomeKeyringInfo * info; // GMainLoop * loop; // } public static class GnomeKeyringContext extends Structure { public GnomeKeyringContext() { } public GnomeKeyringContext(Pointer p) { super(p); } public String keyringName; public Pointer keyringInfo; public Pointer loop; protected List<String> getFieldOrder() { return Arrays.asList("keyringName", "keyringInfo", "loop"); } } }