Fixed the parsing of IPv6 literal addresses with a username
[rsync/rsync.git] / options.c
index 4a7516d..0c64eaa 100644 (file)
--- a/options.c
+++ b/options.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org>
  * Copyright (C) 2000, 2001, 2002 Martin Pool <mbp@samba.org>
- * Copyright (C) 2002-2008 Wayne Davison
+ * Copyright (C) 2002-2009 Wayne Davison
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
  */
 
 #include "rsync.h"
-#include "ifuncs.h"
+#include "itypes.h"
 #include <popt.h>
 #include "zlib/zlib.h"
 
@@ -78,7 +78,6 @@ int def_compress_level = Z_DEFAULT_COMPRESSION;
 int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
 int am_server = 0;
 int am_sender = 0;
-int am_generator = 0;
 int am_starting_up = 1;
 int relative_paths = -1;
 int implied_dirs = 1;
@@ -87,7 +86,6 @@ int msgs2stderr = 0;
 int allow_8bit_chars = 0;
 int force_delete = 0;
 int io_timeout = 0;
-int allowed_lull = 0;
 int prune_empty_dirs = 0;
 int use_qsort = 0;
 char *files_from = NULL;
@@ -95,7 +93,7 @@ int filesfrom_fd = -1;
 char *filesfrom_host = NULL;
 int eol_nulls = 0;
 int protect_args = 0;
-int human_readable = 0;
+int human_readable = 1;
 int recurse = 0;
 int allow_inc_recurse = 1;
 int xfer_dirs = -1;
@@ -104,6 +102,7 @@ int connect_timeout = 0;
 int keep_partial = 0;
 int safe_symlinks = 0;
 int copy_unsafe_links = 0;
+int munge_symlinks = 0;
 int size_only = 0;
 int daemon_bwlimit = 0;
 int bwlimit = 0;
@@ -122,7 +121,9 @@ int checksum_seed = 0;
 int inplace = 0;
 int delay_updates = 0;
 long block_size = 0; /* "long" because popt can't set an int32. */
+char number_separator;
 char *skip_compress = NULL;
+item_list dparam_list = EMPTY_ITEM_LIST;
 
 /** Network address family. **/
 int default_af_hint
@@ -166,6 +167,8 @@ char *rsync_path = RSYNC_PATH;
 char *backup_dir = NULL;
 char backup_dir_buf[MAXPATHLEN];
 char *sockopts = NULL;
+char *usermap = NULL;
+char *groupmap = NULL;
 int rsync_port = 0;
 int compare_dest = 0;
 int copy_dest = 0;
@@ -200,10 +203,10 @@ struct chmod_mode_struct *chmod_modes = NULL;
 static const char *debug_verbosity[] = {
        /*0*/ NULL,
        /*1*/ NULL,
-       /*2*/ "bind,cmd,deltasum,connect,del,dup,filter,flist",
+       /*2*/ "bind,cmd,connect,del,deltasum,dup,filter,flist,iconv",
        /*3*/ "acl,backup,deltasum2,del2,exit,filter2,flist2,fuzzy,genr,own,recv,send,time",
-       /*4*/ "cmd2,deltasum3,del3,exit2,flist3,iconv,own2,proto,time2",
-       /*5*/ "chdir,deltasum4,flist4,fuzzy2,hlink",
+       /*4*/ "cmd2,deltasum3,del3,exit2,flist3,iconv2,own2,proto,time2",
+       /*5*/ "chdir,deltasum4,flist4,fuzzy2,hash,hlink",
 };
 
 #define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1)
@@ -211,7 +214,7 @@ static const char *debug_verbosity[] = {
 static const char *info_verbosity[1+MAX_VERBOSITY] = {
        /*0*/ NULL,
        /*1*/ "copy,del,flist,misc,name,stats,symsafe",
-       /*2*/ "backup,misc2,mount,name2,remove,skip",
+       /*2*/ "backup,mount,name2,remove,skip",
 };
 
 #define MAX_OUT_LEVEL 4 /* The largest N allowed for any flagN word. */
@@ -244,7 +247,7 @@ static struct output_struct info_words[COUNT_INFO+1] = {
        INFO_WORD(COPY, W_REC, "Mention files copied locally on the receiving side"),
        INFO_WORD(DEL, W_REC, "Mention deletions on the receiving side"),
        INFO_WORD(FLIST, W_CLI, "Mention file-list receiving/sending (levels 1-2)"),
-       INFO_WORD(MISC, W_SND|W_REC, "Mention miscellaneous information (levels 1-2)"),
+       INFO_WORD(MISC, W_SND|W_REC, "Mention miscellaneous information"),
        INFO_WORD(MOUNT, W_SND|W_REC, "Mention mounts that were found or skipped"),
        INFO_WORD(NAME, W_SND|W_REC, "Mention 1) updated file/dir names, 2) unchanged names"),
        INFO_WORD(PROGRESS, W_CLI, "Mention 1) per-file progress or 2) total transfer progress"),
@@ -272,8 +275,9 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = {
        DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"),
        DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"),
        DEBUG_WORD(GENR, W_REC, "Debug generator functions"),
+       DEBUG_WORD(HASH, W_SND|W_REC, "Debug hashtable code"),
        DEBUG_WORD(HLINK, W_SND|W_REC, "Debug hard-link actions"),
-       DEBUG_WORD(ICONV, W_CLI|W_SRV, "Debug iconv (character conversion)"),
+       DEBUG_WORD(ICONV, W_CLI|W_SRV, "Debug iconv character conversions (levels 1-2)"),
        DEBUG_WORD(OWN, W_REC, "Debug ownership changes in users & groups (levels 1-2)"),
        DEBUG_WORD(PROTO, W_CLI|W_SRV, "Debug protocol information"),
        DEBUG_WORD(RECV, W_REC, "Debug receiver functions"),
@@ -294,6 +298,7 @@ static int refused_delete, refused_archive_part, refused_compress;
 static int refused_partial, refused_progress, refused_delete_before;
 static int refused_delete_during;
 static int refused_inplace, refused_no_iconv;
+static BOOL usermap_via_chown, groupmap_via_chown;
 static char *max_size_arg, *min_size_arg;
 static char tmp_partialdir[] = ".~tmp~";
 
@@ -563,7 +568,8 @@ static void print_rsync_version(enum logcode f)
        STRUCT_STAT *dumstat;
 
 #if SUBPROTOCOL_VERSION != 0
-       asprintf(&subprotocol, ".PR%d", SUBPROTOCOL_VERSION);
+       if (asprintf(&subprotocol, ".PR%d", SUBPROTOCOL_VERSION) < 0)
+               out_of_memory("print_rsync_version");
 #endif
 #ifdef HAVE_SOCKETPAIR
        got_socketpair = "";
@@ -595,7 +601,7 @@ static void print_rsync_version(enum logcode f)
 
        rprintf(f, "%s  version %s  protocol version %d%s\n",
                RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
-       rprintf(f, "Copyright (C) 1996-2008 by Andrew Tridgell, Wayne Davison, and others.\n");
+       rprintf(f, "Copyright (C) 1996-2009 by Andrew Tridgell, Wayne Davison, and others.\n");
        rprintf(f, "Web site: http://rsync.samba.org/\n");
        rprintf(f, "Capabilities:\n");
        rprintf(f, "    %d-bit files, %d-bit inums, %d-bit timestamps, %d-bit long ints,\n",
@@ -671,6 +677,7 @@ void usage(enum logcode F)
   rprintf(F," -L, --copy-links            transform symlink into referent file/dir\n");
   rprintf(F,"     --copy-unsafe-links     only \"unsafe\" symlinks are transformed\n");
   rprintf(F,"     --safe-links            ignore symlinks that point outside the source tree\n");
+  rprintf(F,"     --munge-links           munge symlinks to make them safer (but unusable)\n");
   rprintf(F," -k, --copy-dirlinks         transform symlink to a dir into referent dir\n");
   rprintf(F," -K, --keep-dirlinks         treat symlinked dir on receiver as dir\n");
   rprintf(F," -H, --hard-links            preserve hard links\n");
@@ -721,6 +728,9 @@ void usage(enum logcode F)
   rprintf(F,"     --delay-updates         put all updated files into place at transfer's end\n");
   rprintf(F," -m, --prune-empty-dirs      prune empty directory chains from the file-list\n");
   rprintf(F,"     --numeric-ids           don't map uid/gid values by user/group name\n");
+  rprintf(F,"     --usermap=STRING        custom username mapping\n");
+  rprintf(F,"     --groupmap=STRING       custom groupname mapping\n");
+  rprintf(F,"     --chown=USER:GROUP      simple username/groupname mapping\n");
   rprintf(F,"     --timeout=SECONDS       set I/O timeout in seconds\n");
   rprintf(F,"     --contimeout=SECONDS    set daemon connection timeout in seconds\n");
   rprintf(F," -I, --ignore-times          don't skip files that match in size and mod-time\n");
@@ -785,6 +795,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
       OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
       OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
       OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG,
+      OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN,
       OPT_SERVER, OPT_REFUSED_BASE = 9000};
 
 static struct poptOption long_options[] = {
@@ -856,6 +867,8 @@ static struct poptOption long_options[] = {
   {"copy-links",      'L', POPT_ARG_NONE,   &copy_links, 0, 0, 0 },
   {"copy-unsafe-links",0,  POPT_ARG_NONE,   &copy_unsafe_links, 0, 0, 0 },
   {"safe-links",       0,  POPT_ARG_NONE,   &safe_symlinks, 0, 0, 0 },
+  {"munge-links",      0,  POPT_ARG_VAL,    &munge_symlinks, 1, 0, 0 },
+  {"no-munge-links",   0,  POPT_ARG_VAL,    &munge_symlinks, 0, 0, 0 },
   {"copy-dirlinks",   'k', POPT_ARG_NONE,   &copy_dirlinks, 0, 0, 0 },
   {"keep-dirlinks",   'K', POPT_ARG_NONE,   &keep_dirlinks, 0, 0, 0 },
   {"hard-links",      'H', POPT_ARG_NONE,   0, 'H', 0, 0 },
@@ -963,6 +976,9 @@ static struct poptOption long_options[] = {
   {"no-s",             0,  POPT_ARG_VAL,    &protect_args, 0, 0, 0},
   {"numeric-ids",      0,  POPT_ARG_VAL,    &numeric_ids, 1, 0, 0 },
   {"no-numeric-ids",   0,  POPT_ARG_VAL,    &numeric_ids, 0, 0, 0 },
+  {"usermap",          0,  POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 },
+  {"groupmap",         0,  POPT_ARG_STRING, 0, OPT_GROUPMAP, 0, 0 },
+  {"chown",            0,  POPT_ARG_STRING, 0, OPT_CHOWN, 0, 0 },
   {"timeout",          0,  POPT_ARG_INT,    &io_timeout, 0, 0, 0 },
   {"no-timeout",       0,  POPT_ARG_VAL,    &io_timeout, 0, 0, 0 },
   {"contimeout",       0,  POPT_ARG_INT,    &connect_timeout, 0, 0, 0 },
@@ -994,6 +1010,7 @@ static struct poptOption long_options[] = {
   /* All the following options switch us into daemon-mode option-parsing. */
   {"config",           0,  POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 },
   {"daemon",           0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
+  {"dparam",           0,  POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 },
   {"detach",           0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
   {"no-detach",        0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
   {0,0,0,0, 0, 0, 0}
@@ -1008,6 +1025,7 @@ static void daemon_usage(enum logcode F)
   rprintf(F,"     --address=ADDRESS       bind to the specified address\n");
   rprintf(F,"     --bwlimit=KBPS          limit I/O bandwidth; KBytes per second\n");
   rprintf(F,"     --config=FILE           specify alternate rsyncd.conf file\n");
+  rprintf(F," -M, --dparam=OVERRIDE       override global daemon config parameter\n");
   rprintf(F,"     --no-detach             do not detach from the parent\n");
   rprintf(F,"     --port=PORT             listen on alternate port number\n");
   rprintf(F,"     --log-file=FILE         override the \"log file\" setting\n");
@@ -1029,6 +1047,7 @@ static struct poptOption long_daemon_options[] = {
   {"bwlimit",          0,  POPT_ARG_INT,    &daemon_bwlimit, 0, 0, 0 },
   {"config",           0,  POPT_ARG_STRING, &config_file, 0, 0, 0 },
   {"daemon",           0,  POPT_ARG_NONE,   &daemon_opt, 0, 0, 0 },
+  {"dparam",          'M', POPT_ARG_STRING, 0, 'M', 0, 0 },
   {"ipv4",            '4', POPT_ARG_VAL,    &default_af_hint, AF_INET, 0, 0 },
   {"ipv6",            '6', POPT_ARG_VAL,    &default_af_hint, AF_INET6, 0, 0 },
   {"detach",           0,  POPT_ARG_VAL,    &no_detach, 0, 0, 0 },
@@ -1312,11 +1331,24 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        pc = poptGetContext(RSYNC_NAME, argc, argv,
                                            long_daemon_options, 0);
                        while ((opt = poptGetNextOpt(pc)) != -1) {
+                               char **cpp;
                                switch (opt) {
                                case 'h':
                                        daemon_usage(FINFO);
                                        exit_cleanup(0);
 
+                               case 'M':
+                                       arg = poptGetOptArg(pc);
+                                       if (!strchr(arg, '=')) {
+                                               rprintf(FERROR,
+                                                   "--dparam value is missing an '=': %s\n",
+                                                   arg);
+                                               goto daemon_error;
+                                       }
+                                       cpp = EXPAND_ITEM_LIST(&dparam_list, char *, 4);
+                                       *cpp = strdup(arg);
+                                       break;
+
                                case 'v':
                                        verbose++;
                                        break;
@@ -1330,6 +1362,9 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                }
                        }
 
+                       if (dparam_list.count && !set_dparams(1))
+                               exit_cleanup(RERR_SYNTAX);
+
                        if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
                                snprintf(err_buf, sizeof err_buf,
                                         "the --temp-dir path is WAY too long.\n");
@@ -1600,6 +1635,76 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        parse_output_words(debug_words, debug_levels, arg, USER_PRIORITY);
                        break;
 
+               case OPT_USERMAP:
+                       if (usermap) {
+                               if (usermap_via_chown) {
+                                       snprintf(err_buf, sizeof err_buf,
+                                           "--usermap conflicts with prior --chown.\n");
+                                       return 0;
+                               }
+                               snprintf(err_buf, sizeof err_buf,
+                                   "You can only specify --usermap once.\n");
+                               return 0;
+                       }
+                       usermap = (char *)poptGetOptArg(pc);
+                       usermap_via_chown = False;
+                       break;
+
+               case OPT_GROUPMAP:
+                       if (groupmap) {
+                               if (groupmap_via_chown) {
+                                       snprintf(err_buf, sizeof err_buf,
+                                           "--groupmap conflicts with prior --chown.\n");
+                                       return 0;
+                               }
+                               snprintf(err_buf, sizeof err_buf,
+                                   "You can only specify --groupmap once.\n");
+                               return 0;
+                       }
+                       groupmap = (char *)poptGetOptArg(pc);
+                       groupmap_via_chown = False;
+                       break;
+
+               case OPT_CHOWN: {
+                       const char *chown = poptGetOptArg(pc);
+                       int len;
+                       if ((arg = strchr(chown, ':')) != NULL)
+                               len = arg++ - chown;
+                       else
+                               len = strlen(chown);
+                       if (len) {
+                               if (usermap) {
+                                       if (!usermap_via_chown) {
+                                               snprintf(err_buf, sizeof err_buf,
+                                                   "--chown conflicts with prior --usermap.\n");
+                                               return 0;
+                                       }
+                                       snprintf(err_buf, sizeof err_buf,
+                                           "You can only specify a user-affecting --chown once.\n");
+                                       return 0;
+                               }
+                               if (asprintf(&usermap, "*:%.*s", len, chown) < 0)
+                                       out_of_memory("parse_arguments");
+                               usermap_via_chown = True;
+                       }
+                       if (arg && *arg) {
+                               if (groupmap) {
+                                       if (!groupmap_via_chown) {
+                                               snprintf(err_buf, sizeof err_buf,
+                                                   "--chown conflicts with prior --groupmap.\n");
+                                               return 0;
+                                       }
+                                       snprintf(err_buf, sizeof err_buf,
+                                           "You can only specify a group-affecting --chown once.\n");
+                                       return 0;
+                               }
+                               if (asprintf(&groupmap, "*:%s", arg) < 0)
+                                       out_of_memory("parse_arguments");
+                               groupmap_via_chown = True;
+                       }
+                       break;
+               }
+
                case OPT_HELP:
                        usage(FINFO);
                        exit_cleanup(0);
@@ -1647,7 +1752,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                }
        }
 
-       if (human_readable && argc == 2 && !am_server) {
+       if (human_readable > 1 && argc == 2 && !am_server) {
                /* Allow the old meaning of 'h' (--help) on its own. */
                usage(FINFO);
                exit_cleanup(0);
@@ -1655,11 +1760,20 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 
        set_output_verbosity(verbose, DEFAULT_PRIORITY);
 
-       if (do_stats && !am_server) {
+       if (do_stats) {
                parse_output_words(info_words, info_levels,
                        verbose > 1 ? "stats3" : "stats2", DEFAULT_PRIORITY);
        }
 
+       if (human_readable) {
+               char buf[32];
+               snprintf(buf, sizeof buf, "%f", 3.14);
+               if (strchr(buf, '.') != NULL)
+                       number_separator = ',';
+               else
+                       number_separator = '.';
+       }
+
 #ifdef ICONV_OPTION
        if (iconv_opt && protect_args != 2) {
                if (!am_server && strcmp(iconv_opt, "-") == 0)
@@ -1824,6 +1938,17 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                need_messages_from_generator = 1;
        }
 
+       if (munge_symlinks && !am_daemon) {
+               STRUCT_STAT st;
+               char prefix[SYMLINK_PREFIX_LEN]; /* NOT +1 ! */
+               strlcpy(prefix, SYMLINK_PREFIX, sizeof prefix); /* trim the trailing slash */
+               if (do_stat(prefix, &st) == 0 && S_ISDIR(st.st_mode)) {
+                       rprintf(FERROR, "Symlink munging is unsafe when a %s directory exists.\n",
+                               prefix);
+                       exit_cleanup(RERR_UNSUPPORTED);
+               }
+       }
+
        if (sanitize_paths) {
                int i;
                for (i = argc; i-- > 0; )
@@ -2211,6 +2336,9 @@ void server_options(char **args, int *argc_p)
                        argstr[x++] = 'i';
 #if defined HAVE_LUTIMES && defined HAVE_UTIMES
                argstr[x++] = 'L';
+#endif
+#ifdef ICONV_OPTION
+               argstr[x++] = 's';
 #endif
        }
 
@@ -2221,7 +2349,8 @@ void server_options(char **args, int *argc_p)
 
        argstr[x] = '\0';
 
-       args[ac++] = argstr;
+       if (x > 1)
+               args[ac++] = argstr;
 
 #ifdef ICONV_OPTION
        if (iconv_opt) {
@@ -2239,6 +2368,9 @@ void server_options(char **args, int *argc_p)
        if (protect_args && !local_server) /* unprotected args stop here */
                args[ac++] = NULL;
 
+       if (do_stats)
+               args[ac++] = "--stats";
+
        if (list_only > 1)
                args[ac++] = "--list-only";
 
@@ -2386,6 +2518,18 @@ void server_options(char **args, int *argc_p)
                args[ac++] = "--use-qsort";
 
        if (am_sender) {
+               if (usermap) {
+                       if (asprintf(&arg, "--usermap=%s", usermap) < 0)
+                               goto oom;
+                       args[ac++] = arg;
+               }
+
+               if (groupmap) {
+                       if (asprintf(&arg, "--groupmap=%s", groupmap) < 0)
+                               goto oom;
+                       args[ac++] = arg;
+               }
+
                if (ignore_existing)
                        args[ac++] = "--ignore-existing";
 
@@ -2474,6 +2618,61 @@ void server_options(char **args, int *argc_p)
        out_of_memory("server_options");
 }
 
+/* If str points to a valid hostspec, return allocated memory containing the
+ * [USER@]HOST part of the string, and set the path_start_ptr to the part of
+ * the string after the host part.  Otherwise, return NULL.  If port_ptr is
+ * non-NULL, we must be parsing an rsync:// URL hostname, and we will set
+ * *port_ptr if a port number is found.  Note that IPv6 IPs will have their
+ * (required for parsing) [ and ] chars elided from the returned string. */
+static char *parse_hostspec(char *str, char **path_start_ptr, int *port_ptr)
+{
+       char *s = str;
+       char *host_start = str;
+       int hostlen = 0, userlen = 0;
+       char *ret;
+
+       for ( ; ; s++) {
+               if (!*s) {
+                       /* It is only OK if we run out of string with rsync:// */
+                       if (port_ptr)
+                               break;
+                       return NULL;
+               }
+               if (*s == ':' || *s == '/') {
+                       if (!hostlen)
+                               hostlen = s - host_start;
+                       if (*s++ == '/') {
+                               if (!port_ptr)
+                                       return NULL;
+                       } else if (port_ptr) {
+                               *port_ptr = atoi(s);
+                               while (isDigit(s)) s++;
+                               if (*s++ != '/')
+                                       return NULL;
+                       }
+                       break;
+               }
+               if (*s == '@') {
+                       userlen = s - str + 1;
+                       host_start = s + 1;
+               } else if (*s == '[') {
+                       if (s != host_start++)
+                               return NULL;
+                       while (*s && *s != ']' && *s != '/') s++; /*SHARED ITERATOR*/
+                       hostlen = s - host_start;
+                       if (*s != ']' || (s[1] && s[1] != '/' && s[1] != ':') || !hostlen)
+                               return NULL;
+               }
+       }
+
+       *path_start_ptr = s;
+       ret = new_array(char, userlen + hostlen + 1);
+       if (userlen)
+               strlcpy(ret, str, userlen + 1);
+       strlcpy(ret + userlen, host_start, hostlen + 1);
+       return ret;
+}
+
 /* Look for a HOST specfication of the form "HOST:PATH", "HOST::PATH", or
  * "rsync://HOST:PORT/PATH".  If found, *host_ptr will be set to some allocated
  * memory with the HOST.  If a daemon-accessing spec was specified, the value
@@ -2483,68 +2682,28 @@ void server_options(char **args, int *argc_p)
  * "[::ffff:127.0.0.1]") which is returned without the '[' and ']'. */
 char *check_for_hostspec(char *s, char **host_ptr, int *port_ptr)
 {
-       char *p;
-       int not_host;
-       int hostlen;
+       char *path;
 
        if (port_ptr && strncasecmp(URL_PREFIX, s, strlen(URL_PREFIX)) == 0) {
-               char *path;
-               s += strlen(URL_PREFIX);
-               if ((p = strchr(s, '/')) != NULL) {
-                       hostlen = p - s;
-                       path = p + 1;
-               } else {
-                       hostlen = strlen(s);
-                       path = "";
-               }
-               if (*s == '[' && (p = strchr(s, ']')) != NULL) {
-                       s++;
-                       hostlen = p - s;
-                       if (p[1] == ':')
-                               *port_ptr = atoi(p+2);
-               } else {
-                       if ((p = strchr(s, ':')) != NULL && p < s + hostlen) {
-                               hostlen = p - s;
-                               *port_ptr = atoi(p+1);
-                       }
+               *host_ptr = parse_hostspec(s + strlen(URL_PREFIX), &path, port_ptr);
+               if (*host_ptr) {
+                       if (!*port_ptr)
+                               *port_ptr = RSYNC_PORT;
+                       return path;
                }
-               if (!*port_ptr)
-                       *port_ptr = RSYNC_PORT;
-               *host_ptr = new_array(char, hostlen + 1);
-               strlcpy(*host_ptr, s, hostlen + 1);
-               return path;
-       }
-
-       if (*s == '[' && (p = strchr(s, ']')) != NULL && p[1] == ':') {
-               s++;
-               hostlen = p - s;
-               *p = '\0';
-               not_host = strchr(s, '/') || !strchr(s, ':');
-               *p = ']';
-               if (not_host)
-                       return NULL;
-               p++;
-       } else {
-               if (!(p = strchr(s, ':')))
-                       return NULL;
-               hostlen = p - s;
-               *p = '\0';
-               not_host = strchr(s, '/') != NULL;
-               *p = ':';
-               if (not_host)
-                       return NULL;
        }
 
-       *host_ptr = new_array(char, hostlen + 1);
-       strlcpy(*host_ptr, s, hostlen + 1);
+       *host_ptr = parse_hostspec(s, &path, NULL);
+       if (!*host_ptr)
+               return NULL;
 
-       if (p[1] == ':') {
+       if (*path == ':') {
                if (port_ptr && !*port_ptr)
                        *port_ptr = RSYNC_PORT;
-               return p + 2;
+               return path + 1;
        }
        if (port_ptr)
                *port_ptr = 0;
 
-       return p + 1;
+       return path;
 }