/* Tell the client the authentication failed. This is only used during the authentication exchange (i.e. inside try_auth()). */ static svn_error_t * fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx) { const char *msg = svn_sasl__errdetail(sasl_ctx); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg)); return svn_ra_svn__flush(conn, pool); }
/* Used if we run into a SASL error outside try_auth(). */ static svn_error_t * fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx) { svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, svn_sasl__errdetail(sasl_ctx)); SVN_ERR(write_failure(conn, pool, &err)); return svn_ra_svn__flush(conn, pool); }
svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const svn_delta_editor_t *editor, void *edit_baton, svn_boolean_t *aborted, svn_boolean_t for_replay) { ra_svn_driver_state_t state; apr_pool_t *subpool = svn_pool_create(pool); const char *cmd; int i; svn_error_t *err, *write_err; apr_array_header_t *params; state.editor = editor; state.edit_baton = edit_baton; state.tokens = apr_hash_make(pool); state.aborted = aborted; state.done = FALSE; state.pool = pool; state.file_pool = svn_pool_create(pool); state.file_refs = 0; state.for_replay = for_replay; while (!state.done) { svn_pool_clear(subpool); if (editor) { SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms)); for (i = 0; ra_svn_edit_cmds[i].cmd; i++) if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0) break; if (ra_svn_edit_cmds[i].cmd) err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state); else if (strcmp(cmd, "failure") == 0) { /* While not really an editor command this can occur when reporter->finish_report() fails before the first editor command */ if (aborted) *aborted = TRUE; err = svn_ra_svn__handle_failure_status(params, pool); return svn_error_compose_create( err, editor->abort_edit(edit_baton, subpool)); } else { err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL, _("Unknown editor command '%s'"), cmd); err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL); } } else { const char* command = NULL; SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command)); if (strcmp(command, "close-edit") == 0) { state.done = TRUE; if (aborted) *aborted = FALSE; err = svn_ra_svn__write_cmd_response(conn, pool, ""); } else err = NULL; } if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR) { if (aborted) *aborted = TRUE; if (!state.done) { /* Abort the edit and use non-blocking I/O to write the error. */ if (editor) svn_error_clear(editor->abort_edit(edit_baton, subpool)); svn_ra_svn__set_block_handler(conn, blocked_write, &state); } write_err = svn_ra_svn__write_cmd_failure( conn, subpool, svn_ra_svn__locate_real_error_child(err)); if (!write_err) write_err = svn_ra_svn__flush(conn, subpool); svn_ra_svn__set_block_handler(conn, NULL, NULL); svn_error_clear(err); SVN_ERR(write_err); break; } SVN_ERR(err); } /* Read and discard editing commands until the edit is complete. Hopefully, the other side will call another editor command, run check_for_error, notice the error, write "abort-edit" at us, and throw the error up a few levels on its side (possibly even tossing it right back at us, which is why we can return SVN_NO_ERROR below). However, if the other side is way ahead of us, it might completely finish the edit (or sequence of edit/revprops, for "replay-range") before we send over our "failure". So we should also stop if we see "success". (Then the other side will try to interpret our "failure" as a command, which will itself fail... The net effect is that whatever error we wrote to the other side will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.) */ while (!state.done) { svn_pool_clear(subpool); err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms); if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED) { /* Other side disconnected; that's no error. */ svn_error_clear(err); svn_pool_destroy(subpool); return SVN_NO_ERROR; } svn_error_clear(err); if (strcmp(cmd, "abort-edit") == 0 || strcmp(cmd, "success") == 0) state.done = TRUE; } svn_pool_destroy(subpool); return SVN_NO_ERROR; }
svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b, enum access_type required, svn_boolean_t needs_username) { sasl_conn_t *sasl_ctx; apr_pool_t *subpool; apr_status_t apr_err; const char *localaddrport = NULL, *remoteaddrport = NULL; const char *mechlist; char hostname[APRMAXHOSTLEN + 1]; sasl_security_properties_t secprops; svn_boolean_t success, no_anonymous; int mech_count, result = SASL_OK; SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport, conn, pool)); apr_err = apr_gethostname(hostname, sizeof(hostname), pool); if (apr_err) { svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname")); SVN_ERR(write_failure(conn, pool, &err)); return svn_ra_svn__flush(conn, pool); } /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol supports sending data along with the final "success" message. */ result = svn_sasl__server_new(SVN_RA_SVN_SASL_NAME, hostname, b->repository->realm, localaddrport, remoteaddrport, NULL, SASL_SUCCESS_DATA, &sasl_ctx); if (result != SASL_OK) { svn_error_t *err = svn_error_create( SVN_ERR_RA_NOT_AUTHORIZED, NULL, svn_sasl__errstring(result, NULL, NULL)); SVN_ERR(write_failure(conn, pool, &err)); return svn_ra_svn__flush(conn, pool); } /* Make sure the context is always destroyed. */ apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb, apr_pool_cleanup_null); /* Initialize security properties. */ svn_ra_svn__default_secprops(&secprops); /* Don't allow ANONYMOUS if a username is required. */ no_anonymous = needs_username || b->repository->anon_access < required; if (no_anonymous) secprops.security_flags |= SASL_SEC_NOANONYMOUS; secprops.min_ssf = b->repository->min_ssf; secprops.max_ssf = b->repository->max_ssf; /* Set security properties. */ result = svn_sasl__setprop(sasl_ctx, SASL_SEC_PROPS, &secprops); if (result != SASL_OK) return fail_cmd(conn, pool, sasl_ctx); /* SASL needs to know if we are externally authenticated. */ if (b->client_info->tunnel_user) result = svn_sasl__setprop(sasl_ctx, SASL_AUTH_EXTERNAL, b->client_info->tunnel_user); if (result != SASL_OK) return fail_cmd(conn, pool, sasl_ctx); /* Get the list of mechanisms. */ result = svn_sasl__listmech(sasl_ctx, NULL, NULL, " ", NULL, &mechlist, NULL, &mech_count); if (result != SASL_OK) return fail_cmd(conn, pool, sasl_ctx); if (mech_count == 0) { svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("Could not obtain the list" " of SASL mechanisms")); SVN_ERR(write_failure(conn, pool, &err)); return svn_ra_svn__flush(conn, pool); } /* Send the list of mechanisms and the realm to the client. */ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(w)c", mechlist, b->repository->realm)); /* The main authentication loop. */ subpool = svn_pool_create(pool); do { svn_pool_clear(subpool); SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success)); } while (!success); svn_pool_destroy(subpool); SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool)); if (no_anonymous) { char *p; const void *user; /* Get the authenticated username. */ result = svn_sasl__getprop(sasl_ctx, SASL_USERNAME, &user); if (result != SASL_OK) return fail_cmd(conn, pool, sasl_ctx); if ((p = strchr(user, '@')) != NULL) { /* Drop the realm part. */ b->client_info->user = apr_pstrndup(b->pool, user, p - (const char *)user); } else { svn_error_t *err; err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, _("Couldn't obtain the authenticated" " username")); SVN_ERR(write_failure(conn, pool, &err)); return svn_ra_svn__flush(conn, pool); } } return SVN_NO_ERROR; }
/* Fail the authentication, from the server's perspective. */ static svn_error_t *fail(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *msg) { SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg)); return svn_error_trace(svn_ra_svn__flush(conn, pool)); }