/** * @copyright * ==================================================================== * Copyright (c) 2003 CollabNet. 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://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== * @endcopyright * * @file Prompter.cpp * @brief Implementation of the class Prompter */ #include "Prompter.h" #include "Pool.h" #include "JNIUtil.h" #include "JNIStringHolder.h" #include "../include/org_tigris_subversion_javahl_PromptUserPassword2.h" #include #include "svn_auth.h" #include "svn_error.h" #include "svn_error_codes.h" #include "svn_private_config.h" /** * Constructor * @param jprompter a global reference to the Java callback object * @param v2 the callback objects implements PromptUserPassword2 * @param v3 the callback objects implements PromptUserPassword3 */ Prompter::Prompter(jobject jprompter, bool v2, bool v3) { m_prompter = jprompter; m_version2 = v2; m_version3 = v3; } Prompter::~Prompter() { if (m_prompter!= NULL) { // Since the reference to the Java object is a global one, it // has to be deleted. JNIEnv *env = JNIUtil::getEnv(); env->DeleteGlobalRef(m_prompter); } } /** * Create a C++ peer object for the Java callback object * * @param jprompter Java callback object * @return C++ peer object */ Prompter *Prompter::makeCPrompter(jobject jprompter) { // If we have no Java object, we need no C++ object. if (jprompter == NULL) return NULL; JNIEnv *env = JNIUtil::getEnv(); // Sanity check that the Java object implements PromptUserPassword. jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword"); if (JNIUtil::isJavaExceptionThrown()) return NULL; if (!env->IsInstanceOf(jprompter, clazz)) { env->DeleteLocalRef(clazz); return NULL; } env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return NULL; // Check if PromptUserPassword2 is implemented by the Java object. jclass clazz2 = env->FindClass(JAVA_PACKAGE"/PromptUserPassword2"); if (JNIUtil::isJavaExceptionThrown()) return NULL; bool v2 = env->IsInstanceOf(jprompter, clazz2) ? true: false; if (JNIUtil::isJavaExceptionThrown()) return NULL; env->DeleteLocalRef(clazz2); if (JNIUtil::isJavaExceptionThrown()) return NULL; bool v3 = false; if (v2) { // Check if PromptUserPassword3 is implemented by the Java object. jclass clazz3 = env->FindClass(JAVA_PACKAGE"/PromptUserPassword3"); if (JNIUtil::isJavaExceptionThrown()) return NULL; v3 = env->IsInstanceOf(jprompter, clazz3) ? true: false; if (JNIUtil::isJavaExceptionThrown()) return NULL; env->DeleteLocalRef(clazz3); if (JNIUtil::isJavaExceptionThrown()) return NULL; } // Create a new global ref for the Java object, because it is // longer used that this call. jobject myPrompt = env->NewGlobalRef(jprompter); if (JNIUtil::isJavaExceptionThrown()) return NULL; // Create the C++ peer. return new Prompter(myPrompt, v2, v3); } /** * Retrieve the username from the Java object * @return Java string for the username or NULL */ jstring Prompter::username() { JNIEnv *env = JNIUtil::getEnv(); // The method id will not change during the time this library is // loaded, so it can be cached. static jmethodID mid = 0; if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword"); if (JNIUtil::isJavaExceptionThrown()) return NULL; mid = env->GetMethodID(clazz, "getUsername", "()Ljava/lang/String;"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return NULL; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return NULL; } jstring ret = static_cast(env->CallObjectMethod(m_prompter, mid)); if (JNIUtil::isJavaExceptionThrown()) return NULL; return ret; } /** * Retrieve the password from the Java object * @return Java string for the password or NULL */ jstring Prompter::password() { JNIEnv *env = JNIUtil::getEnv(); // The method id will not change during the time this library is // loaded, so it can be cached. static jmethodID mid = 0; if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword"); if (JNIUtil::isJavaExceptionThrown()) return NULL; mid = env->GetMethodID(clazz, "getPassword", "()Ljava/lang/String;"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return NULL; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return false; } jstring ret = static_cast(env->CallObjectMethod(m_prompter, mid)); if (JNIUtil::isJavaExceptionThrown()) return NULL; return ret; } /** * Ask the user a question, which can be answered by yes/no. * @param realm the server realm, for which this question is asked * @param question the question to ask the user * @param yesIsDefault flag if the yes-button should be the default button * @return flag who the user answered the question */ bool Prompter::askYesNo(const char *realm, const char *question, bool yesIsDefault) { JNIEnv *env = JNIUtil::getEnv(); // The method id will not change during the time this library is // loaded, so it can be cached. static jmethodID mid = 0; if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword"); if (JNIUtil::isJavaExceptionThrown()) return false; mid = env->GetMethodID(clazz, "askYesNo", "(Ljava/lang/String;Ljava/lang/String;Z)Z"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return false; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return false; } // convert the texts to Java strings jstring jrealm = JNIUtil::makeJString(realm); if (JNIUtil::isJavaExceptionThrown()) return false; jstring jquestion = JNIUtil::makeJString(question); if (JNIUtil::isJavaExceptionThrown()) return false; // execute the callback jboolean ret = env->CallBooleanMethod(m_prompter, mid, jrealm, jquestion, yesIsDefault ? JNI_TRUE : JNI_FALSE); if (JNIUtil::isJavaExceptionThrown()) return false; // delete the Java strings env->DeleteLocalRef(jquestion); if (JNIUtil::isJavaExceptionThrown()) return false; env->DeleteLocalRef(jrealm); if (JNIUtil::isJavaExceptionThrown()) return false; return ret ? true:false; } const char *Prompter::askQuestion(const char *realm, const char *question, bool showAnswer, bool maySave) { JNIEnv *env = JNIUtil::getEnv(); if (m_version3) { static jmethodID mid = 0; static jmethodID mid2 = 0; if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword3"); if (JNIUtil::isJavaExceptionThrown()) return NULL; mid = env->GetMethodID(clazz, "askQuestion", "(Ljava/lang/String;Ljava/lang/String;ZZ)Ljava/lang/String;"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return NULL; mid2 = env->GetMethodID(clazz, "userAllowedSave", "()Z"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return NULL; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return NULL; } jstring jrealm = JNIUtil::makeJString(realm); if (JNIUtil::isJavaExceptionThrown()) return NULL; jstring jquestion = JNIUtil::makeJString(question); if (JNIUtil::isJavaExceptionThrown()) return NULL; jstring janswer = static_cast( env->CallObjectMethod(m_prompter, mid, jrealm, jquestion, showAnswer ? JNI_TRUE : JNI_FALSE, maySave ? JNI_TRUE : JNI_FALSE)); if (JNIUtil::isJavaExceptionThrown()) return NULL; env->DeleteLocalRef(jquestion); if (JNIUtil::isJavaExceptionThrown()) return NULL; env->DeleteLocalRef(jrealm); if (JNIUtil::isJavaExceptionThrown()) return NULL; JNIStringHolder answer(janswer); if (answer != NULL) { m_answer = answer; m_maySave = env->CallBooleanMethod(m_prompter, mid2) ? true: false; if (JNIUtil::isJavaExceptionThrown()) return NULL; } else { m_answer = ""; m_maySave = false; } return m_answer.c_str(); } else { static jmethodID mid = 0; if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword"); if (JNIUtil::isJavaExceptionThrown()) return NULL; mid = env->GetMethodID(clazz, "askQuestion", "(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return NULL; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return NULL; } jstring jrealm = JNIUtil::makeJString(realm); if (JNIUtil::isJavaExceptionThrown()) return NULL; jstring jquestion = JNIUtil::makeJString(question); if (JNIUtil::isJavaExceptionThrown()) return NULL; jstring janswer = static_cast( env->CallObjectMethod(m_prompter, mid, jrealm, jquestion, showAnswer ? JNI_TRUE : JNI_FALSE)); if (JNIUtil::isJavaExceptionThrown()) return NULL; env->DeleteLocalRef(jquestion); if (JNIUtil::isJavaExceptionThrown()) return NULL; env->DeleteLocalRef(jrealm); if (JNIUtil::isJavaExceptionThrown()) return NULL; JNIStringHolder answer(janswer); if (answer != NULL) { m_answer = answer; if (maySave) m_maySave = askYesNo(realm, _("May save the answer ?"), true); else m_maySave = false; } else { m_answer = ""; m_maySave = false; } return m_answer.c_str(); } } int Prompter::askTrust(const char *question, bool maySave) { if (m_version2) { static jmethodID mid = 0; JNIEnv *env = JNIUtil::getEnv(); if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword2"); if (JNIUtil::isJavaExceptionThrown()) return -1; mid = env->GetMethodID(clazz, "askTrustSSLServer", "(Ljava/lang/String;Z)I"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return -1; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return -1; } jstring jquestion = JNIUtil::makeJString(question); if (JNIUtil::isJavaExceptionThrown()) return -1; jint ret = env->CallIntMethod(m_prompter, mid, jquestion, maySave ? JNI_TRUE : JNI_FALSE); if (JNIUtil::isJavaExceptionThrown()) return -1; env->DeleteLocalRef(jquestion); if (JNIUtil::isJavaExceptionThrown()) return -1; return ret; } else { std::string q = question; if (maySave) q += _("(R)eject, accept (t)emporarily or accept (p)ermanently?"); else q += _("(R)eject or accept (t)emporarily?"); const char *answer = askQuestion(NULL, q.c_str(), true, false); if (*answer == 't' || *answer == 'T') return org_tigris_subversion_javahl_PromptUserPassword2_AcceptTemporary; else if (maySave && (*answer == 'p' || *answer == 'P')) return org_tigris_subversion_javahl_PromptUserPassword2_AcceptPermanently; else return org_tigris_subversion_javahl_PromptUserPassword2_Reject; } return -1; } bool Prompter::prompt(const char *realm, const char *pi_username, bool maySave) { JNIEnv *env = JNIUtil::getEnv(); if (m_version3) { static jmethodID mid = 0; static jmethodID mid2 = 0; if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword3"); if (JNIUtil::isJavaExceptionThrown()) return false; mid = env->GetMethodID(clazz, "prompt", "(Ljava/lang/String;Ljava/lang/String;Z)Z"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return false; mid2 = env->GetMethodID(clazz, "userAllowedSave", "()Z"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return false; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return false; } jstring jrealm = JNIUtil::makeJString(realm); if (JNIUtil::isJavaExceptionThrown()) return false; jstring jusername = JNIUtil::makeJString(pi_username); if (JNIUtil::isJavaExceptionThrown()) return false; jboolean ret = env->CallBooleanMethod(m_prompter, mid, jrealm, jusername, maySave ? JNI_TRUE: JNI_FALSE); if (JNIUtil::isJavaExceptionThrown()) return false; env->DeleteLocalRef(jusername); if (JNIUtil::isJavaExceptionThrown()) return false; env->DeleteLocalRef(jrealm); if (JNIUtil::isJavaExceptionThrown()) return false; m_maySave = env->CallBooleanMethod(m_prompter, mid2) ? true : false; if (JNIUtil::isJavaExceptionThrown()) return false; return ret ? true:false; } else { static jmethodID mid = 0; if (mid == 0) { jclass clazz = env->FindClass(JAVA_PACKAGE"/PromptUserPassword"); if (JNIUtil::isJavaExceptionThrown()) return false; mid = env->GetMethodID(clazz, "prompt", "(Ljava/lang/String;Ljava/lang/String;)Z"); if (JNIUtil::isJavaExceptionThrown() || mid == 0) return false; env->DeleteLocalRef(clazz); if (JNIUtil::isJavaExceptionThrown()) return false; } jstring jrealm = JNIUtil::makeJString(realm); if (JNIUtil::isJavaExceptionThrown()) return false; jstring jusername = JNIUtil::makeJString(pi_username); if (JNIUtil::isJavaExceptionThrown()) return false; jboolean ret = env->CallBooleanMethod(m_prompter, mid, jrealm, jusername); if (JNIUtil::isJavaExceptionThrown()) return false; env->DeleteLocalRef(jusername); if (JNIUtil::isJavaExceptionThrown()) return false; env->DeleteLocalRef(jrealm); if (JNIUtil::isJavaExceptionThrown()) return false; if (maySave) m_maySave = askYesNo(realm, _("May save the answer ?"), true); else m_maySave = false; return ret ? true:false; } } svn_auth_provider_object_t *Prompter::getProviderSimple() { apr_pool_t *pool = JNIUtil::getRequestPool()->pool(); svn_auth_provider_object_t *provider; svn_auth_get_simple_prompt_provider(&provider, simple_prompt, this, 2, /* retry limit */ pool); return provider; } svn_auth_provider_object_t *Prompter::getProviderUsername() { apr_pool_t *pool = JNIUtil::getRequestPool()->pool(); svn_auth_provider_object_t *provider; svn_auth_get_username_prompt_provider(&provider, username_prompt, this, 2, /* retry limit */ pool); return provider; } svn_auth_provider_object_t *Prompter::getProviderServerSSLTrust() { apr_pool_t *pool = JNIUtil::getRequestPool()->pool(); svn_auth_provider_object_t *provider; svn_auth_get_ssl_server_trust_prompt_provider (&provider, ssl_server_trust_prompt, this, pool); return provider; } svn_auth_provider_object_t *Prompter::getProviderClientSSL() { apr_pool_t *pool = JNIUtil::getRequestPool()->pool(); svn_auth_provider_object_t *provider; svn_auth_get_ssl_client_cert_prompt_provider(&provider, ssl_client_cert_prompt, this, 2 /* retry limit */, pool); return provider; } svn_auth_provider_object_t *Prompter::getProviderClientSSLPassword() { apr_pool_t *pool = JNIUtil::getRequestPool()->pool(); svn_auth_provider_object_t *provider; svn_auth_get_ssl_client_cert_pw_prompt_provider (&provider, ssl_client_cert_pw_prompt, this, 2 /* retry limit */, pool); return provider; } svn_error_t *Prompter::simple_prompt(svn_auth_cred_simple_t **cred_p, void *baton, const char *realm, const char *username, svn_boolean_t may_save, apr_pool_t *pool) { Prompter *that = (Prompter*)baton; svn_auth_cred_simple_t *ret = (svn_auth_cred_simple_t*)apr_pcalloc(pool, sizeof(*ret)); if (!that->prompt(realm, username, may_save ? true : false)) return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("User canceled dialog")); jstring juser = that->username(); JNIStringHolder user(juser); if (user == NULL) return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("User canceled dialog")); ret->username = apr_pstrdup(pool,user); jstring jpass = that->password(); JNIStringHolder pass(jpass); if (pass == NULL) return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("User canceled dialog")); else { ret->password = apr_pstrdup(pool, pass); ret->may_save = that->m_maySave; } *cred_p = ret; return SVN_NO_ERROR; } svn_error_t *Prompter::username_prompt(svn_auth_cred_username_t **cred_p, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool) { Prompter *that = (Prompter*)baton; svn_auth_cred_username_t *ret = (svn_auth_cred_username_t*)apr_pcalloc(pool, sizeof(*ret)); const char *user = that->askQuestion(realm, _("Username: "), true, may_save ? true : false); if (user == NULL) return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("User canceled dialog")); ret->username = apr_pstrdup(pool,user); ret->may_save = that->m_maySave; *cred_p = ret; return SVN_NO_ERROR; } svn_error_t * Prompter::ssl_server_trust_prompt(svn_auth_cred_ssl_server_trust_t **cred_p, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *cert_info, svn_boolean_t may_save, apr_pool_t *pool) { Prompter *that = (Prompter*)baton; svn_auth_cred_ssl_server_trust_t *ret = (svn_auth_cred_ssl_server_trust_t*)apr_pcalloc(pool, sizeof(*ret)); std::string question = _("Error validating server certificate for "); question += realm; question += ":\n"; if (failures & SVN_AUTH_SSL_UNKNOWNCA) { question += _(" - Unknown certificate issuer\n"); question += _(" Fingerprint: "); question += cert_info->fingerprint; question += "\n"; question += _(" Distinguished name: "); question += cert_info->issuer_dname; question += "\n"; } if (failures & SVN_AUTH_SSL_CNMISMATCH) { question += _(" - Hostname mismatch ("); question += cert_info->hostname; question += _(")\n"); } if (failures & SVN_AUTH_SSL_NOTYETVALID) { question += _(" - Certificate is not yet valid\n"); question += _(" Valid from "); question += cert_info->valid_from; question += "\n"; } if (failures & SVN_AUTH_SSL_EXPIRED) { question += _(" - Certificate is expired\n"); question += _(" Valid until "); question += cert_info->valid_until; question += "\n"; } switch(that->askTrust(question.c_str(), may_save ? true : false)) { case org_tigris_subversion_javahl_PromptUserPassword2_AcceptTemporary: *cred_p = ret; ret->may_save = FALSE; break; case org_tigris_subversion_javahl_PromptUserPassword2_AcceptPermanently: *cred_p = ret; ret->may_save = TRUE; ret->accepted_failures = failures; break; default: *cred_p = NULL; } return SVN_NO_ERROR; } svn_error_t * Prompter::ssl_client_cert_prompt(svn_auth_cred_ssl_client_cert_t **cred_p, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool) { Prompter *that = (Prompter*)baton; svn_auth_cred_ssl_client_cert_t *ret = (svn_auth_cred_ssl_client_cert_t*)apr_pcalloc(pool, sizeof(*ret)); const char *cert_file = that->askQuestion(realm, _("client certificate filename: "), true, may_save ? true : false); if (cert_file == NULL) return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("User canceled dialog")); ret->cert_file = apr_pstrdup(pool, cert_file); ret->may_save = that->m_maySave; *cred_p = ret; return SVN_NO_ERROR; } svn_error_t * Prompter::ssl_client_cert_pw_prompt(svn_auth_cred_ssl_client_cert_pw_t **cred_p, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool) { Prompter *that = (Prompter*)baton; svn_auth_cred_ssl_client_cert_pw_t *ret = (svn_auth_cred_ssl_client_cert_pw_t*)apr_pcalloc(pool, sizeof(*ret)); const char *info = that->askQuestion(realm, _("client certificate passphrase: "), false, may_save ? true : false); if (info == NULL) return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("User canceled dialog")); ret->password = apr_pstrdup(pool, info); ret->may_save = that->m_maySave; *cred_p = ret; return SVN_NO_ERROR; }