问题描述
使用同一个账号登录svn。有的电脑清除登录信息后,执行SVN-log命令,可直接查看log等一系列操作不需要登录,断网重启电脑、卸载重新安装都不起作用。。
有的电脑清除登录信息,执行SVN-log命令后会弹出登录对话框。
在网上没有找到解决方法。
从源代码分析
使用debug方式编译代码,后面使用vs调试。
编译前,修改ext\serf\serf_private.h文件:
将上述几个宏都设置为1,方便调试查看serf模块输出的log。执行以下命令编译。
nant x64 debug setup
编译完成后,用设置VS的TortoiseProc项目的属性
这样TortoiseProc运行时,会出现一个控制台用于输出serf模块的运行log。
A.设置界面,清除登录信息
CSetSavedDataPage::OnInitDialog
保存界面初始化
点击“Clear All”
CSetSavedDataPage::OnBnClickedAuthhistclear
清除注册表\HKEY_CURRENT_USER\SOFTWARE\TortoiseSVN\Auth
删除文件夹C:\Users\Administrator\AppData\Roaming\Subversion\auth\
清除注册表\HKEY_CURRENT_USER\SOFTWARE\TortoiseSVN\CAPIAuthz
B.清除登录信息后能直接使用svn的电脑上执行svn-log命令
LogCommand::Execute=>
CLogDlg dlg;
dlg.DoModal();=>
CLogDlg::OnInitDialog()=>
ReadProjectPropertiesAndBugTraqInfo=>m_ProjectProperties.ReadProps(m_path);
new async::CAsyncCall(this, &CLogDlg::LogThread, &netScheduler)
new async::CAsyncCall(this, &CLogDlg::DetectVisualStudioRunningThread, &vsRunningScheduler);
CLogDlg::LogThread
new async::CAsyncCall(this, &CLogDlg::StatusThread, &diskScheduler);
GetRootAndHead(m_path, rootpath, head)=>
+svnPath = path.GetSVNApiPath(localpool)
+cachedProperties.GetHeadRevision (uuid, path) =>
++svn.GetHEADRevision (url, false)=>
+++svn_client_open_ra_session2=>
+4+svn_client__open_ra_session_internal=>
+5+SVN_ERR(svn_ra_create_callbacks(&cbtable, result_pool)) //设置了数个回调函数
+5+SVN_ERR(svn_ra_open4 =>
+6+SVN_ERR(svn_auth__make_session_auth( =>
+7+SVN_ERR(initfunc(svn_ra_version(), &vtable, scratch_pool)) => //vtable为serf模块的所有函数列表
+8+svn_ra_serf__init
+6+session = apr_pcalloc(sesspool, sizeof(*session));
+6+session->cancel_func = callbacks->cancel_func;//callbacks->cancel_func = 0x00007ffbc42c31e0 {libsvn_tsvn.dll!cancel_callback(void *)}
+6+err = vtable->open_session => //svn_ra_serf__open
+7+serf_sess = apr_pcalloc(result_pool, sizeof(*serf_sess));
+7+serf_sess->wc_callbacks = callbacks; //svn模块的回调函数s
+7+serf_sess->progress_func = callbacks->progress_func; //callbacks->progress_func = 0x00007ffc732e3710 {libsvn_tsvn.dll!progress_func(__int64, __int64, void *, apr_pool_t *)}
+7+serf_sess->cancel_func = callbacks->cancel_func;
//callbacks->cancel_func = 0x00007ffc732e31e0 {libsvn_tsvn.dll!cancel_callback(void *)}
+7+serf_connection_create2(
+7+serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress, // progress_func = 0x00007ffc733ee8e0 {libsvn_tsvn.dll!svn_ra_serf__progress(void *, __int64, __int64)}
+7+err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, =>
+8+svn_ra_serf__context_run_one =>
+9+svn_ra_serf__context_run_wait =>
+A+svn_ra_serf__context_run =>
+B+serf_context_run =>
+C+serf_event_trigger =>
+D+serf__process_connection =>
+E+read_from_connection =>
+F+serf_bucket_peek => //serf_ssl_peek
+G+serf_databuf_peek =>
+H+common_databuf_prep =>
+I+status = (*databuf->read)(databuf->read_baton, sizeof(databuf->buf), => //ssl_decrypt
+J+ssl23_read =>
+K+ssl23_connect =>
+L+ssl23_get_server_hello =>
+M+ssl3_connect =>
+N+ssl3_get_server_certificate =>
+O+ssl_verify_cert_chain
...
调用层次有点多,不再继续跟踪。。
查找证书相关信息的代码
windows_ssl_server_trust_first_credentials(void **credentials,
void **iter_baton,
void *provider_baton,
apr_hash_t *parameters,
const char *realmstring,
apr_pool_t *pool)
{
apr_uint32_t *failure_ptr = svn_hash_gets(parameters,
SVN_AUTH_PARAM_SSL_SERVER_FAILURES);
apr_uint32_t failures = *failure_ptr;
const svn_auth_ssl_server_cert_info_t *cert_info =
svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO);
证书获取
ssl_verify_cert_chain(SSL *s, STACK_OF(X509) *sk) =>
X509_verify_cert(X509_STORE_CTX *ctx) =>
validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx) =>
server_cert = X509_STORE_CTX_get_current_cert(store_ctx);
cert = apr_palloc(subpool, sizeof(serf_ssl_certificate_t));
cert->ssl_cert = server_cert;
cert->depth = depth;
ssl_server_cert_cb(void *baton, int failures,
const serf_ssl_certificate_t *cert) =>
ssl_server_cert(void *baton, int failures,
const serf_ssl_certificate_t *cert,
apr_pool_t *scratch_pool)
serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
cert_info.hostname = svn_hash_gets(subject, "CN");
cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
if (! cert_info.fingerprint)
cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
if (! cert_info.valid_from)
cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
if (! cert_info.valid_until)
cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
svn_auth_set_parameter(conn->session->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
&cert_info);
对代码不太熟悉,进展困难,放弃,换个方向
C.清除登录信息后能需要登录后才能使用svn的电脑上执行svn-log命令
弹出登录对话框时的堆栈调用情况
定位代码:
serf__handle_basic_auth(int code, =>
status = serf__provide_credentials(ctx,
&username, &password,
request, baton,
code, authn_info->scheme->name,
realm, cred_pool);
serf__provide_credentials(serf_context_t *ctx, =>
/* Ask the application. */
status = (*ctx->cred_cb)(username, password,
authn_req, authn_req->handler_baton,
code, authn_type, realm, pool);
svn_auth_first_credentials(void **credentials, =>
for (i = 0; i < table->providers->nelts; i++)
{
provider = APR_ARRAY_IDX(table->providers, i,
svn_auth_provider_object_t *);
SVN_ERR(provider->vtable->first_credentials(&creds, &iter_baton,
provider->provider_baton,
parameters,
realmstring,
auth_baton->pool));
if (creds != NULL)
{
got_first = TRUE;
break;
}
simple_prompt_first_creds(void **credentials_p, =>
SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t **) credentials_p,
prompt_for_simple_creds(svn_auth_cred_simple_t **cred_p, =>
SVN_ERR(pb->prompt_func(cred_p, pb->prompt_baton, realmstring,
default_username, may_save, pool));
SVNPrompt::simpleprompt(svn_auth_cred_simple_t **cred =>
if (!svn->m_bSuppressed && svn->SimplePrompt(UserName, PassWord, Realm, may_save))
{
ret->username = apr_pstrdup(pool, CUnicodeUtils::GetUTF8(UserName));
ret->password = apr_pstrdup(pool, CUnicodeUtils::GetUTF8(PassWord));
ret->may_save = may_save;
*cred = ret;
handle_auth_headers(int code, =>
status = handler(code, request, response,
auth_hdr, auth_attr, baton, ctx->pool);
D.回到能直接登录的电脑,调试上述代码
发现步骤C处获取用户输入用户名和密码的代码未被调用,直接原因是handler 没有serf__provide_credentials的指针
serf_authn_schemes:
name = 0x00007ffbefe93b40 "Negotiate"
name = 0x00007ffbefe93b5c "NTLM"
name = 0x00007ffbefe93b6c "Digest"
name = 0x00007ffbefe93b7c "Basic"
进一步查阅SVN的资料发现:
SVN有多种身份认证的方式,需要用户名和密码登录的是“Basic Authentication”。认证方式可在SVN服务器上配置。
详细内容查看:
Version Control with Subversion
本文使用的SVN服务器是VisualSVN。
Serf模块按照优先级顺序,从左到右Negotiate、NTLM、Digest、Basic认证用户信息。
根据serf输出的log可以查看细节
无需输入用户名和密码的svn输出信息
客户端请求svn 路径信息:
服务器返回:
客户端再次请求(请求头有身份认证信息)
服务器返回
客户端继续请求(请求头有身份认证信息)
服务器返回
需要用户名和密码的svn输出信息
和“无需输入用户名和密码”步骤一致,最后一步服务器反馈:SAMEORIGIN认证失败
后续客户端使用NTLM认证仍失败。
最后使用Basic方式认证成功。
前两种认证方式失败的原因(涉及到客户端SSP、服务器)不再继续调查。
Microsoft Kerberos - Win32 apps | Microsoft Learn
Microsoft NTLM - Win32 apps | Microsoft Learn
解决方法
如果想切换账号,更改Windows登录的方式(如更换域账号)即可。或者将服务器的认证方式改成Basic Authentication单一认证(不建议)。