Index: libsvn_ra_svn/client.c =================================================================== --- libsvn_ra_svn/client.c (revision 7471) +++ libsvn_ra_svn/client.c (working copy) @@ -41,6 +41,15 @@ typedef struct { svn_ra_svn_conn_t *conn; + int protocol_version; + svn_boolean_t is_tunneled; + svn_auth_baton_t *auth_baton; + const char *user; + const char *realm_prefix; +} ra_svn_session_baton_t; + +typedef struct { + ra_svn_session_baton_t *sess; apr_pool_t *pool; svn_revnum_t *new_rev; svn_commit_callback_t callback; @@ -48,6 +57,7 @@ } ra_svn_commit_callback_baton_t; typedef struct { + ra_svn_session_baton_t *sess; svn_ra_svn_conn_t *conn; apr_pool_t *pool; const svn_delta_editor_t *editor; @@ -182,6 +192,110 @@ return SVN_NO_ERROR; } +/* --- AUTHENTICATION ROUTINES --- */ + +static svn_boolean_t find_mech(apr_array_header_t *mechlist, const char *mech) +{ + int i; + svn_ra_svn_item_t *elt; + + for (i = 0; i < mechlist->nelts; i++) + { + elt = &((svn_ra_svn_item_t *) mechlist->elts)[i]; + if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, mech) == 0) + return TRUE; + } + return FALSE; +} + +/* Having picked a mechanism, start authentication by writing out an + * auth response. If COMPAT is true, also write out a version number + * and capability list. MECH_ARG may be NULL for mechanisms with no + * initial client response. */ +static svn_error_t *auth_response(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + const char *mech, const char *mech_arg, + svn_boolean_t compat) +{ + if (compat) + return svn_ra_svn_write_tuple(conn, pool, "nw(?c)()", (apr_uint64_t) 1, + mech, mech_arg); + else + return svn_ra_svn_write_tuple(conn, pool, "w(?c)", mech, mech_arg); +} + +/* Read the "success" response to ANONYMOUS or EXTERNAL authentication. */ +static svn_error_t *read_success(svn_ra_svn_conn_t *conn, apr_pool_t *pool) +{ + const char *status, *arg; + + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &status, &arg)); + if (strcmp(status, "failure") == 0 && arg) + return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + "Authentication error from server: %s", arg); + else if (strcmp(status, "success") != 0 || arg) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + "Unexpected server response to authentication"); + return SVN_NO_ERROR; +} + +/* Respond to an auth request and perform authentication. REALM may + * be NULL for the initial authentication exchange of protocol version + * 1. */ +static svn_error_t *do_auth(ra_svn_session_baton_t *sess, + apr_array_header_t *mechlist, + const char *realm, apr_pool_t *pool) +{ + svn_ra_svn_conn_t *conn = sess->conn; + const char *realmstring; + svn_auth_iterstate_t *iterstate; + void *creds; + svn_error_t *err; + svn_boolean_t compat = (realm == NULL); + + realmstring = realm ? apr_psprintf(pool, "%s %s", sess->realm_prefix, realm) + : sess->realm_prefix; + if (sess->is_tunneled && find_mech(mechlist, "EXTERNAL")) + { + /* Ask the server to use the tunnel connection environment (on + * Unix, that means uid) to determine the authentication name. */ + SVN_ERR(auth_response(conn, pool, "EXTERNAL", "", compat)); + return read_success(conn, pool); + } + else if (find_mech(mechlist, "ANONYMOUS")) + { + if (!sess->user) + { + err = svn_auth_first_credentials(&creds, &iterstate, + SVN_AUTH_CRED_USERNAME, realmstring, + sess->auth_baton, pool); + if (!err && creds) + sess->user = ((svn_auth_cred_username_t *) creds)->username; + svn_error_clear(err); + } + SVN_ERR(auth_response(conn, pool, "ANONYMOUS", + sess->user ? sess->user : "", compat)); + return read_success(conn, pool); + } + else + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + "Cannot negotiate authentication mechanism"); +} + +static svn_error_t *handle_auth_request(ra_svn_session_baton_t *sess, + apr_pool_t *pool) +{ + svn_ra_svn_conn_t *conn = sess->conn; + apr_array_header_t *mechlist; + const char *realm; + + if (sess->protocol_version < 2) + return SVN_NO_ERROR; + SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "lc", &mechlist, &realm)); + if (mechlist->nelts == 0) + return SVN_NO_ERROR; + return do_auth(sess, mechlist, realm, pool); +} + /* --- REPORTER IMPLEMENTATION --- */ static svn_error_t *ra_svn_set_path(void *baton, const char *path, @@ -204,7 +318,7 @@ SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-path", "c", path)); return SVN_NO_ERROR; } - + static svn_error_t *ra_svn_link_path(void *baton, const char *path, const char *url, svn_revnum_t rev, @@ -223,6 +337,7 @@ ra_svn_reporter_baton_t *b = baton; SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "finish-report", "")); + SVN_ERR(handle_auth_request(b->sess, b->pool)); SVN_ERR(svn_ra_svn_drive_editor(b->conn, b->pool, b->editor, b->edit_baton, NULL)); SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, b->pool, "")); @@ -245,7 +360,7 @@ ra_svn_abort_report }; -static void ra_svn_get_reporter(svn_ra_svn_conn_t *conn, apr_pool_t *pool, +static void ra_svn_get_reporter(ra_svn_session_baton_t *sess, apr_pool_t *pool, const svn_delta_editor_t *editor, void *edit_baton, const svn_ra_reporter_t **reporter, @@ -254,7 +369,8 @@ ra_svn_reporter_baton_t *b; b = apr_palloc(pool, sizeof(*b)); - b->conn = conn; + b->sess = sess; + b->conn = sess->conn; b->pool = pool; b->editor = editor; b->edit_baton = edit_baton; @@ -329,20 +445,6 @@ return SVN_NO_ERROR; } -static svn_boolean_t find_mech(apr_array_header_t *mechlist, const char *mech) -{ - int i; - svn_ra_svn_item_t *elt; - - for (i = 0; i < mechlist->nelts; i++) - { - elt = &((svn_ra_svn_item_t *) mechlist->elts)[i]; - if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, mech) == 0) - return TRUE; - } - return FALSE; -} - /* This function handles any errors which occur in the child process * created for a tunnel agent. We write the error out as a command * failure; the code in ra_svn_open() to read the server's greeting @@ -362,24 +464,65 @@ svn_error_clear(svn_ra_svn_flush(conn, pool)); } -static svn_error_t *ra_svn_open(void **sess, const char *url, +static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, + apr_pool_t *pool) +{ + apr_status_t status; + apr_proc_t *proc; + apr_procattr_t *attr; + + status = apr_procattr_create(&attr, pool); + if (status == APR_SUCCESS) + status = apr_procattr_io_set(attr, 1, 1, 0); + if (status == APR_SUCCESS) + status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); + if (status == APR_SUCCESS) + status = apr_procattr_child_errfn_set(attr, handle_child_process_error); + proc = apr_palloc(pool, sizeof(*proc)); + if (status == APR_SUCCESS) + status = apr_proc_create(proc, *args, args, NULL, attr, pool); + if (status != APR_SUCCESS) + return svn_error_create(status, NULL, "Could not create tunnel."); + + /* Arrange for the tunnel agent to get a SIGKILL on pool + * cleanup. This is a little extreme, but the alternatives + * weren't working out: + * - Closing the pipes and waiting for the process to die + * was prone to mysterious hangs which are difficult to + * diagnose (e.g. svnserve dumps core due to unrelated bug; + * sshd goes into zombie state; ssh connection is never + * closed; ssh never terminates). + * - Killing the tunnel agent with SIGTERM leads to unsightly + * stderr output from ssh. + */ + apr_pool_note_subprocess(pool, proc, APR_KILL_ALWAYS); + + /* APR pipe objects inherit by default. But we don't want the + * tunnel agent's pipes held open by future child processes + * (such as other ra_svn sessions), so turn that off. */ + apr_file_inherit_unset(proc->in); + apr_file_inherit_unset(proc->out); + + /* Guard against dotfile output to stdout on the server. */ + *conn = svn_ra_svn_create_conn(NULL, proc->out, proc->in, pool); + (*conn)->proc = proc; + SVN_ERR(svn_ra_svn_skip_leading_garbage(*conn, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t *ra_svn_open(void **baton, const char *url, const svn_ra_callbacks_t *callbacks, void *callback_baton, apr_hash_t *config, apr_pool_t *pool) { + ra_svn_session_baton_t *sess; svn_ra_svn_conn_t *conn; apr_socket_t *sock; - const char *hostname, *user, *status, *tunnel, *realmstring, **args; + const char *hostname, *user, *tunnel, **args; unsigned short port; apr_uint64_t minver, maxver; - apr_array_header_t *mechlist, *caplist, *status_param; - apr_procattr_t *attr; - apr_proc_t *proc; - apr_status_t apr_status; - svn_auth_iterstate_t *iterstate; - svn_error_t *err; - void *creds; + apr_array_header_t *mechlist, *caplist; if (parse_url(url, &tunnel, &user, &port, &hostname, pool) != 0) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, @@ -388,43 +531,7 @@ if (tunnel) { SVN_ERR(find_tunnel_agent(tunnel, hostname, &args, config, pool)); - apr_status = apr_procattr_create(&attr, pool); - if (apr_status == APR_SUCCESS) - apr_status = apr_procattr_io_set(attr, 1, 1, 0); - if (apr_status == APR_SUCCESS) - apr_status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); - if (apr_status == APR_SUCCESS) - apr_status = apr_procattr_child_errfn_set(attr, - handle_child_process_error); - proc = apr_palloc(pool, sizeof(*proc)); - if (apr_status == APR_SUCCESS) - apr_status = apr_proc_create(proc, *args, args, NULL, attr, pool); - if (apr_status != APR_SUCCESS) - return svn_error_create(apr_status, NULL, "Could not create tunnel."); - conn = svn_ra_svn_create_conn(NULL, proc->out, proc->in, pool); - conn->proc = proc; - - /* Arrange for the tunnel agent to get a SIGKILL on pool - * cleanup. This is a little extreme, but the alternatives - * weren't working out: - * - Closing the pipes and waiting for the process to die - * was prone to mysterious hangs which are difficult to - * diagnose (e.g. svnserve dumps core due to unrelated bug; - * sshd goes into zombie state; ssh connection is never - * closed; ssh never terminates). - * - Killing the tunnel agent with SIGTERM leads to unsightly - * stderr output from ssh. - */ - apr_pool_note_subprocess(pool, proc, APR_KILL_ALWAYS); - - /* APR pipe objects inherit by default. But we don't want the - * tunnel agent's pipes held open by future child processes - * (such as other ra_svn sessions), so turn that off. */ - apr_file_inherit_unset(proc->in); - apr_file_inherit_unset(proc->out); - - /* Guard against dotfile output to stdout on the server. */ - svn_ra_svn_skip_leading_garbage(conn, pool); + SVN_ERR(make_tunnel(args, &conn, pool)); } else { @@ -435,104 +542,100 @@ /* Read server's greeting. */ SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "nnll", &minver, &maxver, &mechlist, &caplist)); - /* We only support protocol version 1. */ - if (minver > 1) + /* We support protocol versions 1 and 2. */ + if (minver > 2) return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, "Server requires minimum version %d", (int) minver); - if (tunnel && find_mech(mechlist, "EXTERNAL")) + + sess = apr_palloc(pool, sizeof(*sess)); + sess->conn = conn; + sess->protocol_version = (maxver > 2) ? 2 : maxver; + sess->is_tunneled = (tunnel != NULL); + sess->auth_baton = callbacks->auth_baton; + sess->user = user; + sess->realm_prefix = apr_psprintf(pool, "", hostname, port); + + /* In protocol version 2, we send back our protocol version, our + * capability list, and the URL, and subsequently there is an auth + * request. In version 1, we send back the protocol version, auth + * mechanism, mechanism initial response, and capability list, and; + * then send the URL after authentication. do_auth temporarily has + * support for the mixed-style response. */ + /* When we punt support for protocol version 1, we should: + * - Eliminate this conditional and the similar one below + * - Remove v1 support from auth_response and inline it into do_auth + * - Remove the (realm == NULL) support from do_auth + * - Inline do_auth into handle_auth_request + * - Remove the protocol version check from handle_auth_request */ + if (sess->protocol_version == 1) { - /* Ask the server to use the ssh connection environment (on - * Unix, that means uid) to determine the authentication name. */ - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "nw(c)()", (apr_uint64_t) 1, - "EXTERNAL", "")); + SVN_ERR(do_auth(sess, mechlist, NULL, pool)); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c", url)); } - else if (find_mech(mechlist, "ANONYMOUS")) + else { - if (!user) - { - realmstring = apr_psprintf(pool, "", hostname, port); - - err = svn_auth_first_credentials(&creds, &iterstate, - SVN_AUTH_CRED_USERNAME, realmstring, - callbacks->auth_baton, pool); - if (err) - svn_error_clear(err); - else if (creds) - user = ((svn_auth_cred_username_t *) creds)->username; - } - - /* We send along whatever username we've got as the mechanism argument, - * and if the server wants, it can make use of that when committing - * changes. */ - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "nw(c)()", (apr_uint64_t) 1, - "ANONYMOUS", user ? user : "")); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n()c", + (apr_uint64_t) 2, url)); + SVN_ERR(handle_auth_request(sess, pool)); } - else - return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, - "Cannot negotiate authentication mechanism"); - /* Write client response to greeting, picking version 1 and the - * anonymous authentication mechanism with an empty argument. */ - - /* Read the server's challenge. Since we're only doing anonymous or - * external authentication, the only expected answer is a success - * notification with no parameter. */ - SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "wl", &status, &status_param)); - if (strcmp(status, "success") != 0) - return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, - "Unexpected server response to authentication"); - /* This is where the security layer would go into effect if we * supported security layers, which is a ways off. */ - /* Send URL to server and read UUID response. */ - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c", url)); + /* Read the repository's uuid. */ SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "c?c", &conn->uuid, &conn->repos_root)); - *sess = conn; + *baton = sess; return SVN_NO_ERROR; } -static svn_error_t *ra_svn_get_latest_rev(void *sess, svn_revnum_t *rev, +static svn_error_t *ra_svn_get_latest_rev(void *baton, svn_revnum_t *rev, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-latest-rev", "")); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev)); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_get_dated_rev(void *sess, svn_revnum_t *rev, +static svn_error_t *ra_svn_get_dated_rev(void *baton, svn_revnum_t *rev, apr_time_t tm, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-dated-rev", "c", svn_time_to_cstring(tm, pool))); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev)); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_change_rev_prop(void *sess, svn_revnum_t rev, +static svn_error_t *ra_svn_change_rev_prop(void *baton, svn_revnum_t rev, const char *name, const svn_string_t *value, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "change-rev-prop", "rcs", rev, name, value)); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_get_uuid(void *sess, const char **uuid, +static svn_error_t *ra_svn_get_uuid(void *baton, const char **uuid, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; *uuid = conn->uuid; return SVN_NO_ERROR; @@ -550,25 +653,29 @@ return SVN_NO_ERROR; } -static svn_error_t *ra_svn_rev_proplist(void *sess, svn_revnum_t rev, +static svn_error_t *ra_svn_rev_proplist(void *baton, svn_revnum_t rev, apr_hash_t **props, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; apr_array_header_t *proplist; SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-proplist", "r", rev)); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &proplist)); SVN_ERR(parse_proplist(proplist, pool, props)); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_rev_prop(void *sess, svn_revnum_t rev, +static svn_error_t *ra_svn_rev_prop(void *baton, svn_revnum_t rev, const char *name, svn_string_t **value, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-prop", "rc", rev, name)); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?s)", value)); return SVN_NO_ERROR; } @@ -580,7 +687,8 @@ const char *committed_date; const char *committed_author; - SVN_ERR(svn_ra_svn_read_tuple(ccb->conn, ccb->pool, "r(?c)(?c)", + SVN_ERR(handle_auth_request(ccb->sess, ccb->pool)); + SVN_ERR(svn_ra_svn_read_tuple(ccb->sess->conn, ccb->pool, "r(?c)(?c)", &new_rev, &committed_date, &committed_author)); @@ -590,7 +698,7 @@ } -static svn_error_t *ra_svn_commit(void *sess, +static svn_error_t *ra_svn_commit(void *baton, const svn_delta_editor_t **editor, void **edit_baton, const char *log_msg, @@ -598,16 +706,18 @@ void *callback_baton, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; ra_svn_commit_callback_baton_t *ccb; /* Tell the server we're starting the commit. */ SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "commit", "c", log_msg)); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); /* Remember a few arguments for when the commit is over. */ ccb = apr_palloc(pool, sizeof(*ccb)); - ccb->conn = conn; + ccb->sess = sess; ccb->pool = pool; ccb->callback = callback; ccb->callback_baton = callback_baton; @@ -620,13 +730,14 @@ return SVN_NO_ERROR; } -static svn_error_t *ra_svn_get_file(void *sess, const char *path, +static svn_error_t *ra_svn_get_file(void *baton, const char *path, svn_revnum_t rev, svn_stream_t *stream, svn_revnum_t *fetched_rev, apr_hash_t **props, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; svn_ra_svn_item_t *item; apr_array_header_t *proplist; unsigned char digest[MD5_DIGESTSIZE]; @@ -635,6 +746,7 @@ SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-file", "c(?r)bb", path, rev, (props != NULL), (stream != NULL))); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?c)rl", &expected_checksum, &rev, &proplist)); @@ -687,13 +799,14 @@ return SVN_NO_ERROR; } -static svn_error_t *ra_svn_get_dir(void *sess, const char *path, +static svn_error_t *ra_svn_get_dir(void *baton, const char *path, svn_revnum_t rev, apr_hash_t **dirents, svn_revnum_t *fetched_rev, apr_hash_t **props, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; svn_revnum_t crev; apr_array_header_t *proplist, *dirlist; int i; @@ -705,6 +818,7 @@ SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-dir", "c(?r)bb", path, rev, (props != NULL), (dirents != NULL))); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "rll", &rev, &proplist, &dirlist)); @@ -742,14 +856,15 @@ } -static svn_error_t *ra_svn_update(void *sess, +static svn_error_t *ra_svn_update(void *baton, const svn_ra_reporter_t **reporter, void **report_baton, svn_revnum_t rev, const char *target, svn_boolean_t recurse, const svn_delta_editor_t *update_editor, void *update_baton, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; if (target == NULL) target = ""; @@ -757,15 +872,16 @@ /* Tell the server we want to start an update. */ SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "update", "(?r)cb", rev, target, recurse)); + SVN_ERR(handle_auth_request(sess, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * update_editor upon finish_report(). */ - ra_svn_get_reporter(conn, pool, update_editor, update_baton, + ra_svn_get_reporter(sess, pool, update_editor, update_baton, reporter, report_baton); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_switch(void *sess, +static svn_error_t *ra_svn_switch(void *baton, const svn_ra_reporter_t **reporter, void **report_baton, svn_revnum_t rev, const char *target, svn_boolean_t recurse, @@ -773,7 +889,8 @@ const svn_delta_editor_t *update_editor, void *update_baton, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; if (target == NULL) target = ""; @@ -781,15 +898,16 @@ /* Tell the server we want to start a switch. */ SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "switch", "(?r)cbc", rev, target, recurse, switch_url)); + SVN_ERR(handle_auth_request(sess, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * update_editor upon finish_report(). */ - ra_svn_get_reporter(conn, pool, update_editor, update_baton, + ra_svn_get_reporter(sess, pool, update_editor, update_baton, reporter, report_baton); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_status(void *sess, +static svn_error_t *ra_svn_status(void *baton, const svn_ra_reporter_t **reporter, void **report_baton, const char *target, svn_revnum_t rev, @@ -797,23 +915,25 @@ const svn_delta_editor_t *status_editor, void *status_baton, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; if (target == NULL) target = ""; /* Tell the server we want to start a status operation. */ - SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)", + SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)", target, recurse, rev)); + SVN_ERR(handle_auth_request(sess, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * status_editor upon finish_report(). */ - ra_svn_get_reporter(conn, pool, status_editor, status_baton, + ra_svn_get_reporter(sess, pool, status_editor, status_baton, reporter, report_baton); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_diff(void *sess, +static svn_error_t *ra_svn_diff(void *baton, const svn_ra_reporter_t **reporter, void **report_baton, svn_revnum_t rev, const char *target, @@ -823,7 +943,8 @@ const svn_delta_editor_t *diff_editor, void *diff_baton, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; if (target == NULL) target = ""; @@ -831,22 +952,24 @@ /* Tell the server we want to start a diff. */ SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "diff", "(?r)cbbc", rev, target, recurse, ignore_ancestry, versus_url)); + SVN_ERR(handle_auth_request(sess, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * diff_editor upon finish_report(). */ - ra_svn_get_reporter(conn, pool, diff_editor, diff_baton, + ra_svn_get_reporter(sess, pool, diff_editor, diff_baton, reporter, report_baton); return SVN_NO_ERROR; } -static svn_error_t *ra_svn_log(void *sess, const apr_array_header_t *paths, +static svn_error_t *ra_svn_log(void *baton, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, svn_boolean_t discover_changed_paths, svn_boolean_t strict_node_history, svn_log_message_receiver_t receiver, void *receiver_baton, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; apr_pool_t *subpool; int i; const char *path, *author, *date, *message, *cpath, *action, *copy_path; @@ -867,6 +990,7 @@ } SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bb)", start, end, discover_changed_paths, strict_node_history)); + SVN_ERR(handle_auth_request(sess, pool)); /* Read the log messages. */ subpool = svn_pool_create(pool); @@ -915,14 +1039,16 @@ return SVN_NO_ERROR; } -static svn_error_t *ra_svn_check_path(void *sess, const char *path, +static svn_error_t *ra_svn_check_path(void *baton, const char *path, svn_revnum_t rev, svn_node_kind_t *kind, apr_pool_t *pool) { - svn_ra_svn_conn_t *conn = sess; + ra_svn_session_baton_t *sess = baton; + svn_ra_svn_conn_t *conn = sess->conn; const char *kind_word; SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "check-path", "c(?r)", path, rev)); + SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word)); SVN_ERR(interpret_kind(kind_word, pool, kind)); return SVN_NO_ERROR; Index: libsvn_ra_svn/protocol =================================================================== --- libsvn_ra_svn/protocol (revision 7471) +++ libsvn_ra_svn/protocol (working copy) @@ -1,6 +1,8 @@ -Syntactic structure -------------------- +This file documents version 2 of the svn protocol. +1. Syntactic structure +---------------------- + The Subversion protocol is specified in terms of the following syntactic elements, specified using ABNF [RFC 2234]: @@ -67,43 +69,53 @@ The interpretation of parameters in a successful command response is context-dependent. -Connection establishment and protocol setup -------------------------------------------- +2. Connection establishment and protocol setup +---------------------------------------------- By default, the client connects to the server on port 3690. Upon receiving a connection, the server sends a greeting, using a command response whose parameters match the prototype: - greeting: ( minver:number maxver:number - ( mech:word ... ) ( cap:word ... ) ) + greeting: ( minver:number maxver:number mechs:list ( cap:word ... ) ) minver and maxver give the minimum and maximum Subversion protocol -versions supported by the server. The mech values give a list of SASL -[RFC 2222] mechanisms supported by the server. The cap values give a -list of server capabilities; no capabilities are currently defined. +versions supported by the server. mechs is present for historical +reasons, and is ignored by the client. The cap values give a list of +server capabilities; no capabilities are currently defined. If the client does not support a protocol version within the specified range, or does not support any of the specified SASL mechanisms, it closes the connection. Otherwise, the client responds to the greeting with an item matching the prototype: - response: ( version:number mech:word [ mecharg:string ] ( cap:word ... ) ) + response: ( version:number ( cap:word ... ) url:string ) -version gives the protocol version selected by the client, mech gives -the SASL authentication mechanism, and mecharg gives the initial -response for the authentication exchange. mecharg must be omitted if -the selected mechanism does not begin with an initial client response. -(Calling this an "initial response" is confusing, but technically -correct; SASL talks in terms of server challenges and client -responses, even when the mechanism begins with information from the -client or ends with information from the server.) The cap values give -a list of client capabilities; no capabilities are currently defined. +version gives the protocol version selected by the client. The cap +values give a list of client capabilities; no capabilities are +currently defined. url gives the URL the client is accessing. Upon receiving the client's response to the greeting, the server sends -a series of challenges. Each challenge is a tuple matching the -prototype: +an authentication request, which is a command response whose arguments +match the prototype: + auth-request: ( ( mech:word ... ) realm:string ) + +The mech values give a list of SASL mechanisms supported by the +server. The realm string is similar to an HTTP authentication realm +as defined in [RFC 2617]; it allows the server to indicate which of +several protection spaces the server wishes to authenticate in. If +the mechanism list is empty, then no authentication is required and no +further action takes place as part of the authentication challenge; +otherwise, the client responds with a tuple matching the prototype: + + auth-response: ( mech:word [ token:string ] ) + +mech specifies the SASL mechanism and token, if present, gives the +"initial response" of the authentication exchange. Upon receiving the +client's auth-response, the server sends a series of challenges, each +a tuple matching the prototype: + challenge: ( step ( token:string ) ) | ( failure ( message:string ) ) | ( success [ token:string ] ) @@ -119,24 +131,21 @@ authentication mechanism, but there is no response. If the first word of the challenge is "failure", the authentication -exchange is unsuccessful. After sending a failure notification, the -server must close the connection, as no further action is possible. +exchange is unsuccessful. The client may then give up, or make +another auth-response and restart the authentication process. RFC 2222 requires that a protocol profile define a service name for the sake of the GSSAPI mechanism. The service name for this protocol is "svn". -Upon receiving a success notification from the server, the security -layer (if any) goes into effect, starting with the next message from -the client. The client passes its URL to the server in a tuple -matching the prototype: +After a successful authentication exchange, the server sends a command +response whose parameters match the prototype: - url: ( client-url:string ) + repos-info: ( uuid:string repos-url:string ) -The server decomposes the client url into repository path and root fs -path, and sends back either a successful command response with no -parameters or an error response as appropriate. (See section 3.) The -client may now begin sending commands from the main command set. +uuid gives the universal unique identifier of the repository, and +repos-url gives the URL of the repository's root directory. The +client can now begin sending commands from the main command set. 3. Commands @@ -168,7 +177,12 @@ 3.1.1. Main Command Set -The main command set corresponds to the svn_ra interfaces. +The main command set corresponds to the svn_ra interfaces. After each +main command is issued by the client, the server sends an auth-request +as described in section 2. (If no new authentication is required, the +auth-reuqest contains an empty mechanism list, and the server proceeds +immediately to sending the command response.) Some commands include a +second place for auth-request point as noted below. get-latest-rev params: ( ) @@ -194,7 +208,8 @@ params: ( logmsg:string ) response: ( ) Upon receiving response, client switches to editor command set. - Upon successful completion of edit, server sends commit-info. + Upon successful completion of edit, server sends auth-request. + After auth exchange completes, server sends commit-info. commit-info: ( new-rev:number date:string author:string ) get-file @@ -215,25 +230,33 @@ update params: ( [ rev:number ] target:string recurse:bool ) Client switches to report command set. - Upon finish-report, server switches to editor command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. response: ( ) switch params: ( [ rev:number ] target:string recurse:bool url:string ) Client switches to report command set. - Upon finish-report, server switches to editor command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. response: ( ) status params: ( target:string recurse:bool ) Client switches to report command set. - Upon finish-report, server switches to editor command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. response: ( ) diff params: ( [ rev:number ] target:string recurse:bool url:string ) Client switches to report command set. - Upon finish-report, server switches to editor command set. + Upon finish-report, server sends auth-request. + After auth exchange completes, server switches to editor command set. + After edit completes, server sends response. response: ( ) log Index: svnserve/serve.c =================================================================== --- svnserve/serve.c (revision 7471) +++ svnserve/serve.c (working copy) @@ -50,6 +50,7 @@ const char *user; svn_boolean_t read_only; /* Disallow commit and change-rev-prop */ svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */ + int protocol_version; } server_baton_t; typedef struct { @@ -59,6 +60,7 @@ } commit_callback_baton_t; typedef struct { + server_baton_t *sb; const char *repos_url; void *report_baton; svn_error_t *err; @@ -84,6 +86,15 @@ return SVN_NO_ERROR; } +/* Send a trivial auth request, listing no mechanisms. */ +static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, server_baton_t *b) +{ + if (b->protocol_version < 2) + return SVN_NO_ERROR; + return svn_ra_svn_write_cmd_response(conn, pool, "()c", ""); +} + /* --- REPORTER COMMAND SET --- */ /* To allow for pipelining, reporter commands have no reponses. If we @@ -143,6 +154,7 @@ report_driver_baton_t *b = baton; /* No arguments to parse. */ + SVN_ERR(trivial_auth_request(conn, pool, b->sb)); if (!b->err) b->err = svn_repos_finish_report(b->report_baton); return SVN_NO_ERROR; @@ -191,6 +203,7 @@ recurse, ignore_ancestry, editor, edit_baton, pool)); + rb.sb = b; rb.repos_url = b->repos_url; rb.report_baton = report_baton; rb.err = NULL; @@ -302,6 +315,7 @@ server_baton_t *b = baton; svn_revnum_t rev; + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev)); return SVN_NO_ERROR; @@ -316,6 +330,7 @@ const char *timestr; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", ×tr)); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool)); SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool)); SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev)); @@ -331,6 +346,7 @@ svn_string_t *value; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rcs", &rev, &name, &value)); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(must_not_be_read_only(b)); SVN_CMD_ERR(svn_repos_fs_change_rev_prop(b->repos, rev, b->user, name, value, pool)); @@ -346,6 +362,7 @@ apr_hash_t *props; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev)); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_fs_revision_proplist(&props, b->fs, rev, pool)); SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success")); SVN_ERR(write_proplist(conn, pool, props)); @@ -362,6 +379,7 @@ svn_string_t *value; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name)); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_fs_revision_prop(&value, b->fs, rev, name, pool)); SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(?s)", value)); return SVN_NO_ERROR; @@ -390,6 +408,7 @@ svn_revnum_t new_rev; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg)); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(must_not_be_read_only(b)); ccb.new_rev = &new_rev; ccb.date = &date; @@ -400,8 +419,11 @@ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted)); if (!aborted) - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)", - new_rev, date, author)); + { + SVN_ERR(trivial_auth_request(conn, pool, b)); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)", + new_rev, date, author)); + } return SVN_NO_ERROR; } @@ -424,6 +446,7 @@ /* Parse arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev, &want_props, &want_contents)); + SVN_ERR(trivial_auth_request(conn, pool, b)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); full_path = svn_path_join(b->fs_path, path, pool); @@ -496,6 +519,7 @@ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev, &want_props, &want_contents)); + SVN_ERR(trivial_auth_request(conn, pool, b)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); full_path = svn_path_join(b->fs_path, path, pool); @@ -591,6 +615,7 @@ /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb", &rev, &target, &recurse)); + SVN_ERR(trivial_auth_request(conn, pool, b)); if (svn_path_is_empty(target)) target = NULL; /* ### Compatibility hack, shouldn't be needed */ if (!SVN_IS_VALID_REVNUM(rev)) @@ -611,6 +636,7 @@ /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc", &rev, &target, &recurse, &switch_url)); + SVN_ERR(trivial_auth_request(conn, pool, b)); if (svn_path_is_empty(target)) target = NULL; /* ### Compatibility hack, shouldn't be needed */ if (!SVN_IS_VALID_REVNUM(rev)) @@ -632,6 +658,7 @@ /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)", &target, &recurse, &rev)); + SVN_ERR(trivial_auth_request(conn, pool, b)); if (svn_path_is_empty(target)) target = NULL; /* ### Compatibility hack, shouldn't be needed */ if (!SVN_IS_VALID_REVNUM(rev)) @@ -651,6 +678,7 @@ /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbc", &rev, &target, &recurse, &ignore_ancestry, &versus_url)); + SVN_ERR(trivial_auth_request(conn, pool, b)); if (svn_path_is_empty(target)) target = NULL; /* ### Compatibility hack, shouldn't be needed */ if (!SVN_IS_VALID_REVNUM(rev)) @@ -723,6 +751,7 @@ full_path = svn_path_join(b->fs_path, elt->u.string->data, pool); *((const char **) apr_array_push(full_paths)) = full_path; } + SVN_ERR(trivial_auth_request(conn, pool, b)); /* Get logs. (Can't report errors back to the client at this point.) */ lb.fs_path = b->fs_path; @@ -752,6 +781,7 @@ svn_node_kind_t kind; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev)); + SVN_ERR(trivial_auth_request(conn, pool, b)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); full_path = svn_path_join(b->fs_path, path, pool); @@ -848,35 +878,29 @@ return SVN_NO_ERROR; } -svn_error_t *serve(svn_ra_svn_conn_t *conn, const char *root, - svn_boolean_t tunnel, svn_boolean_t read_only, - svn_boolean_t believe_username, apr_pool_t *pool) +static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + svn_boolean_t tunnel) { - svn_error_t *err, *io_err; - apr_uint64_t ver; - const char *mech, *mecharg, *user = NULL, *client_url, *repos_url, *fs_path; - apr_array_header_t *caplist; - svn_repos_t *repos; - server_baton_t b; - const char *uuid; - svn_boolean_t valid_mech = FALSE; - - /* Send greeting, saying we only support protocol version 1, the - * anonymous authentication mechanism, and no extensions. */ - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(nn(!", "success", - (apr_uint64_t) 1, (apr_uint64_t) 1)); SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS")); #if APR_HAS_USER if (tunnel) SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL")); #endif - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)())")); + return SVN_NO_ERROR; +} - /* Read client response. This should specify version 1, the - * mechanism, a mechanism argument, and possibly some - * capabilities. */ - SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "nw(?c)l", &ver, &mech, &mecharg, - &caplist)); +/* Authenticate, once the client has chosen a mechanism and possibly + * sent an initial mechanism token. On success, set *success to true + * and *user to the authenticated username (or NULL for anonymous). + * On authentication failure, report failure to the client and set + * *success to FALSE. On communications failure, return an error. */ +static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + svn_boolean_t tunnel, svn_boolean_t believe_username, + const char *mech, const char *mecharg, + svn_boolean_t *success, const char **user) +{ + *success = FALSE; + *user = NULL; #if APR_HAS_USER if (tunnel && strcmp(mech, "EXTERNAL") == 0) @@ -885,64 +909,119 @@ apr_gid_t gid; if (!mecharg) /* Must be present */ - return SVN_NO_ERROR; + return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", + "Mechanism argument must be present"); if (apr_uid_current(&uid, &gid, pool) != APR_SUCCESS - || apr_uid_name_get((char **) &user, uid, pool) != APR_SUCCESS) - { - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", - "Can't determine username")); - return SVN_NO_ERROR; - } - if (*mecharg && strcmp(mecharg, user) != 0) - { - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", - "Requested username does not match")); - return SVN_NO_ERROR; - } - valid_mech = TRUE; + || apr_uid_name_get((char **) user, uid, pool) != APR_SUCCESS) + return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", + "Can't determine username"); + if (*mecharg && strcmp(mecharg, *user) != 0) + return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", + "Requested username does not match"); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success")); + *success = TRUE; + return SVN_NO_ERROR; } #endif if (strcmp(mech, "ANONYMOUS") == 0) { if (believe_username && mecharg && *mecharg) - user = mecharg; - - valid_mech = TRUE; + *user = mecharg; + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success")); + *success = TRUE; + return SVN_NO_ERROR; } - if (!valid_mech) /* Client gave us an unlisted mech. */ - return SVN_NO_ERROR; - /* Write back a success notification. */ - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success")); + return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", + "Must authenticate with listed mechanism"); +} - /* This is where the security layer would go into effect if we - * supported security layers, which is a ways off. */ +svn_error_t *serve(svn_ra_svn_conn_t *conn, const char *root, + svn_boolean_t tunnel, svn_boolean_t read_only, + svn_boolean_t believe_username, apr_pool_t *pool) +{ + svn_error_t *err, *io_err; + apr_uint64_t ver; + const char *mech, *mecharg; + apr_array_header_t *caplist; + server_baton_t b; + const char *uuid; + svn_boolean_t success; + svn_ra_svn_item_t *item, *first; - /* Read client's URL. */ - SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "c", &client_url)); + b.read_only = read_only; + b.user = NULL; - err = find_repos(client_url, root, &repos, &repos_url, &fs_path, pool); - if (err) + /* Send greeting. When we drop support for version 1, we can + * start sending an empty mechlist. */ + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(nn(!", "success", + (apr_uint64_t) 1, (apr_uint64_t) 2)); + SVN_ERR(send_mechs(conn, pool, tunnel)); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)())")); + + /* Read client response. Because the client response form changed + * between version 1 and version 2, we have to do some of this by + * hand until we punt support for version 1. */ + SVN_ERR(svn_ra_svn_read_item(conn, pool, &item)); + if (item->kind != SVN_RA_SVN_LIST || item->u.list->nelts < 2) + return SVN_NO_ERROR; + first = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t); + if (first->kind != SVN_RA_SVN_NUMBER) + return SVN_NO_ERROR; + b.protocol_version = first->u.number; + if (b.protocol_version == 1) { - io_err = svn_ra_svn_write_cmd_failure(conn, pool, err); - svn_error_clear(err); - if (io_err) - return io_err; - SVN_ERR(svn_ra_svn_flush(conn, pool)); - return SVN_NO_ERROR; + /* Version 1: auth exchange is mixed with client version and + * capability list, and happens before the client URL is received. */ + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "nw(?c)l", + &ver, &mech, &mecharg, &caplist)); + SVN_ERR(auth(conn, pool, tunnel, believe_username, mech, mecharg, + &success, &b.user)); + if (!success) + return svn_ra_svn_flush(conn, pool); + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "c", &b.url)); + err = find_repos(b.url, root, &b.repos, &b.repos_url, &b.fs_path, pool); + if (err) + { + io_err = svn_ra_svn_write_cmd_failure(conn, pool, err); + svn_error_clear(err); + SVN_ERR(io_err); + return svn_ra_svn_flush(conn, pool); + } + b.fs = svn_repos_fs(b.repos); } - - b.repos = repos; - b.url = client_url; - b.repos_url = repos_url; - b.fs_path = fs_path; - b.user = user; - b.fs = svn_repos_fs(repos); - b.read_only = read_only; + else if (b.protocol_version == 2) + { + /* Version 2: client sends version, capability list, and client + * URL, and then we do an auth request. */ + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "nlc", &ver, + &caplist, &b.url)); + err = find_repos(b.url, root, &b.repos, &b.repos_url, &b.fs_path, pool); + if (err) + { + io_err = svn_ra_svn_write_cmd_failure(conn, pool, err); + svn_error_clear(err); + SVN_ERR(io_err); + return svn_ra_svn_flush(conn, pool); + } + b.fs = svn_repos_fs(b.repos); + do + { + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success")); + SVN_ERR(send_mechs(conn, pool, tunnel)); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", "")); + SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg)); + SVN_ERR(auth(conn, pool, tunnel, believe_username, mech, mecharg, + &success, &b.user)); + } + while (!success); + } + else + return SVN_NO_ERROR; SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool)); - svn_ra_svn_write_cmd_response(conn, pool, "cc", uuid, repos_url); + svn_ra_svn_write_cmd_response(conn, pool, "cc", uuid, b.repos_url); return svn_ra_svn_handle_commands(conn, pool, main_commands, &b); }