diff mbox

[06/19] mount.cifs: parse unc into separate fields

Message ID 1269613542-6402-7-git-send-email-jlayton@samba.org
State New
Headers show

Commit Message

Jeff Layton March 26, 2010, 2:25 p.m. UTC
From: Jeff Layton <jlayton@redhat.com>

The UNC is currently handled as a single string and mount.cifs will
just munge it whenever it needs to change the delimiter type or
uppercase it, etc. This is tricky to handle correctly and means that
we often need to keep track of what's already been changed. Instead
of doing this, just track the pieces of the UNC in separate fields
in the parsed_mount_info, and then use those pieces to build strings
as needed.

Signed-off-by: Jeff Layton <jlayton@redhat.com>
---
 mount.cifs.c |  375 ++++++++++++++++++++++++++++------------------------------
 1 files changed, 179 insertions(+), 196 deletions(-)
diff mbox

Patch

diff --git a/mount.cifs.c b/mount.cifs.c
index 26621ff..b51e2a1 100644
--- a/mount.cifs.c
+++ b/mount.cifs.c
@@ -74,7 +74,7 @@ 
 #define MAX_ADDRESS_LEN INET6_ADDRSTRLEN
 
 /* limit list of addresses to 16 max-size addrs */
-#define MAX_ADDR_LIST_LEN (MAX_ADDRESS_LEN * 16)
+#define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * 16)
 
 #ifndef SAFE_FREE
 #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0)
@@ -124,14 +124,14 @@ 
 /* struct for holding parsed mount info for use by privleged process */
 struct parsed_mount_info {
 	unsigned long	flags;
-	char		host[NI_MAXHOST];
-	char		share[MAX_SHARE_LEN];
-	char		prefix[PATH_MAX];
+	char		host[NI_MAXHOST + 1];
+	char		share[MAX_SHARE_LEN + 1];
+	char		prefix[PATH_MAX + 1];
 	char		options[MAX_OPTIONS_LEN];
 	char		domain[DOMAIN_SIZE + 1];
 	char		username[MAX_USERNAME_SIZE + 1];
 	char		password[MOUNT_PASSWD_SIZE + 1];
-	char		address_list[MAX_ADDR_LIST_LEN];
+	char		addrlist[MAX_ADDR_LIST_LEN];
 	unsigned int	got_domain:1;
 	unsigned int	got_user:1;
 	unsigned int	got_password:1;
@@ -139,14 +139,10 @@  struct parsed_mount_info {
 
 const char *thisprogram;
 int verboseflag = 0;
-int fakemnt = 0;
-static int got_ip = 0;
-static int got_unc = 0;
-static int got_uid = 0;
-static int got_gid = 0;
-char * prefixpath = NULL;
 const char *cifs_fstype = "cifs";
 
+static int parse_unc(char *unc_name, struct parsed_mount_info *parsed_info);
+
 #if CIFS_LEGACY_SETUID_CHECK
 static int
 check_mountpoint(const char *progname, char *mountpoint)
@@ -509,6 +505,7 @@  parse_options(const char *data, struct parsed_mount_info *parsed_info)
 	int out_len = 0;
 	int word_len;
 	int rc = 0;
+	int got_uid = 0, got_gid = 0;
 	char user[32];
 	char group[32];
 
@@ -600,7 +597,6 @@  parse_options(const char *data, struct parsed_mount_info *parsed_info)
 			} else if (strnlen(value, MAX_ADDRESS_LEN) <= MAX_ADDRESS_LEN) {
 				if(verboseflag)
 					fprintf(stderr, "ip address %s override specified\n",value);
-				got_ip = 1;
 			} else {
 				fprintf(stderr, "ip address too long\n");
 				return EX_USAGE;
@@ -611,30 +607,10 @@  parse_options(const char *data, struct parsed_mount_info *parsed_info)
 			if (!value || !*value) {
 				fprintf(stderr, "invalid path to network resource\n");
 				return EX_USAGE;  /* needs_arg; */
-			} else if(strnlen(value,5) < 5) {
-				fprintf(stderr, "UNC name too short");
-			}
-
-			if (strnlen(value, 300) < 300) {
-				got_unc = 1;
-				if (strncmp(value, "//", 2) == 0) {
-					if(got_unc)
-						fprintf(stderr, "unc name specified twice, ignoring second\n");
-					else
-						got_unc = 1;
-				} else if (strncmp(value, "\\\\", 2) != 0) {	                   
-					fprintf(stderr, "UNC Path does not begin with // or \\\\ \n");
-					return EX_USAGE;
-				} else {
-					if(got_unc)
-						fprintf(stderr, "unc name specified twice, ignoring second\n");
-					else
-						got_unc = 1;
-				}
-			} else {
-				fprintf(stderr, "CIFS: UNC name too long\n");
-				return EX_USAGE;
 			}
+			rc = parse_unc(value, parsed_info);
+			if (rc)
+				return rc;
 		} else if ((strncmp(data, "dom" /* domain */, 3) == 0)
 			   || (strncmp(data, "workg", 5) == 0)) {
 			/* note this allows for synonyms of "domain"
@@ -844,107 +820,165 @@  replace_commas(char *pass)
 	return 0;
 }
 
-/* replace all occurances of "from" in a string with "to" */
-static void replace_char(char *string, char from, char to, int maxlen)
+/*
+ * resolve "host" portion of parsed info to comma-separated list of
+ * address(es)
+ */
+static int
+resolve_host(struct parsed_mount_info *parsed_info)
 {
-	char *lastchar = string + maxlen;
-	while (string) {
-		string = strchr(string, from);
-		if (string) {
-			*string = to;
-			if (string >= lastchar)
-				return;
+	int rc;
+	/* 10 for max width of decimal scopeid */
+	char tmpbuf[NI_MAXHOST + 1 + 10 + 1];
+	const char *ipaddr;
+	size_t len;
+	struct addrinfo *addrlist, *addr;
+	struct sockaddr_in *sin;
+	struct sockaddr_in6 *sin6;
+
+	rc = getaddrinfo(parsed_info->host, NULL, NULL, &addrlist);
+	if (rc != 0) {
+		fprintf(stderr, "mount error: could not resolve address for "
+				"%s: %s\n", parsed_info->host,
+				rc == EAI_SYSTEM ? strerror(errno) :
+				gai_strerror(rc));
+		/* FIXME: return better error based on rc? */
+		return EX_USAGE;
+	}
+
+	addr = addrlist;
+	while (addr) {
+		/* skip non-TCP entries */
+		if (addr->ai_socktype != SOCK_STREAM ||
+		    addr->ai_protocol != IPPROTO_TCP) {
+			addr = addr->ai_next;
+			continue;
 		}
+
+		switch (addr->ai_addr->sa_family) {
+		case AF_INET6:
+			sin6 = (struct sockaddr_in6 *) addr->ai_addr;
+			ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf,
+					   sizeof(tmpbuf));
+			if (!ipaddr) {
+				rc = EX_SYSERR;
+				fprintf(stderr, "mount error: problem parsing address "
+					"list: %s\n", strerror(errno));
+				goto resolve_host_out;
+			}
+
+			if (sin6->sin6_scope_id) {
+				len = strnlen(tmpbuf, sizeof(tmpbuf));
+				ipaddr = tmpbuf + len;
+				snprintf(tmpbuf, sizeof(tmpbuf) - len, "%%%u", 
+						sin6->sin6_scope_id);
+			}
+			break;
+		case AF_INET:
+			sin = (struct sockaddr_in *) addr->ai_addr;
+			ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf,
+					   sizeof(tmpbuf));
+			if (!ipaddr) {
+				rc = EX_SYSERR;
+				fprintf(stderr, "mount error: problem parsing address "
+					"list: %s\n", strerror(errno));
+				goto resolve_host_out;
+			}
+
+			break;
+		default:
+			addr = addr->ai_next;
+			continue;
+		}
+
+		if (parsed_info->addrlist[0] != '\0')
+			strlcat(parsed_info->addrlist, ",",
+				sizeof(parsed_info->addrlist));
+		strlcat(parsed_info->addrlist, tmpbuf,
+				sizeof(parsed_info->addrlist));
+		addr = addr->ai_next;
 	}
+
+resolve_host_out:
+	freeaddrinfo(addrlist);
+	return rc;
 }
 
-/* Note that caller frees the returned buffer if necessary */
-static struct addrinfo *
-parse_server(char **punc_name)
+static int
+parse_unc(char *unc_name, struct parsed_mount_info *parsed_info)
 {
-	char *unc_name = *punc_name;
 	int length = strnlen(unc_name, MAX_UNC_LEN);
-	char *share;
-	struct addrinfo *addrlist;
-	int rc;
+	char *host, *share, *prepath;
+	size_t hostlen, sharelen, prepathlen;
 
 	if(length > (MAX_UNC_LEN - 1)) {
 		fprintf(stderr, "mount error: UNC name too long\n");
-		return NULL;
+		return EX_USAGE;
 	}
 
 	if(length < 3) {
 		fprintf(stderr, "mount error: UNC name too short\n");
-		return NULL;
+		return EX_USAGE;
 	}
 
 	if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
 	    (strncasecmp("smb://", unc_name, 6) == 0)) {
 		fprintf(stderr, "Mounting cifs URL not implemented yet. Attempt to mount %s\n", unc_name);
-		return NULL;
+		return EX_USAGE;
 	}
 
-	if(strncmp(unc_name,"//",2) && strncmp(unc_name,"\\\\",2)) {
-		/* check for nfs syntax ie server:share */
-		share = strchr(unc_name,':');
+	/* Set up "host" and "share" pointers based on UNC format. */
+	if(strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
+		/*
+		 * check for nfs syntax (server:/share/prepath)
+		 *
+		 * FIXME: IPv6 addresses?
+		 */
+		host = unc_name;
+		share = strchr(host, ':');
 		if(!share) {
-			fprintf(stderr, "mount error: improperly formatted UNC name.");
-			fprintf(stderr, " %s does not begin with \\\\ or //\n",unc_name);
-			return NULL;
+			fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
+			return EX_USAGE;
 		}
-
-		*punc_name = (char *)malloc(length + 3);
-		if(*punc_name == NULL) {
-			*punc_name = unc_name;
-			return NULL;
+		hostlen = share - host;
+		share++;
+		if (*share == '/')
+			++share;
+	} else {
+		host = unc_name + 2;
+		hostlen = strcspn(host, "/\\");
+		if (!hostlen) {
+			fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
+			return EX_USAGE;
 		}
-
-		*share = '/';
-		strlcpy((*punc_name)+2, unc_name, length + 1);
-		SAFE_FREE(unc_name);
-		unc_name = *punc_name;
-		unc_name[length+2] = 0;
+		share = host + hostlen + 1;
 	}
 
-	unc_name[0] = '/';
-	unc_name[1] = '/';
-	unc_name += 2;
-
-	/*
-	 * allow for either delimiter between host and sharename
-	 * If there's not one, then the UNC is malformed
-	 */
-	if (!(share = strpbrk(unc_name, "/\\"))) {
-		fprintf(stderr, "mount error: Malformed UNC\n");
-		return NULL;
+	if (hostlen + 1 > sizeof(parsed_info->host)) {
+		fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
+		return EX_USAGE;
 	}
 
-	*share = 0;  /* temporarily terminate the string */
-	share += 1;
-	if(got_ip == 0) {
-		rc = getaddrinfo(unc_name, NULL, NULL, &addrlist);
-		if (rc != 0) {
-			fprintf(stderr, "mount error: could not resolve address for %s: %s\n",
-				unc_name, gai_strerror(rc));
-			addrlist = NULL;
-		}
+	sharelen = strcspn(share, "/\\");
+	if (sharelen + 1 > sizeof(parsed_info->share)) {
+		fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
+		return EX_USAGE;
 	}
-	*(share - 1) = '/'; /* put delimiter back */
 
-	/* we don't convert the prefixpath delimiters since '\\' is a valid char in posix paths */
-	if ((prefixpath = strpbrk(share, "/\\"))) {
-		*prefixpath = 0;  /* permanently terminate the string */
-		if (!strlen(++prefixpath))
-			prefixpath = NULL; /* this needs to be done explicitly */
-	}
-	if(got_ip) {
-		if(verboseflag)
-			fprintf(stderr, "ip address specified explicitly\n");
-		return NULL;
+	prepath = share + sharelen;
+	prepathlen = strlen(prepath);
+
+	if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
+		fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
+		return EX_USAGE;
 	}
-	/* BB should we pass an alternate version of the share name as Unicode */
 
-	return addrlist;
+	/* copy pieces into their resepective buffers */
+	memcpy(parsed_info->host, host, hostlen);
+	memcpy(parsed_info->share, share, sharelen);
+	memcpy(parsed_info->prefix, prepath, prepathlen);
+
+	return 0;
 }
 
 static int
@@ -1047,25 +1081,20 @@  int main(int argc, char ** argv)
 {
 	int c;
 	char * orgoptions = NULL;
-	char * share_name = NULL;
-	const char * ipaddr = NULL;
 	char * mountpoint = NULL;
 	char * options = NULL;
-	char * optionstail;
 	char * resolved_path = NULL;
-	char * temp;
-	char * dev_name = NULL;
+	char * dev_name;
+	char *currentaddress, *nextaddress;
 	int rc = 0;
 	int nomtab = 0;
 	int uid = 0;
 	int gid = 0;
+	int fakemnt = 0;
+	int already_uppercased = 0;
 	size_t options_size = MAX_OPTIONS_LEN;
-	size_t current_len;
 	int retry = 0; /* set when we have to retry mount with uppercase */
-	struct addrinfo *addrhead = NULL, *addr;
 	struct mntent mountent;
-	struct sockaddr_in *addr4 = NULL;
-	struct sockaddr_in6 *addr6 = NULL;
 	struct parsed_mount_info *parsed_info = NULL;
 	FILE * pmntfile;
 
@@ -1230,12 +1259,6 @@  int main(int argc, char ** argv)
 	}
 
 	dev_name = argv[optind];
-	share_name = strndup(argv[optind], MAX_UNC_LEN);
-	if (share_name == NULL) {
-		fprintf(stderr, "%s: %s", thisprogram, strerror(ENOMEM));
-		rc = EX_SYSERR;
-		goto mount_exit;
-	}
 	mountpoint = argv[optind + 1];
 
 	/* make sure mountpoint is legit */
@@ -1292,13 +1315,14 @@  int main(int argc, char ** argv)
 
 	parsed_info->flags &= ~(MS_USERS|MS_USER);
 
-	addrhead = addr = parse_server(&share_name);
-	if((addrhead == NULL) && (got_ip == 0)) {
-		fprintf(stderr, "No ip address specified and hostname not found\n");
-		rc = EX_USAGE;
+	rc = parse_unc(dev_name, parsed_info);
+	if (rc)
 		goto mount_exit;
-	}
-	
+
+	rc = resolve_host(parsed_info);
+	if (rc)
+		goto mount_exit;
+
 	/* BB save off path and pop after mount returns? */
 	resolved_path = (char *)malloc(PATH_MAX+1);
 	if (!resolved_path) {
@@ -1345,12 +1369,6 @@  int main(int argc, char ** argv)
 		parsed_info->got_password = 1;
 	}
 
-	if(!share_name) {
-		fprintf(stderr, "No server share name specified\n");
-                rc = EX_USAGE;
-		goto mount_exit;
-	}
-
 	/* copy in ver= string. It's not really needed, but what the hell */
 	strlcat(parsed_info->options, ",ver=", sizeof(parsed_info->options));
 	strlcat(parsed_info->options, OPTIONS_VERSION, sizeof(parsed_info->options));
@@ -1377,63 +1395,33 @@  int main(int argc, char ** argv)
 		goto mount_exit;
 	}
 
+	currentaddress = parsed_info->addrlist;
+	nextaddress = strchr(currentaddress, ',');
+	if (nextaddress)
+		*nextaddress++ = '\0';
+
 mount_retry:
-	strlcpy(options, "unc=", options_size);
-	strlcat(options, share_name, options_size);
+	if (!currentaddress) {
+		fprintf(stderr, "Unable to find suitable address.\n");
+		rc = EX_SYSERR;
+		goto mount_exit;
+	}
+	strlcpy(options, "ip=", options_size);
+	strlcat(options, currentaddress, options_size);
 
-	/* scan backwards and reverse direction of slash */
-	temp = strrchr(options, '/');
-	if(temp > options + 6)
-		*temp = '\\';
+	strlcat(options, ",unc=\\\\", options_size);
+	strlcat(options, parsed_info->host, options_size);
+	strlcat(options, "\\", options_size);
+	strlcat(options, parsed_info->share, options_size);
 
 	if (*parsed_info->options) {
 		strlcat(options, ",", options_size);
 		strlcat(options, parsed_info->options, options_size);
 	}
 
-	if(prefixpath) {
+	if(*parsed_info->prefix) {
 		strlcat(options,",prefixpath=",options_size);
-		strlcat(options,prefixpath,options_size); /* no need to cat the / */
-	}
-
-	/* convert all '\\' to '/' in share portion so that /proc/mounts looks pretty */
-	replace_char(dev_name, '\\', '/', strlen(share_name));
-
-	if (!got_ip && addr) {
-		strlcat(options, ",ip=", options_size);
-		current_len = strnlen(options, options_size);
-		optionstail = options + current_len;
-		switch (addr->ai_addr->sa_family) {
-		case AF_INET6:
-			addr6 = (struct sockaddr_in6 *) addr->ai_addr;
-			ipaddr = inet_ntop(AF_INET6, &addr6->sin6_addr, optionstail,
-					   options_size - current_len);
-			break;
-		case AF_INET:
-			addr4 = (struct sockaddr_in *) addr->ai_addr;
-			ipaddr = inet_ntop(AF_INET, &addr4->sin_addr, optionstail,
-					   options_size - current_len);
-			break;
-		default:
-			ipaddr = NULL;
-		}
-
-		/* if the address looks bogus, try the next one */
-		if (!ipaddr) {
-			addr = addr->ai_next;
-			if (addr)
-				goto mount_retry;
-			rc = EX_SYSERR;
-			goto mount_exit;
-		}
-	}
-
-	if (addr && addr->ai_addr->sa_family == AF_INET6 && addr6->sin6_scope_id) {
-		strlcat(options, "%", options_size);
-		current_len = strnlen(options, options_size);
-		optionstail = options + current_len;
-		snprintf(optionstail, options_size - current_len, "%u",
-			 addr6->sin6_scope_id);
+		strlcat(options, parsed_info->prefix, options_size);
 	}
 
 	if(verboseflag)
@@ -1463,24 +1451,22 @@  mount_retry:
 		switch (errno) {
 		case ECONNREFUSED:
 		case EHOSTUNREACH:
-			if (addr) {
-				addr = addr->ai_next;
-				if (addr)
-					goto mount_retry;
-			}
-			break;
+			currentaddress = nextaddress;
+			nextaddress = strchr(currentaddress, ',');
+			if (nextaddress)
+				*nextaddress++ = '\0';
+			goto mount_retry;
 		case ENODEV:
 			fprintf(stderr, "mount error: cifs filesystem not supported by the system\n");
 			break;
 		case ENXIO:
-			if(retry == 0) {
-				retry = 1;
-				if (uppercase_string(dev_name) &&
-				    uppercase_string(share_name) &&
-				    uppercase_string(prefixpath)) {
-					fprintf(stderr, "retrying with upper case share name\n");
-					goto mount_retry;
-				}
+			if (!already_uppercased &&
+			    uppercase_string(parsed_info->host) &&
+			    uppercase_string(parsed_info->share) &&
+			    uppercase_string(parsed_info->prefix)) {
+				fprintf(stderr, "Retrying with upper case share name\n");
+				already_uppercased = 1;
+				goto mount_retry;
 			}
 		}
 		fprintf(stderr, "mount error(%d): %s\n", errno, strerror(errno));
@@ -1544,13 +1530,10 @@  mount_retry:
 	if (rc)
 		rc = EX_FILEIO;
 mount_exit:
-	if (addrhead)
-		freeaddrinfo(addrhead);
 	memset(parsed_info->password, 0, sizeof(parsed_info->password));
 	SAFE_FREE(parsed_info);
 	SAFE_FREE(options);
 	SAFE_FREE(orgoptions);
 	SAFE_FREE(resolved_path);
-	SAFE_FREE(share_name);
 	return rc;
 }