[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

naive authentication scheme for ra_svn

From: Garrett Rooney <rooneg_at_electricjellyfish.net>
Date: 2003-09-13 23:49:00 CEST

It's always bugged me that there was no way to use ra_svn to write to a
repository and have usernames show up correctly for commits (at least
without tunneling it over ssh). I mean there are a number of situations
where it would be useful to be able to just run svnserve in daemon mode
(on a private network, or with data where more stringent security is not
required), authenticating via something equivalent to http basic auth.
The protocol even has provisions for such a thing, but we haven't had
time to implement it.

Well, I was playing around this weekend, and I hacked something together
to do just that. It implements a new SASL mechanism, 'NAIVE', which has
the user send a username and an md5 encoded password over the wire to
the server, which authenticates them against a simple tab delimited file.

This is in no way ready to be committed, as if we're going to have some
kind of authentication scheme built in it should be something a bit more
real, perhaps digest auth from RFC 2831 or something. The current
scheme is horribly insecure, vulnerable to all manner of attacks, and it
wouldn't be all that much harder to do better. It also has no means for
retrying after a failure to authenticate (so the user can specify a new
username, as we do with ra_dav), and doesn't try to write auth data to
disk when authentication is successful. Also, the code could stand to
be cleaned up a bit in some places.

Still, I figured people might like to take a look at it, and if someone
is looking to implement digest auth, it might provide a good starting point.

-garrett

Index: subversion/libsvn_ra_svn/client.c
===================================================================
--- subversion/libsvn_ra_svn/client.c (revision 7056)
+++ subversion/libsvn_ra_svn/client.c (working copy)
@@ -36,6 +36,7 @@
 #include "svn_ra.h"
 #include "svn_ra_svn.h"
 #include "svn_md5.h"
+#include "svn_auth.h"
 
 #include "ra_svn.h"
 
@@ -377,6 +378,7 @@
   apr_procattr_t *attr;
   apr_proc_t *proc;
   apr_status_t apr_status;
+ svn_boolean_t take_last_chance = TRUE;
 
   if (parse_url(url, &tunnel, &user, &port, &hostname, pool) != 0)
     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
@@ -439,26 +441,108 @@
                              (int) minver);
   if (tunnel && find_mech(mechlist, "EXTERNAL"))
     {
+ take_last_chance = FALSE;
+
       /* 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", ""));
     }
- else if (find_mech(mechlist, "ANONYMOUS"))
+ else if (find_mech(mechlist, "NAIVE"))
     {
- SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "nw(c)()", (apr_uint64_t) 1,
- "ANONYMOUS", ""));
+ const char *pass = NULL;
+ svn_auth_cred_simple_t *simple_creds;
+ svn_auth_iterstate_t *iterstate;
+ void *creds;
+ const char *realmstring;
+ const char *portstring = apr_psprintf (pool, "%d", port);
+
+ /* <svn://svn.collab.net:3690> */
+ realmstring = apr_pstrcat(pool, "<", "svn://", hostname, ":",
+ portstring, "> ", NULL);
+
+ SVN_ERR (svn_auth_first_credentials (&creds, &iterstate,
+ SVN_AUTH_CRED_SIMPLE,
+ realmstring,
+ callbacks->auth_baton,
+ pool));
+ simple_creds = creds;
+
+ if (simple_creds->username)
+ user = simple_creds->username;
+ if (simple_creds->password)
+ pass = simple_creds->password;
+
+ if (user && pass)
+ {
+ take_last_chance = FALSE;
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "nw(c)()",
+ (apr_uint64_t) 1, "NAIVE", user));
+
+ for (;;)
+ {
+ const char *word, *arg;
+
+ SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &word, &arg));
+
+ if (strcmp(word, "step") == 0)
+ {
+ if (strcmp(arg, "md5") == 0)
+ {
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+ const char *cdigest;
+
+ apr_md5(digest, pass, strlen(pass));
+
+ cdigest = svn_md5_digest_to_cstring(digest, pool);
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "nw(c)()",
+ (apr_uint64_t) 1, "NAIVE",
+ cdigest));
+ break;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA,
+ NULL,
+ "Didn't expect to get '%s' here",
+ arg);
+ }
+ }
+ else if (strcmp(word, "failure") == 0)
+ {
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ "Naive authentication failed");
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Didn't expect to get '%s' here",
+ word);
+ }
+ }
+ }
     }
- else
- return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
- "Cannot negotiate authentication mechanism");
 
+ if (take_last_chance)
+ {
+ if (find_mech(mechlist, "ANONYMOUS"))
+ {
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "nw(c)()",
+ (apr_uint64_t) 1, "ANONYMOUS", ""));
+ }
+ 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. */
+ /* Read the server's challenge. */
   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,
Index: subversion/svnserve/serve.c
===================================================================
--- subversion/svnserve/serve.c (revision 7056)
+++ subversion/svnserve/serve.c (working copy)
@@ -892,9 +892,49 @@
   return SVN_NO_ERROR;
 }
 
+/* Iterate through the lines in PWFILE looking for one that has a username of
+ USER. If that line's password hash matches PASS, return TRUE in ANSWER,
+ otherwise return FALSE in ANSWER. Use POOL for all allocations. */
+static svn_error_t *
+check_password(svn_boolean_t *answer, const char *pwfile, const char *user,
+ const char *pass, apr_pool_t *pool)
+{
+ apr_size_t len = 1024;
+ apr_status_t apr_err;
+ apr_file_t *file;
+ char line[1024];
+
+ *answer = FALSE;
+
+ apr_err = apr_file_open(&file, pwfile, APR_READ, APR_OS_DEFAULT, pool);
+ if (apr_err)
+ return svn_error_createf(apr_err, NULL, "Couldn't open password file '%s'",
+ pwfile);
+
+ while (svn_io_read_length_line(file, line, &len) != APR_EOF)
+ {
+ char *tab = strchr(line, '\t');
+
+ if (tab && tab < line + strlen(line))
+ {
+ *tab = '\0';
+
+ if (strcmp(user, line) == 0 && strcmp(tab + 1, pass) == 0)
+ {
+ *answer = TRUE;
+ break;
+ }
+ }
+
+ len = 1024; /* reset max length */
+ }
+
+ 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,
- apr_pool_t *pool)
+ const char *pwfile, apr_pool_t *pool)
 {
   svn_error_t *err, *io_err;
   apr_uint64_t ver;
@@ -916,6 +956,8 @@
   SVN_ERR(svn_ra_svn_start_list(conn, pool));
   /* We support anonymous and maybe external authentication. */
   SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS"));
+ if (pwfile)
+ SVN_ERR(svn_ra_svn_write_word(conn, pool, "NAIVE"));
 #if APR_HAS_USER
   if (tunnel)
     SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
@@ -960,7 +1002,35 @@
 
   if (strcmp(mech, "ANONYMOUS") == 0)
     valid_mech = TRUE;
+ else if (pwfile && strcmp(mech, "NAIVE") == 0)
+ {
+ apr_uint64_t version;
+ svn_boolean_t match;
+ const char *pass;
 
+ user = mecharg;
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "step", "md5"));
+
+ SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "nw(c)()", &version, &mech,
+ &pass));
+
+ SVN_ERR(check_password(&match, pwfile, user, pass, pool));
+
+ if (! match)
+ {
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
+ "Password didn't match"));
+
+ /* XXX why do i need this? */
+ SVN_ERR(svn_ra_svn_flush(conn, pool));
+
+ return SVN_NO_ERROR;
+ }
+
+ valid_mech = TRUE;
+ }
+
   if (!valid_mech) /* Client gave us an unlisted mech. */
     return SVN_NO_ERROR;
 
Index: subversion/svnserve/main.c
===================================================================
--- subversion/svnserve/main.c (revision 7056)
+++ subversion/svnserve/main.c (working copy)
@@ -79,7 +79,8 @@
   if (!progname)
     progname = "svn-server";
   fprintf(stderr,
- "Usage: %s [-X|-d|-t|-R" CONNECTION_USAGE "] [-r root]\n", progname);
+ "Usage: %s [-X|-d|-t|-R" CONNECTION_USAGE "] [-r root] [-p pwfile]\n",
+ progname);
   exit(1);
 }
 
@@ -111,6 +112,7 @@
   const char *root;
   svn_ra_svn_conn_t *conn;
   svn_boolean_t read_only;
+ const char *pwfile;
   apr_pool_t *pool;
 };
 
@@ -119,7 +121,8 @@
 {
   struct serve_thread_t *d = data;
 
- svn_error_clear(serve(d->conn, d->root, FALSE, d->read_only, d->pool));
+ svn_error_clear(serve(d->conn, d->root, FALSE, d->read_only, d->pwfile,
+ d->pool));
   svn_pool_destroy(d->pool);
 
   return NULL;
@@ -130,6 +133,7 @@
 {
   svn_boolean_t listen_once = FALSE, daemon_mode = FALSE, tunnel_mode = FALSE;
   svn_boolean_t read_only = FALSE;
+ const char *pwfile = NULL;
   apr_socket_t *sock, *usock;
   apr_file_t *in_file, *out_file;
   apr_sockaddr_t *sa;
@@ -160,7 +164,7 @@
 
   while (1)
     {
- status = apr_getopt(os, "dtXr:R" CONNECTION_OPT, &opt, &arg);
+ status = apr_getopt(os, "dtXr:Rp:" CONNECTION_OPT, &opt, &arg);
       if (APR_STATUS_IS_EOF(status))
         break;
       if (status != APR_SUCCESS)
@@ -192,6 +196,10 @@
         case 'T':
           handling_mode = connection_mode_thread;
           break;
+
+ case 'p':
+ pwfile = arg;
+ break;
         }
     }
   if (os->ind != argc)
@@ -204,7 +212,7 @@
       apr_file_open_stdin(&in_file, pool);
       apr_file_open_stdout(&out_file, pool);
       conn = svn_ra_svn_create_conn(NULL, in_file, out_file, pool);
- svn_error_clear(serve(conn, root, tunnel_mode, read_only, pool));
+ svn_error_clear(serve(conn, root, tunnel_mode, read_only, pwfile, pool));
       exit(0);
     }
 
@@ -275,7 +283,7 @@
 
       if (listen_once)
         {
- err = serve(conn, root, FALSE, read_only, connection_pool);
+ err = serve(conn, root, FALSE, read_only, pwfile, connection_pool);
 
           if (listen_once && err
               && err->apr_err != SVN_ERR_RA_SVN_CONNECTION_CLOSED)
@@ -294,7 +302,7 @@
           if (status == APR_INCHILD)
             {
               svn_error_clear(serve(conn, root, FALSE, read_only,
- connection_pool));
+ pwfile, connection_pool));
               apr_socket_close(usock);
               exit(0);
             }
@@ -334,6 +342,7 @@
           thread_data->conn = conn;
           thread_data->root = root;
           thread_data->read_only = read_only;
+ thread_data->pwfile = pwfile;
           thread_data->pool = connection_pool;
           status = apr_thread_create(&tid, tattr, serve_thread, thread_data,
                                      connection_pool);
@@ -348,7 +357,8 @@
 
         case connection_mode_single:
           /* Serve one connection at a time. */
- svn_error_clear(serve(conn, root, FALSE, read_only, connection_pool));
+ svn_error_clear(serve(conn, root, FALSE, read_only, pwfile,
+ connection_pool));
           svn_pool_destroy(connection_pool);
         }
     }
Index: subversion/svnserve/server.h
===================================================================
--- subversion/svnserve/server.h (revision 7056)
+++ subversion/svnserve/server.h (working copy)
@@ -29,7 +29,7 @@
 
 svn_error_t *serve(svn_ra_svn_conn_t *conn, const char *root,
                    svn_boolean_t tunnel, svn_boolean_t read_only,
- apr_pool_t *pool);
+ const char *pwfile, apr_pool_t *pool);
 
 #ifdef __cplusplus
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat Sep 13 23:49:50 2003

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.