浏览代码

add support for hierarchical mailboxes

Oswald Buddenhagen 13 年之前
父节点
当前提交
2585dd3324
共有 7 个文件被更改,包括 283 次插入73 次删除
  1. 1 1
      configure.in
  2. 104 38
      src/drv_imap.c
  3. 109 30
      src/drv_maildir.c
  4. 11 2
      src/isync.h
  5. 9 2
      src/main.c
  6. 13 0
      src/mbsync.1
  7. 36 0
      src/util.c

+ 1 - 1
configure.in

@@ -12,7 +12,7 @@ fi
 CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
 
 AC_CHECK_HEADERS(sys/poll.h sys/select.h)
-AC_CHECK_FUNCS(vasprintf)
+AC_CHECK_FUNCS(vasprintf memrchr)
 
 AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"])
 AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"])

+ 104 - 38
src/drv_imap.c

@@ -50,6 +50,7 @@ typedef struct imap_store_conf {
 	store_conf_t gen;
 	imap_server_conf_t *server;
 	unsigned use_namespace:1;
+	char delimiter;
 } imap_store_conf_t;
 
 typedef struct imap_message {
@@ -82,6 +83,7 @@ typedef struct imap_store {
 	/* trash folder's existence is not confirmed yet */
 	enum { TrashUnknown, TrashChecking, TrashKnown } trashnc;
 	unsigned got_namespace:1;
+	char delimiter; /* hierarchy delimiter */
 	list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
 	message_t **msgapp; /* FETCH results */
 	unsigned caps; /* CAPABILITY results */
@@ -808,21 +810,63 @@ parse_list_rsp( imap_store_t *ctx, char *cmd )
 				return;
 			}
 	free_list( list );
-	(void) next_arg( &cmd ); /* skip delimiter */
 	arg = next_arg( &cmd );
-	l = strlen( ctx->gen.conf->path );
-	if (memcmp( arg, ctx->gen.conf->path, l ))
-		return;
-	arg += l;
-	if (l && !strcmp( arg, "INBOX" )) {
-		warn( "IMAP warning: ignoring INBOX in %s\n", ctx->gen.conf->path );
-		return;
+	if (!ctx->delimiter)
+		ctx->delimiter = *arg;
+	arg = next_arg( &cmd );
+	if (memcmp( arg, "INBOX", 5 ) || (arg[5] && arg[5] != ctx->delimiter)) {
+		l = strlen( ctx->gen.conf->path );
+		if (memcmp( arg, ctx->gen.conf->path, l ))
+			return;
+		arg += l;
+		if (!memcmp( arg, "INBOX", 5 ) && (!arg[5] || arg[5] == ctx->delimiter)) {
+			if (!arg[5])
+				warn( "IMAP warning: ignoring INBOX in %s\n", ctx->gen.conf->path );
+			return;
+		}
 	}
 	if (!memcmp( arg + strlen( arg ) - 5, ".lock", 5 )) /* workaround broken servers */
 		return;
+	if (map_name( arg, ctx->delimiter, '/') < 0) {
+		warn( "IMAP warning: ignoring mailbox %s (reserved character '/' in name)\n", arg );
+		return;
+	}
 	add_string_list( &ctx->gen.boxes, arg );
 }
 
+static int
+prepare_name( char *buf, const imap_store_t *ctx, const char *prefix, const char *name )
+{
+	int pl;
+
+	nfsnprintf( buf, 1024, "%s%n%s", prefix, &pl, name );
+	switch (map_name( buf + pl, '/', ctx->delimiter )) {
+	case -1:
+		error( "IMAP error: mailbox name %s contains server's hierarchy delimiter\n", buf + pl );
+		return -1;
+	case -2:
+		error( "IMAP error: server's hierarchy delimiter not known\n" );
+		return -1;
+	default:
+		return 0;
+	}
+}
+
+static int
+prepare_box( char *buf, const imap_store_t *ctx )
+{
+	const char *name = ctx->gen.name;
+
+	return prepare_name( buf, ctx,
+	    (!memcmp( name, "INBOX", 5 ) && (!name[5] || name[5] == '/')) ? "" : ctx->prefix, name );
+}
+
+static int
+prepare_trash( char *buf, const imap_store_t *ctx )
+{
+	return prepare_name( buf, ctx, ctx->prefix, ctx->gen.conf->trash );
+}
+
 struct imap_cmd_trycreate {
 	struct imap_cmd gen;
 	struct imap_cmd *orig_cmd;
@@ -1157,6 +1201,7 @@ imap_open_store( store_conf_t *conf,
 			ctx->gen.boxes = 0;
 			ctx->gen.listed = 0;
 			ctx->gen.conf = conf;
+			ctx->delimiter = 0;
 			ctx->callbacks.imap_open = cb;
 			ctx->callback_aux = aux;
 			set_bad_callback( &ctx->gen, (void (*)(void *))imap_open_store_bail, ctx );
@@ -1367,10 +1412,9 @@ imap_open_store_namespace( imap_store_t *ctx )
 {
 	imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf;
 
-	ctx->prefix = "";
-	if (*cfg->gen.path)
-		ctx->prefix = cfg->gen.path;
-	else if (cfg->use_namespace && CAP(NAMESPACE)) {
+	ctx->prefix = cfg->gen.path;
+	ctx->delimiter = cfg->delimiter;
+	if (((!*ctx->prefix && cfg->use_namespace) || !cfg->delimiter) && CAP(NAMESPACE)) {
 		/* get NAMESPACE info */
 		if (!ctx->got_namespace)
 			imap_exec( ctx, 0, imap_open_store_namespace_p2, "NAMESPACE" );
@@ -1395,11 +1439,20 @@ imap_open_store_namespace_p2( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSE
 static void
 imap_open_store_namespace2( imap_store_t *ctx )
 {
-	/* XXX for now assume personal namespace */
-	if (is_list( ctx->ns_personal ) &&
-	    is_list( ctx->ns_personal->child ) &&
-	    is_atom( ctx->ns_personal->child->child ))
-		ctx->prefix = ctx->ns_personal->child->child->val;
+	imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf;
+	list_t *nsp, *nsp_1st, *nsp_1st_ns, *nsp_1st_dl;
+
+	/* XXX for now assume 1st personal namespace */
+	if (is_list( (nsp = ctx->ns_personal) ) &&
+	    is_list( (nsp_1st = nsp->child) ) &&
+	    is_atom( (nsp_1st_ns = nsp_1st->child) ) &&
+	    is_atom( (nsp_1st_dl = nsp_1st_ns->next) ))
+	{
+		if (!*ctx->prefix && cfg->use_namespace)
+			ctx->prefix = nsp_1st_ns->val;
+		if (!ctx->delimiter)
+			ctx->delimiter = *nsp_1st_dl->val;
+	}
 	imap_open_store_finalize( ctx );
 }
 
@@ -1446,15 +1499,14 @@ imap_select( store_t *gctx, int create,
 {
 	imap_store_t *ctx = (imap_store_t *)gctx;
 	struct imap_cmd_simple *cmd;
-	const char *prefix;
+	char buf[1024];
 
 	free_generic_messages( gctx->msgs );
 	gctx->msgs = 0;
 
-	if (!strcmp( gctx->name, "INBOX" )) {
-		prefix = "";
-	} else {
-		prefix = ctx->prefix;
+	if (prepare_box( buf, ctx ) < 0) {
+		cb( DRV_BOX_BAD, aux );
+		return;
 	}
 
 	ctx->gen.uidnext = 0;
@@ -1463,7 +1515,7 @@ imap_select( store_t *gctx, int create,
 	cmd->gen.param.create = create;
 	cmd->gen.param.trycreate = 1;
 	imap_exec( ctx, &cmd->gen, imap_done_simple_box,
-	           "SELECT \"%s%s\"", prefix, gctx->name );
+	           "SELECT \"%s\"", buf );
 }
 
 /******************* imap_load *******************/
@@ -1636,13 +1688,17 @@ imap_trash_msg( store_t *gctx, message_t *msg,
 {
 	imap_store_t *ctx = (imap_store_t *)gctx;
 	struct imap_cmd_simple *cmd;
+	char buf[1024];
 
 	INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux)
 	cmd->gen.param.create = 1;
 	cmd->gen.param.to_trash = 1;
+	if (prepare_trash( buf, ctx ) < 0) {
+		cb( DRV_BOX_BAD, aux );
+		return;
+	}
 	imap_exec( ctx, &cmd->gen, imap_done_simple_msg,
-	           "UID COPY %d \"%s%s\"",
-	           msg->uid, ctx->prefix, gctx->conf->trash );
+	           "UID COPY %d \"%s\"", msg->uid, buf );
 }
 
 /******************* imap_store_msg *******************/
@@ -1655,9 +1711,8 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
 {
 	imap_store_t *ctx = (imap_store_t *)gctx;
 	struct imap_cmd_out_uid *cmd;
-	const char *prefix, *box;
 	int d;
-	char flagstr[128];
+	char flagstr[128], buf[1024];
 
 	d = 0;
 	if (data->flags) {
@@ -1672,16 +1727,20 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
 	cmd->out_uid = -2;
 
 	if (to_trash) {
-		box = gctx->conf->trash;
-		prefix = ctx->prefix;
 		cmd->gen.param.create = 1;
 		cmd->gen.param.to_trash = 1;
+		if (prepare_trash( buf, ctx ) < 0) {
+			cb( DRV_BOX_BAD, -1, aux );
+			return;
+		}
 	} else {
-		box = gctx->name;
-		prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
+		if (prepare_box( buf, ctx ) < 0) {
+			cb( DRV_BOX_BAD, -1, aux );
+			return;
+		}
 	}
 	imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
-	           "APPEND \"%s%s\" %s", prefix, box, flagstr );
+	           "APPEND \"%s\" %s", buf, flagstr );
 }
 
 static void
@@ -1710,15 +1769,20 @@ imap_find_new_msgs( store_t *gctx,
 /******************* imap_list *******************/
 
 static void
-imap_list( store_t *gctx,
+imap_list( store_t *gctx, int flags,
            void (*cb)( int sts, void *aux ), void *aux )
 {
 	imap_store_t *ctx = (imap_store_t *)gctx;
-	struct imap_cmd_simple *cmd;
-
-	INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux)
-	imap_exec( ctx, &cmd->gen, imap_done_simple_box,
-	           "LIST \"\" \"%s%%\"", ctx->prefix );
+	struct imap_cmd_refcounted_state *sts = imap_refcounted_new_state( cb, aux );
+
+	if (((flags & LIST_PATH) &&
+	     imap_exec( ctx, imap_refcounted_new_cmd( sts ), imap_refcounted_done_box,
+	                "LIST \"\" \"%s*\"", ctx->prefix ) < 0) ||
+	    ((flags & LIST_INBOX) && (!(flags & LIST_PATH) || *ctx->prefix) &&
+	     imap_exec( ctx, imap_refcounted_new_cmd( sts ), imap_refcounted_done_box,
+	                "LIST \"\" INBOX*" ) < 0))
+		{}
+	imap_refcounted_done( sts );
 }
 
 /******************* imap_cancel *******************/
@@ -1853,6 +1917,8 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
 				store->use_namespace = parse_bool( cfg );
 			else if (!strcasecmp( "Path", cfg->cmd ))
 				store->gen.path = nfstrdup( cfg->val );
+			else if (!strcasecmp( "PathDelimiter", cfg->cmd ))
+				store->delimiter = *cfg->val;
 			else
 				parse_generic_store( &store->gen, cfg, err );
 			continue;

+ 109 - 30
src/drv_maildir.c

@@ -94,6 +94,29 @@ maildir_parse_flags( const char *base )
 	return flags;
 }
 
+static char *
+maildir_join_path( const char *prefix, const char *box )
+{
+	char *out, *p;
+	int pl, bl, n;
+	char c;
+
+	pl = strlen( prefix );
+	for (bl = 0, n = 0; (c = box[bl]); bl++)
+		if (c == '/')
+			n++;
+	out = nfmalloc( pl + bl + n + 1 );
+	memcpy( out, prefix, pl );
+	p = out + pl;
+	while ((c = *box++)) {
+		*p++ = c;
+		if (c == '/')
+			*p++ = '.';
+	}
+	*p = 0;
+	return out;
+}
+
 static void
 maildir_open_store( store_conf_t *conf,
                     void (*cb)( store_t *ctx, void *aux ), void *aux )
@@ -109,7 +132,8 @@ maildir_open_store( store_conf_t *conf,
 	ctx = nfcalloc( sizeof(*ctx) );
 	ctx->gen.conf = conf;
 	ctx->uvfd = -1;
-	nfasprintf( &ctx->trash, "%s%s", conf->path, conf->trash );
+	if (conf->trash)
+		ctx->trash = maildir_join_path( conf->path, conf->trash );
 	cb( &ctx->gen, aux );
 }
 
@@ -168,40 +192,87 @@ maildir_invoke_bad_callback( store_t *ctx )
 	ctx->bad_callback( ctx->bad_callback_aux );
 }
 
-static void
-maildir_list( store_t *gctx,
-              void (*cb)( int sts, void *aux ), void *aux )
+static int maildir_list_part( store_t *gctx, int doInbox, int *flags );
+
+static int
+maildir_list_recurse( store_t *gctx, int isBox, int *flags, const char *inbox,
+                      char *path, int pathLen, char *name, int nameLen )
 {
 	DIR *dir;
+	int pl, nl;
 	struct dirent *de;
+	struct stat st;
 
-	if (!(dir = opendir( gctx->conf->path ))) {
-		sys_error( "Maildir error: cannot list %s", gctx->conf->path );
-		maildir_invoke_bad_callback( gctx );
-		cb( DRV_CANCELED, aux );
-		return;
+	if (isBox) {
+		nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "/cur" );
+		if (stat( path, &st ) || !S_ISDIR(st.st_mode))
+			return 0;
+		path[pathLen] = 0;
+		add_string_list( &gctx->boxes, name );
+		name[nameLen++] = '/';
+	}
+	if (!(dir = opendir( path ))) {
+		sys_error( "Maildir error: cannot list %s", path );
+		return -1;
 	}
 	while ((de = readdir( dir ))) {
-		const char *inbox = ((maildir_store_conf_t *)gctx->conf)->inbox;
-		int bl, isibx;
-		struct stat st;
-		char buf[PATH_MAX];
-
-		if (*de->d_name == '.')
-			continue;
-		bl = nfsnprintf( buf, sizeof(buf), "%s%s/cur", gctx->conf->path, de->d_name );
-		if (stat( buf, &st ) || !S_ISDIR(st.st_mode))
-			continue;
-		isibx = !memcmp( buf, inbox, bl - 4 ) && !inbox[bl - 4];
-		if (!isibx && !strcmp( de->d_name, "INBOX" )) {
-			warn( "Maildir warning: ignoring INBOX in %s\n", gctx->conf->path );
-			continue;
+		const char *ent = de->d_name;
+		pl = pathLen + nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "%s", ent );
+		if (inbox && !memcmp( path, inbox, pl ) && !inbox[pl]) {
+			if (maildir_list_part( gctx, 1, flags ) < 0)
+				return -1;
+		} else {
+			if (!memcmp( ent, "INBOX", 6 )) {
+				path[pathLen] = 0;
+				warn( "Maildir warning: ignoring INBOX in %s\n", path );
+				continue;
+			}
+			if (*ent == '.') {
+				if (!isBox)
+					continue;
+				ent++;
+			} else {
+				if (isBox)
+					continue;
+			}
+			nl = nameLen + nfsnprintf( name + nameLen, _POSIX_PATH_MAX - nameLen, "%s", ent );
+			if (maildir_list_recurse( gctx, 1, flags, inbox, path, pl, name, nl ) < 0)
+				return -1;
 		}
-		add_string_list( &gctx->boxes, isibx ? "INBOX" : de->d_name );
 	}
 	closedir (dir);
+	return 0;
+}
 
-	cb( DRV_OK, aux );
+static int
+maildir_list_part( store_t *gctx, int doInbox, int *flags )
+{
+	int pl, nl;
+	const char *inbox = ((maildir_store_conf_t *)gctx->conf)->inbox;
+	char path[_POSIX_PATH_MAX], name[_POSIX_PATH_MAX];
+
+	if (doInbox) {
+		*flags &= ~LIST_INBOX;
+		pl = nfsnprintf( path, _POSIX_PATH_MAX, "%s", inbox );
+		nl = nfsnprintf( name, _POSIX_PATH_MAX, "INBOX" );
+		return maildir_list_recurse( gctx, 1, flags, 0, path, pl, name, nl );
+	} else {
+		pl = nfsnprintf( path, _POSIX_PATH_MAX, "%s", gctx->conf->path );
+		return maildir_list_recurse( gctx, 0, flags, inbox, path, pl, name, 0 );
+	}
+}
+
+static void
+maildir_list( store_t *gctx, int flags,
+              void (*cb)( int sts, void *aux ), void *aux )
+{
+	if (((flags & LIST_PATH) && maildir_list_part( gctx, 0, &flags ) < 0) ||
+	    ((flags & LIST_INBOX) && maildir_list_part( gctx, 1, &flags ) < 0)) {
+		maildir_invoke_bad_callback( gctx );
+		cb( DRV_CANCELED, aux );
+	} else {
+		cb( DRV_OK, aux );
+	}
 }
 
 static const char *subdirs[] = { "cur", "new", "tmp" };
@@ -237,8 +308,9 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx )
 {
 	DIR *dirp;
 	struct dirent *entry;
+	char *p;
 	time_t now;
-	int i, bl;
+	int i, bl, ret;
 	struct stat st;
 	char buf[_POSIX_PATH_MAX];
 
@@ -246,6 +318,13 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx )
 	if (stat( buf, &st )) {
 		if (errno == ENOENT) {
 			if (create) {
+				p = memrchr( buf, '/', bl - 1 );
+				if (*(p + 1) == '.') {
+					*p = 0;
+					if ((ret = maildir_validate( buf, 1, ctx )) != DRV_OK)
+						return ret;
+					*p = '/';
+				}
 				if (mkdir( buf, 0700 )) {
 					sys_error( "Maildir error: cannot create mailbox '%s'", buf );
 					maildir_invoke_bad_callback( &ctx->gen );
@@ -822,10 +901,10 @@ maildir_select( store_t *gctx, int create,
 #ifdef USE_DB
 	ctx->db = 0;
 #endif /* USE_DB */
-	if (!strcmp( gctx->name, "INBOX" ))
-		gctx->path = nfstrdup( ((maildir_store_conf_t *)gctx->conf)->inbox );
-	else
-		nfasprintf( &gctx->path, "%s%s", gctx->conf->path, gctx->name );
+	gctx->path =
+		(!memcmp( gctx->name, "INBOX", 5 ) && (!gctx->name[5] || gctx->name[5] == '/')) ?
+			maildir_join_path( ((maildir_store_conf_t *)gctx->conf)->inbox, gctx->name + 5 ) :
+			maildir_join_path( gctx->conf->path, gctx->name );
 
 	if ((ret = maildir_validate( gctx->path, create, ctx )) != DRV_OK) {
 		cb( ret, aux );

+ 11 - 2
src/isync.h

@@ -259,6 +259,9 @@ typedef struct {
 */
 #define DRV_CRLF        1
 
+#define LIST_PATH       1
+#define LIST_INBOX      2
+
 struct driver {
 	int flags;
 
@@ -283,8 +286,8 @@ struct driver {
 	 * Pending commands will have their callbacks synchronously invoked with DRV_CANCELED. */
 	void (*cancel_store)( store_t *ctx );
 
-	/* List the mailboxes in this store. */
-	void (*list)( store_t *ctx,
+	/* List the mailboxes in this store. Flags are ORed LIST_* values. */
+	void (*list)( store_t *ctx, int flags,
 	              void (*cb)( int sts, void *aux ), void *aux );
 
 	/* Invoked before select(), this informs the driver which operations (OP_*)
@@ -415,6 +418,10 @@ void free_string_list( string_list_t *list );
 
 void free_generic_messages( message_t * );
 
+#ifndef HAVE_MEMRCHR
+void *memrchr( const void *s, int c, size_t n );
+#endif
+
 void *nfmalloc( size_t sz );
 void *nfcalloc( size_t sz );
 void *nfrealloc( void *mem, size_t sz );
@@ -426,6 +433,8 @@ void ATTR_NORETURN oob( void );
 
 char *expand_strdup( const char *s );
 
+int map_name( char *arg, char in, char out );
+
 void sort_ints( int *arr, int len );
 
 void arc4_init( void );

+ 9 - 2
src/main.c

@@ -129,7 +129,7 @@ matches( const char *t, const char *p )
 		} else if (*p == '%') {
 			p++;
 			do {
-				if (*t == '.' || *t == '/') /* this is "somewhat" hacky ... */
+				if (*t == '/')
 					return 0;
 				if (matches( t, p ))
 					return 1;
@@ -690,6 +690,8 @@ static void
 store_opened( store_t *ctx, void *aux )
 {
 	MVARS(aux)
+	string_list_t *cpat;
+	int flags;
 
 	if (!ctx) {
 		mvars->ret = mvars->skip = 1;
@@ -699,8 +701,13 @@ store_opened( store_t *ctx, void *aux )
 	}
 	mvars->ctx[t] = ctx;
 	if (!mvars->skip && !mvars->boxlist && mvars->chan->patterns && !ctx->listed) {
+		for (flags = 0, cpat = mvars->chan->patterns; cpat; cpat = cpat->next) {
+			const char *pat = cpat->string;
+			if (*pat != '!')
+				flags |= (!memcmp( pat, "INBOX", 5 ) && (!pat[5] || pat[5] == '/')) ? LIST_INBOX : LIST_PATH;
+		}
 		set_bad_callback( ctx, store_bad, AUX );
-		mvars->drv[t]->list( ctx, store_listed, AUX );
+		mvars->drv[t]->list( ctx, flags, store_listed, AUX );
 	} else {
 		mvars->state[t] = ST_OPEN;
 		sync_chans( mvars, E_OPEN );

+ 13 - 0
src/mbsync.1

@@ -105,6 +105,13 @@ There are two auxiliary object classes: Accounts and Groups. An Account
 describes the connection part of remote Stores, so a server connection can be
 shared between multiple Stores. A Group aggregates multiple Channels to
 save typing on the command line.
+.P
+File system locations (in particular, \fBPath\fR and \fBInbox\fR) use the
+Store's internal path separators, which may be slashes, periods, etc., or
+even combinations thereof.
+.br
+Mailbox names, OTOH, always use canonical path separators, which are
+Unix-like forward slashes.
 ..
 .SS All Stores
 These options can be used in all supported Store types.
@@ -140,6 +147,7 @@ If \fIsize\fR is 0, the maximum message size is \fBunlimited\fR.
 Create a virtual mailbox (relative to \fBPath\fR), which is backed by
 the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the
 Channels section.
+This virtual mailbox does not support subfolders.
 ..
 .TP
 \fBTrash\fR \fImailbox\fR
@@ -306,6 +314,11 @@ mailbox names. Disabling this makes sense for some broken IMAP servers.
 This option is meaningless if a \fBPath\fR was specified.
 (Default: \fIyes\fR)
 ..
+.TP
+\fBPathDelimiter\fR \fIdelim\fR
+Specify the server's hierarchy delimiter character.
+(Default: taken from the server's first "personal" NAMESPACE)
+..
 .SS Channels
 .TP
 \fBChannel\fR \fIname\fR

+ 36 - 0
src/util.c

@@ -229,6 +229,19 @@ vasprintf( char **strp, const char *fmt, va_list ap )
 }
 #endif
 
+#ifndef HAVE_MEMRCHR
+void *
+memrchr( const void *s, int c, size_t n )
+{
+	u_char *b = (u_char *)s, *e = b + n;
+
+	while (--e >= b)
+		if (*e == c)
+			return (void *)e;
+	return 0;
+}
+#endif
+
 void
 oob( void )
 {
@@ -378,6 +391,29 @@ expand_strdup( const char *s )
 		return nfstrdup( s );
 }
 
+/* Return value: 0 = ok, -1 = out found in arg, -2 = in found in arg but no out specified */
+int
+map_name( char *arg, char in, char out )
+{
+	int l, k;
+
+	if (!in || in == out)
+		return 0;
+	for (l = 0; arg[l]; l++)
+		if (arg[l] == in) {
+			if (!out)
+				return -2;
+			arg[l] = out;
+		} else if (arg[l] == out) {
+			/* restore original name for printing error message */
+			for (k = 0; k < l; k++)
+				if (arg[k] == out)
+					arg[k] = in;
+			return -1;
+		}
+	return 0;
+}
+
 static int
 compare_ints( const void *l, const void *r )
 {