diff mbox

[linux-cifs-client,06/19] mount.cifs: parse unc into separate fields

Message ID 1269613542-6402-7-git-send-email-jlayton@samba.org (mailing list archive)
State New, archived
Headers show

Commit Message

Jeff Layton March 26, 2010, 2:25 p.m. UTC
None
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;
 }