cifs-utils: bump version to 4.2
[cifs-utils.git] / mount.cifs.c
1 /*
2  * Mount helper utility for Linux CIFS VFS (virtual filesystem) client
3  * Copyright (C) 2003,2008 Steve French  (sfrench@us.ibm.com)
4  * Copyright (C) 2008 Jeremy Allison (jra@samba.org)
5  * Copyright (C) 2010 Jeff Layton (jlayton@samba.org)
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif /* HAVE_CONFIG_H */
24
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <pwd.h>
29 #include <grp.h>
30 #include <ctype.h>
31 #include <sys/types.h>
32 #include <sys/mount.h>
33 #include <sys/stat.h>
34 #include <sys/utsname.h>
35 #include <sys/socket.h>
36 #include <arpa/inet.h>
37 #include <getopt.h>
38 #include <errno.h>
39 #include <netdb.h>
40 #include <string.h>
41 #include <mntent.h>
42 #include <fcntl.h>
43 #include <limits.h>
44 #include <fstab.h>
45 #include <sys/mman.h>
46 #include <sys/wait.h>
47 #ifdef HAVE_LIBCAP
48 #include <sys/capability.h>
49 #endif /* HAVE_LIBCAP */
50 #include "mount.h"
51 #include "util.h"
52
53 #ifndef MS_MOVE 
54 #define MS_MOVE 8192 
55 #endif 
56
57 #ifndef MS_BIND
58 #define MS_BIND 4096
59 #endif
60
61 /* private flags - clear these before passing to kernel */
62 #define MS_USERS        0x40000000
63 #define MS_USER         0x80000000
64
65 #define MAX_UNC_LEN 1024
66
67 /* I believe that the kernel limits options data to a page */
68 #define MAX_OPTIONS_LEN 4096
69
70 /* max length of mtab options */
71 #define MTAB_OPTIONS_LEN 220
72
73 /*
74  * Maximum length of "share" portion of a UNC. I have no idea if this is at
75  * all valid. According to MSDN, the typical max length of any component is
76  * 255, so use that here.
77  */
78 #define MAX_SHARE_LEN 256
79
80 /* max length of username (somewhat made up here) */
81 #define MAX_USERNAME_SIZE 32
82
83 /* currently maximum length of IPv6 address string */
84 #define MAX_ADDRESS_LEN INET6_ADDRSTRLEN
85
86 /* limit list of addresses to 16 max-size addrs */
87 #define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * 16)
88
89 #ifndef SAFE_FREE
90 #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x = NULL; } } while (0)
91 #endif
92
93 #define MOUNT_PASSWD_SIZE 128
94 #define DOMAIN_SIZE 64
95
96 /*
97  * value of the ver= option that gets passed to the kernel. Used to indicate
98  * behavioral changes introduced in the mount helper.
99  */
100 #define OPTIONS_VERSION "1"
101
102 /*
103  * mount.cifs has been the subject of many "security" bugs that have arisen
104  * because of users and distributions installing it as a setuid root program
105  * before it had been audited for security holes. The default behavior is
106  * now to allow mount.cifs to be run as a setuid root program. Some admins
107  * may want to disable this fully, so this switch remains in place.
108  */
109 #define CIFS_DISABLE_SETUID_CAPABILITY 0
110
111 /*
112  * When an unprivileged user runs a setuid mount.cifs, we set certain mount
113  * flags by default. These defaults can be changed here.
114  */
115 #define CIFS_SETUID_FLAGS (MS_NOSUID|MS_NODEV)
116
117 /* struct for holding parsed mount info for use by privleged process */
118 struct parsed_mount_info {
119         unsigned long flags;
120         char host[NI_MAXHOST + 1];
121         char share[MAX_SHARE_LEN + 1];
122         char prefix[PATH_MAX + 1];
123         char options[MAX_OPTIONS_LEN];
124         char domain[DOMAIN_SIZE + 1];
125         char username[MAX_USERNAME_SIZE + 1];
126         char password[MOUNT_PASSWD_SIZE + 1];
127         char addrlist[MAX_ADDR_LIST_LEN];
128         unsigned int got_user:1;
129         unsigned int got_password:1;
130         unsigned int fakemnt:1;
131         unsigned int nomtab:1;
132         unsigned int verboseflag:1;
133 };
134
135 const char *thisprogram;
136 const char *cifs_fstype = "cifs";
137
138 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
139
140 static int check_setuid(void)
141 {
142         if (geteuid()) {
143                 fprintf(stderr, "This program is not installed setuid root - "
144                         " \"user\" CIFS mounts not supported.\n");
145                 return EX_USAGE;
146         }
147
148 #if CIFS_DISABLE_SETUID_CAPABILITY
149         if (getuid() && !geteuid()) {
150                 printf("This mount.cifs program has been built with the "
151                        "ability to run as a setuid root program disabled.\n");
152                 return EX_USAGE;
153         }
154 #endif /* CIFS_DISABLE_SETUID_CAPABILITY */
155
156         return 0;
157 }
158
159 static int
160 check_fstab(const char *progname, const char *mountpoint, const char *devname,
161             char **options)
162 {
163         FILE *fstab;
164         struct mntent *mnt;
165
166         /* make sure this mount is listed in /etc/fstab */
167         fstab = setmntent(_PATH_FSTAB, "r");
168         if (!fstab) {
169                 fprintf(stderr, "Couldn't open %s for reading!\n", _PATH_FSTAB);
170                 return EX_FILEIO;
171         }
172
173         while ((mnt = getmntent(fstab))) {
174                 if (!strcmp(mountpoint, mnt->mnt_dir))
175                         break;
176         }
177         endmntent(fstab);
178
179         if (mnt == NULL || strcmp(mnt->mnt_fsname, devname)) {
180                 fprintf(stderr, "%s: permission denied: no match for "
181                         "%s found in %s\n", progname, mountpoint, _PATH_FSTAB);
182                 return EX_USAGE;
183         }
184
185         /*
186          * 'mount' munges the options from fstab before passing them
187          * to us. It is non-trivial to test that we have the correct
188          * set of options. We don't want to trust what the user
189          * gave us, so just take whatever is in /etc/fstab.
190          */
191         free(*options);
192         *options = strdup(mnt->mnt_opts);
193         return 0;
194 }
195
196 /* BB finish BB
197
198         cifs_umount
199         open nofollow - avoid symlink exposure? 
200         get owner of dir see if matches self or if root
201         call system(umount argv) etc.
202
203 BB end finish BB */
204
205 static int mount_cifs_usage(FILE * stream)
206 {
207         fprintf(stream, "\nUsage:  %s <remotetarget> <dir> -o <options>\n",
208                 thisprogram);
209         fprintf(stream, "\nMount the remote target, specified as a UNC name,");
210         fprintf(stream, " to a local directory.\n\nOptions:\n");
211         fprintf(stream, "\tuser=<arg>\n\tpass=<arg>\n\tdom=<arg>\n");
212         fprintf(stream, "\nLess commonly used options:");
213         fprintf(stream,
214                 "\n\tcredentials=<filename>,guest,perm,noperm,setuids,nosetuids,rw,ro,");
215         fprintf(stream,
216                 "\n\tsep=<char>,iocharset=<codepage>,suid,nosuid,exec,noexec,serverino,");
217         fprintf(stream,
218                 "\n\tmapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
219         fprintf(stream,
220                 "\n\tdirectio,nounix,cifsacl,sec=<authentication mechanism>,sign");
221         fprintf(stream,
222                 "\n\nOptions not needed for servers supporting CIFS Unix extensions");
223         fprintf(stream,
224                 "\n\t(e.g. unneeded for mounts to most Samba versions):");
225         fprintf(stream,
226                 "\n\tuid=<uid>,gid=<gid>,dir_mode=<mode>,file_mode=<mode>,sfu");
227         fprintf(stream, "\n\nRarely used options:");
228         fprintf(stream,
229                 "\n\tport=<tcpport>,rsize=<size>,wsize=<size>,unc=<unc_name>,ip=<ip_address>,");
230         fprintf(stream,
231                 "\n\tdev,nodev,nouser_xattr,netbiosname=<OUR_RFC1001NAME>,hard,soft,intr,");
232         fprintf(stream,
233                 "\n\tnointr,ignorecase,noposixpaths,noacl,prefixpath=<path>,nobrl");
234         fprintf(stream,
235                 "\n\nOptions are described in more detail in the manual page");
236         fprintf(stream, "\n\tman 8 mount.cifs\n");
237         fprintf(stream, "\nTo display the version number of the mount helper:");
238         fprintf(stream, "\n\t%s -V\n", thisprogram);
239
240         if (stream == stderr)
241                 return EX_USAGE;
242         return 0;
243 }
244
245 /*
246  * CIFS has to "escape" commas in the password field so that they don't
247  * end up getting confused for option delimiters. Copy password into pw
248  * field, turning any commas into double commas.
249  */
250 static int set_password(struct parsed_mount_info *parsed_info, const char *src)
251 {
252         char *dst = parsed_info->password;
253         unsigned int i = 0, j = 0;
254
255         while (src[i]) {
256                 if (src[i] == ',')
257                         dst[j++] = ',';
258                 dst[j++] = src[i++];
259                 if (j > sizeof(parsed_info->password)) {
260                         fprintf(stderr, "Converted password too long!\n");
261                         return EX_USAGE;
262                 }
263         }
264         dst[j] = '\0';
265         parsed_info->got_password = 1;
266         return 0;
267 }
268
269 /* caller frees username if necessary */
270 static char *getusername(uid_t uid)
271 {
272         char *username = NULL;
273         struct passwd *password = getpwuid(uid);
274
275         if (password)
276                 username = password->pw_name;
277         return username;
278 }
279
280 /*
281  * Parse a username string into parsed_mount_info fields. The format is:
282  *
283  * DOMAIN\username%password
284  *
285  * ...obviously the only required component is "username". The source string
286  * is modified in the process, but it should remain unchanged at the end.
287  */
288 static int parse_username(char *rawuser, struct parsed_mount_info *parsed_info)
289 {
290         char *user, *password, slash;
291         int rc = 0;
292
293         /* everything after first % sign is a password */
294         password = strchr(rawuser, '%');
295         if (password) {
296                 rc = set_password(parsed_info, password);
297                 if (rc)
298                         return rc;
299         }
300
301         /* everything after first '/' or '\' is a username */
302         user = strchr(rawuser, '/');
303         if (!user)
304                 user = strchr(rawuser, '\\');
305
306         /* everything before that slash is a domain */
307         if (user) {
308                 slash = *user;
309                 *user = '\0';
310                 strlcpy(parsed_info->domain, rawuser,
311                         sizeof(parsed_info->domain));
312                 *(user++) = slash;
313         } else {
314                 user = rawuser;
315         }
316
317         strlcpy(parsed_info->username, user, sizeof(parsed_info->username));
318         parsed_info->got_user = 1;
319         if (password)
320                 *password = '%';
321
322         return 0;
323 }
324
325 static int open_cred_file(char *file_name,
326                           struct parsed_mount_info *parsed_info)
327 {
328         char *line_buf;
329         char *temp_val, *newline;
330         FILE *fs;
331         int i, length;
332
333         i = access(file_name, R_OK);
334         if (i)
335                 return i;
336
337         fs = fopen(file_name, "r");
338         if (fs == NULL)
339                 return errno;
340         line_buf = (char *)malloc(4096);
341         if (line_buf == NULL) {
342                 fclose(fs);
343                 return EX_SYSERR;
344         }
345
346         while (fgets(line_buf, 4096, fs)) {
347                 /* parse line from credential file */
348
349                 /* eat leading white space */
350                 for (i = 0; i < 4086; i++) {
351                         if ((line_buf[i] != ' ') && (line_buf[i] != '\t'))
352                                 break;
353                         /* if whitespace - skip past it */
354                 }
355
356                 /* NULL terminate at newline */
357                 newline = strchr(line_buf + i, '\n');
358                 if (newline)
359                         *newline = '\0';
360
361                 if (strncasecmp("username", line_buf + i, 8) == 0) {
362                         temp_val = strchr(line_buf + i, '=');
363                         if (temp_val) {
364                                 /* go past equals sign */
365                                 temp_val++;
366                                 for (length = 0; length < 4087; length++) {
367                                         if ((temp_val[length] == '\n')
368                                             || (temp_val[length] == '\0')) {
369                                                 temp_val[length] = '\0';
370                                                 break;
371                                         }
372                                 }
373                                 if (length > 4086) {
374                                         fprintf(stderr,
375                                                 "mount.cifs failed due to malformed username in credentials file\n");
376                                         memset(line_buf, 0, 4096);
377                                         return EX_USAGE;
378                                 }
379                                 parsed_info->got_user = 1;
380                                 strlcpy(parsed_info->username, temp_val,
381                                         sizeof(parsed_info->username));
382                         }
383                 } else if (strncasecmp("password", line_buf + i, 8) == 0) {
384                         temp_val = strchr(line_buf + i, '=');
385                         if (!temp_val)
386                                 continue;
387                         ++temp_val;
388                         i = set_password(parsed_info, temp_val);
389                         if (i)
390                                 return i;
391                 } else if (strncasecmp("domain", line_buf + i, 6) == 0) {
392                         temp_val = strchr(line_buf + i, '=');
393                         if (temp_val) {
394                                 /* go past equals sign */
395                                 temp_val++;
396                                 if (parsed_info->verboseflag)
397                                         fprintf(stderr, "\nDomain %s\n",
398                                                 temp_val);
399
400                                 for (length = 0; length < DOMAIN_SIZE + 1;
401                                      length++) {
402                                         if ((temp_val[length] == '\n')
403                                             || (temp_val[length] == '\0')) {
404                                                 temp_val[length] = '\0';
405                                                 break;
406                                         }
407                                 }
408
409                                 if (length > DOMAIN_SIZE) {
410                                         fprintf(stderr,
411                                                 "mount.cifs failed: domain in credentials file too long\n");
412                                         return EX_USAGE;
413                                 }
414
415                                 strlcpy(parsed_info->domain, temp_val,
416                                         sizeof(parsed_info->domain));
417                         }
418                 }
419
420         }
421         fclose(fs);
422         SAFE_FREE(line_buf);
423         return 0;
424 }
425
426 static int
427 get_password_from_file(int file_descript, char *filename,
428                        struct parsed_mount_info *parsed_info)
429 {
430         int rc = 0;
431         char buf[sizeof(parsed_info->password) + 1];
432
433         if (filename != NULL) {
434                 rc = access(filename, R_OK);
435                 if (rc) {
436                         fprintf(stderr,
437                                 "mount.cifs failed: access check of %s failed: %s\n",
438                                 filename, strerror(errno));
439                         return EX_SYSERR;
440                 }
441                 file_descript = open(filename, O_RDONLY);
442                 if (file_descript < 0) {
443                         fprintf(stderr,
444                                 "mount.cifs failed. %s attempting to open password file %s\n",
445                                 strerror(errno), filename);
446                         return EX_SYSERR;
447                 }
448         }
449
450         memset(buf, 0, sizeof(buf));
451         rc = read(file_descript, buf, sizeof(buf) - 1);
452         if (rc < 0) {
453                 fprintf(stderr,
454                         "mount.cifs failed. Error %s reading password file\n",
455                         strerror(errno));
456                 rc = EX_SYSERR;
457                 goto get_pw_exit;
458         }
459
460         rc = set_password(parsed_info, buf);
461
462 get_pw_exit:
463         if (filename != NULL) {
464                 close(file_descript);
465         }
466         return rc;
467 }
468
469 static int
470 parse_options(const char *data, struct parsed_mount_info *parsed_info)
471 {
472         char *value = NULL, *equals = NULL;
473         char *next_keyword = NULL;
474         char *out = parsed_info->options;
475         unsigned long *filesys_flags = &parsed_info->flags;
476         int out_len = 0;
477         int word_len;
478         int rc = 0;
479         int got_uid = 0, got_gid = 0;
480         char user[32];
481         char group[32];
482
483         /* make sure we're starting from beginning */
484         out[0] = '\0';
485
486         /* BB fixme check for separator override BB */
487         if (getuid()) {
488                 got_uid = 1;
489                 snprintf(user, sizeof(user), "%u", getuid());
490                 got_gid = 1;
491                 snprintf(group, sizeof(group), "%u", getgid());
492         }
493
494         if (!data)
495                 return EX_USAGE;
496
497         /*
498          * format is keyword,keyword2=value2,keyword3=value3... 
499          * data  = next keyword
500          * value = next value ie stuff after equal sign
501          */
502         while (data && *data) {
503                 next_keyword = strchr(data, ',');       /* BB handle sep= */
504
505                 /* temporarily null terminate end of keyword=value pair */
506                 if (next_keyword)
507                         *next_keyword++ = 0;
508
509                 /* temporarily null terminate keyword if there's a value */
510                 value = NULL;
511                 if ((equals = strchr(data, '=')) != NULL) {
512                         *equals = '\0';
513                         value = equals + 1;
514                 }
515
516                 /* FIXME: turn into a token parser? */
517                 if (strncmp(data, "users", 5) == 0) {
518                         if (!value || !*value) {
519                                 *filesys_flags |= MS_USERS;
520                                 goto nocopy;
521                         }
522                 } else if (strncmp(data, "user_xattr", 10) == 0) {
523                         /* do nothing - need to skip so not parsed as user name */
524                 } else if (strncmp(data, "user", 4) == 0) {
525                         if (!value || !*value) {
526                                 if (data[4] == '\0') {
527                                         *filesys_flags |= MS_USER;
528                                         goto nocopy;
529                                 } else {
530                                         fprintf(stderr,
531                                                 "username specified with no parameter\n");
532                                         return EX_USAGE;
533                                 }
534                         } else {
535                                 if (strnlen(value, 260) >= 260) {
536                                         fprintf(stderr, "username too long\n");
537                                         return EX_USAGE;
538                                 }
539                                 rc = parse_username(value, parsed_info);
540                                 if (rc) {
541                                         fprintf(stderr,
542                                                 "problem parsing username\n");
543                                         return rc;
544                                 }
545                                 goto nocopy;
546                         }
547                 } else if (strncmp(data, "pass", 4) == 0) {
548                         if (parsed_info->got_password) {
549                                 fprintf(stderr,
550                                         "password specified twice, ignoring second\n");
551                                 goto nocopy;
552                         }
553                         if (!value || !*value) {
554                                 parsed_info->got_password = 1;
555                                 goto nocopy;
556                         }
557                         rc = set_password(parsed_info, value);
558                         if (rc)
559                                 return rc;
560                         goto nocopy;
561                 } else if (strncmp(data, "sec", 3) == 0) {
562                         if (value) {
563                                 if (!strncmp(value, "none", 4) ||
564                                     !strncmp(value, "krb5", 4))
565                                         parsed_info->got_password = 1;
566                         }
567                 } else if (strncmp(data, "ip", 2) == 0) {
568                         if (!value || !*value) {
569                                 fprintf(stderr,
570                                         "target ip address argument missing");
571                         } else if (strnlen(value, MAX_ADDRESS_LEN) <=
572                                    MAX_ADDRESS_LEN) {
573                                 if (parsed_info->verboseflag)
574                                         fprintf(stderr,
575                                                 "ip address %s override specified\n",
576                                                 value);
577                         } else {
578                                 fprintf(stderr, "ip address too long\n");
579                                 return EX_USAGE;
580                         }
581                 } else if ((strncmp(data, "unc", 3) == 0)
582                            || (strncmp(data, "target", 6) == 0)
583                            || (strncmp(data, "path", 4) == 0)) {
584                         if (!value || !*value) {
585                                 fprintf(stderr,
586                                         "invalid path to network resource\n");
587                                 return EX_USAGE;        /* needs_arg; */
588                         }
589                         rc = parse_unc(value, parsed_info);
590                         if (rc)
591                                 return rc;
592                 } else if ((strncmp(data, "dom" /* domain */ , 3) == 0)
593                            || (strncmp(data, "workg", 5) == 0)) {
594                         /* note this allows for synonyms of "domain"
595                            such as "DOM" and "dom" and "workgroup"
596                            and "WORKGRP" etc. */
597                         if (!value || !*value) {
598                                 fprintf(stderr, "CIFS: invalid domain name\n");
599                                 return EX_USAGE;
600                         }
601                         if (strnlen(value, sizeof(parsed_info->domain)) >=
602                             sizeof(parsed_info->domain)) {
603                                 fprintf(stderr, "domain name too long\n");
604                                 return EX_USAGE;
605                         }
606                         strlcpy(parsed_info->domain, value,
607                                 sizeof(parsed_info->domain));
608                         goto nocopy;
609                 } else if (strncmp(data, "cred", 4) == 0) {
610                         if (value && *value) {
611                                 rc = open_cred_file(value, parsed_info);
612                                 if (rc) {
613                                         fprintf(stderr,
614                                                 "error %d (%s) opening credential file %s\n",
615                                                 rc, strerror(rc), value);
616                                         return rc;
617                                 }
618                         } else {
619                                 fprintf(stderr,
620                                         "invalid credential file name specified\n");
621                                 return EX_USAGE;
622                         }
623                 } else if (strncmp(data, "uid", 3) == 0) {
624                         if (value && *value) {
625                                 got_uid = 1;
626                                 if (!isdigit(*value)) {
627                                         struct passwd *pw;
628
629                                         if (!(pw = getpwnam(value))) {
630                                                 fprintf(stderr,
631                                                         "bad user name \"%s\"\n",
632                                                         value);
633                                                 return EX_USAGE;
634                                         }
635                                         snprintf(user, sizeof(user), "%u",
636                                                  pw->pw_uid);
637                                 } else {
638                                         strlcpy(user, value, sizeof(user));
639                                 }
640                         }
641                         goto nocopy;
642                 } else if (strncmp(data, "gid", 3) == 0) {
643                         if (value && *value) {
644                                 got_gid = 1;
645                                 if (!isdigit(*value)) {
646                                         struct group *gr;
647
648                                         if (!(gr = getgrnam(value))) {
649                                                 fprintf(stderr,
650                                                         "bad group name \"%s\"\n",
651                                                         value);
652                                                 return EX_USAGE;
653                                         }
654                                         snprintf(group, sizeof(group), "%u",
655                                                  gr->gr_gid);
656                                 } else {
657                                         strlcpy(group, value, sizeof(group));
658                                 }
659                         }
660                         goto nocopy;
661                         /* fmask and dmask synonyms for people used to smbfs syntax */
662                 } else if (strcmp(data, "file_mode") == 0
663                            || strcmp(data, "fmask") == 0) {
664                         if (!value || !*value) {
665                                 fprintf(stderr,
666                                         "Option '%s' requires a numerical argument\n",
667                                         data);
668                                 return EX_USAGE;
669                         }
670
671                         if (value[0] != '0') {
672                                 fprintf(stderr,
673                                         "WARNING: '%s' not expressed in octal.\n",
674                                         data);
675                         }
676
677                         if (strcmp(data, "fmask") == 0) {
678                                 fprintf(stderr,
679                                         "WARNING: CIFS mount option 'fmask' is deprecated. Use 'file_mode' instead.\n");
680                                 data = "file_mode";     /* BB fix this */
681                         }
682                 } else if (strcmp(data, "dir_mode") == 0
683                            || strcmp(data, "dmask") == 0) {
684                         if (!value || !*value) {
685                                 fprintf(stderr,
686                                         "Option '%s' requires a numerical argument\n",
687                                         data);
688                                 return EX_USAGE;
689                         }
690
691                         if (value[0] != '0') {
692                                 fprintf(stderr,
693                                         "WARNING: '%s' not expressed in octal.\n",
694                                         data);
695                         }
696
697                         if (strcmp(data, "dmask") == 0) {
698                                 fprintf(stderr,
699                                         "WARNING: CIFS mount option 'dmask' is deprecated. Use 'dir_mode' instead.\n");
700                                 data = "dir_mode";
701                         }
702                         /* the following eight mount options should be
703                            stripped out from what is passed into the kernel
704                            since these eight options are best passed as the
705                            mount flags rather than redundantly to the kernel 
706                            and could generate spurious warnings depending on the
707                            level of the corresponding cifs vfs kernel code */
708                 } else if (strncmp(data, "nosuid", 6) == 0) {
709                         *filesys_flags |= MS_NOSUID;
710                 } else if (strncmp(data, "suid", 4) == 0) {
711                         *filesys_flags &= ~MS_NOSUID;
712                 } else if (strncmp(data, "nodev", 5) == 0) {
713                         *filesys_flags |= MS_NODEV;
714                 } else if ((strncmp(data, "nobrl", 5) == 0) ||
715                            (strncmp(data, "nolock", 6) == 0)) {
716                         *filesys_flags &= ~MS_MANDLOCK;
717                 } else if (strncmp(data, "dev", 3) == 0) {
718                         *filesys_flags &= ~MS_NODEV;
719                 } else if (strncmp(data, "noexec", 6) == 0) {
720                         *filesys_flags |= MS_NOEXEC;
721                 } else if (strncmp(data, "exec", 4) == 0) {
722                         *filesys_flags &= ~MS_NOEXEC;
723                 } else if (strncmp(data, "guest", 5) == 0) {
724                         parsed_info->got_user = 1;
725                         parsed_info->got_password = 1;
726                 } else if (strncmp(data, "ro", 2) == 0) {
727                         *filesys_flags |= MS_RDONLY;
728                         goto nocopy;
729                 } else if (strncmp(data, "rw", 2) == 0) {
730                         *filesys_flags &= ~MS_RDONLY;
731                         goto nocopy;
732                 } else if (strncmp(data, "remount", 7) == 0) {
733                         *filesys_flags |= MS_REMOUNT;
734                 }
735
736                 /* check size before copying option to buffer */
737                 word_len = strlen(data);
738                 if (value)
739                         word_len += 1 + strlen(value);
740
741                 /* need 2 extra bytes for comma and null byte */
742                 if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
743                         fprintf(stderr, "Options string too long\n");
744                         return EX_USAGE;
745                 }
746
747                 /* put back equals sign, if any */
748                 if (equals)
749                         *equals = '=';
750
751                 /* go ahead and copy */
752                 if (out_len)
753                         strlcat(out, ",", MAX_OPTIONS_LEN);
754
755                 strlcat(out, data, MAX_OPTIONS_LEN);
756                 out_len = strlen(out);
757 nocopy:
758                 data = next_keyword;
759         }
760
761         /* special-case the uid and gid */
762         if (got_uid) {
763                 word_len = strlen(user);
764
765                 if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
766                         fprintf(stderr, "Options string too long\n");
767                         return EX_USAGE;
768                 }
769
770                 if (out_len) {
771                         strlcat(out, ",", out_len + word_len + 6);
772                         out_len++;
773                 }
774                 snprintf(out + out_len, word_len + 5, "uid=%s", user);
775                 out_len = strlen(out);
776         }
777         if (got_gid) {
778                 word_len = strlen(group);
779
780                 if (out_len + 1 + word_len + 6 > MAX_OPTIONS_LEN) {
781                         fprintf(stderr, "Options string too long\n");
782                         return EX_USAGE;
783                 }
784
785                 if (out_len) {
786                         strlcat(out, ",", out_len + word_len + 6);
787                         out_len++;
788                 }
789                 snprintf(out + out_len, word_len + 5, "gid=%s", group);
790                 out_len = strlen(out);
791         }
792
793         return 0;
794 }
795
796 /*
797  * resolve "host" portion of parsed info to comma-separated list of
798  * address(es)
799  */
800 static int resolve_host(struct parsed_mount_info *parsed_info)
801 {
802         int rc;
803         /* 10 for max width of decimal scopeid */
804         char tmpbuf[NI_MAXHOST + 1 + 10 + 1];
805         const char *ipaddr;
806         size_t len;
807         struct addrinfo *addrlist, *addr;
808         struct sockaddr_in *sin;
809         struct sockaddr_in6 *sin6;
810
811         rc = getaddrinfo(parsed_info->host, NULL, NULL, &addrlist);
812         if (rc != 0) {
813                 fprintf(stderr, "mount error: could not resolve address for "
814                         "%s: %s\n", parsed_info->host,
815                         rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
816                 /* FIXME: return better error based on rc? */
817                 return EX_USAGE;
818         }
819
820         addr = addrlist;
821         while (addr) {
822                 /* skip non-TCP entries */
823                 if (addr->ai_socktype != SOCK_STREAM ||
824                     addr->ai_protocol != IPPROTO_TCP) {
825                         addr = addr->ai_next;
826                         continue;
827                 }
828
829                 switch (addr->ai_addr->sa_family) {
830                 case AF_INET6:
831                         sin6 = (struct sockaddr_in6 *)addr->ai_addr;
832                         ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf,
833                                            sizeof(tmpbuf));
834                         if (!ipaddr) {
835                                 rc = EX_SYSERR;
836                                 fprintf(stderr,
837                                         "mount error: problem parsing address "
838                                         "list: %s\n", strerror(errno));
839                                 goto resolve_host_out;
840                         }
841
842                         if (sin6->sin6_scope_id) {
843                                 len = strnlen(tmpbuf, sizeof(tmpbuf));
844                                 ipaddr = tmpbuf + len;
845                                 snprintf(tmpbuf, sizeof(tmpbuf) - len, "%%%u",
846                                          sin6->sin6_scope_id);
847                         }
848                         break;
849                 case AF_INET:
850                         sin = (struct sockaddr_in *)addr->ai_addr;
851                         ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf,
852                                            sizeof(tmpbuf));
853                         if (!ipaddr) {
854                                 rc = EX_SYSERR;
855                                 fprintf(stderr,
856                                         "mount error: problem parsing address "
857                                         "list: %s\n", strerror(errno));
858                                 goto resolve_host_out;
859                         }
860
861                         break;
862                 default:
863                         addr = addr->ai_next;
864                         continue;
865                 }
866
867                 if (parsed_info->addrlist[0] != '\0')
868                         strlcat(parsed_info->addrlist, ",",
869                                 sizeof(parsed_info->addrlist));
870                 strlcat(parsed_info->addrlist, tmpbuf,
871                         sizeof(parsed_info->addrlist));
872                 addr = addr->ai_next;
873         }
874
875 resolve_host_out:
876         freeaddrinfo(addrlist);
877         return rc;
878 }
879
880 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
881 {
882         int length = strnlen(unc_name, MAX_UNC_LEN);
883         const char *host, *share, *prepath;
884         size_t hostlen, sharelen, prepathlen;
885
886         if (length > (MAX_UNC_LEN - 1)) {
887                 fprintf(stderr, "mount error: UNC name too long\n");
888                 return EX_USAGE;
889         }
890
891         if (length < 3) {
892                 fprintf(stderr, "mount error: UNC name too short\n");
893                 return EX_USAGE;
894         }
895
896         if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
897             (strncasecmp("smb://", unc_name, 6) == 0)) {
898                 fprintf(stderr,
899                         "Mounting cifs URL not implemented yet. Attempt to mount %s\n",
900                         unc_name);
901                 return EX_USAGE;
902         }
903
904         /* Set up "host" and "share" pointers based on UNC format. */
905         if (strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
906                 /*
907                  * check for nfs syntax (server:/share/prepath)
908                  *
909                  * FIXME: IPv6 addresses?
910                  */
911                 host = unc_name;
912                 share = strchr(host, ':');
913                 if (!share) {
914                         fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
915                         return EX_USAGE;
916                 }
917                 hostlen = share - host;
918                 share++;
919                 if (*share == '/')
920                         ++share;
921         } else {
922                 host = unc_name + 2;
923                 hostlen = strcspn(host, "/\\");
924                 if (!hostlen) {
925                         fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
926                         return EX_USAGE;
927                 }
928                 share = host + hostlen + 1;
929         }
930
931         if (hostlen + 1 > sizeof(parsed_info->host)) {
932                 fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
933                 return EX_USAGE;
934         }
935
936         sharelen = strcspn(share, "/\\");
937         if (sharelen + 1 > sizeof(parsed_info->share)) {
938                 fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
939                 return EX_USAGE;
940         }
941
942         prepath = share + sharelen;
943         prepathlen = strlen(prepath);
944
945         if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
946                 fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
947                 return EX_USAGE;
948         }
949
950         /* copy pieces into their resepective buffers */
951         memcpy(parsed_info->host, host, hostlen);
952         memcpy(parsed_info->share, share, sharelen);
953         memcpy(parsed_info->prefix, prepath, prepathlen);
954
955         return 0;
956 }
957
958 static int get_pw_from_env(struct parsed_mount_info *parsed_info)
959 {
960         int rc = 0;
961
962         if (getenv("PASSWD"))
963                 rc = set_password(parsed_info, getenv("PASSWD"));
964         else if (getenv("PASSWD_FD"))
965                 rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
966                                             parsed_info);
967         else if (getenv("PASSWD_FILE"))
968                 rc = get_password_from_file(0, getenv("PASSWD_FILE"),
969                                             parsed_info);
970
971         return rc;
972 }
973
974 static struct option longopts[] = {
975         {"all", 0, NULL, 'a'},
976         {"help", 0, NULL, 'h'},
977         {"move", 0, NULL, 'm'},
978         {"bind", 0, NULL, 'b'},
979         {"read-only", 0, NULL, 'r'},
980         {"ro", 0, NULL, 'r'},
981         {"verbose", 0, NULL, 'v'},
982         {"version", 0, NULL, 'V'},
983         {"read-write", 0, NULL, 'w'},
984         {"rw", 0, NULL, 'w'},
985         {"options", 1, NULL, 'o'},
986         {"type", 1, NULL, 't'},
987         {"uid", 1, NULL, '1'},
988         {"gid", 1, NULL, '2'},
989         {"user", 1, NULL, 'u'},
990         {"username", 1, NULL, 'u'},
991         {"dom", 1, NULL, 'd'},
992         {"domain", 1, NULL, 'd'},
993         {"password", 1, NULL, 'p'},
994         {"pass", 1, NULL, 'p'},
995         {"credentials", 1, NULL, 'c'},
996         {"port", 1, NULL, 'P'},
997         {NULL, 0, NULL, 0}
998 };
999
1000 /* convert a string to uppercase. return false if the string
1001  * wasn't ASCII. Return success on a NULL ptr */
1002 static int uppercase_string(char *string)
1003 {
1004         if (!string)
1005                 return 1;
1006
1007         while (*string) {
1008                 /* check for unicode */
1009                 if ((unsigned char)string[0] & 0x80)
1010                         return 0;
1011                 *string = toupper((unsigned char)*string);
1012                 string++;
1013         }
1014
1015         return 1;
1016 }
1017
1018 static void print_cifs_mount_version(void)
1019 {
1020         printf("mount.cifs version: %s\n", VERSION);
1021 }
1022
1023 /*
1024  * This function borrowed from fuse-utils...
1025  *
1026  * glibc's addmntent (at least as of 2.10 or so) doesn't properly encode
1027  * newlines embedded within the text fields. To make sure no one corrupts
1028  * the mtab, fail the mount if there are embedded newlines.
1029  */
1030 static int check_newline(const char *progname, const char *name)
1031 {
1032         const char *s;
1033         for (s = "\n"; *s; s++) {
1034                 if (strchr(name, *s)) {
1035                         fprintf(stderr,
1036                                 "%s: illegal character 0x%02x in mount entry\n",
1037                                 progname, *s);
1038                         return EX_USAGE;
1039                 }
1040         }
1041         return 0;
1042 }
1043
1044 static int check_mtab(const char *progname, const char *devname,
1045                       const char *dir)
1046 {
1047         if (check_newline(progname, devname) == -1 ||
1048             check_newline(progname, dir) == -1)
1049                 return EX_USAGE;
1050         return 0;
1051 }
1052
1053 static int
1054 add_mtab(char *devname, char *mountpoint, unsigned long flags)
1055 {
1056         int rc = 0;
1057         uid_t uid;
1058         char *mount_user = NULL;
1059         struct mntent mountent;
1060         FILE *pmntfile;
1061         sigset_t mask, oldmask;
1062
1063         uid = getuid();
1064         if (uid != 0)
1065                 mount_user = getusername(uid);
1066
1067         /*
1068          * Set the real uid to the effective uid. This prevents unprivileged
1069          * users from sending signals to this process, though ^c on controlling
1070          * terminal should still work.
1071          */
1072         rc = setreuid(geteuid(), -1);
1073         if (rc != 0) {
1074                 fprintf(stderr, "Unable to set real uid to effective uid: %s\n",
1075                                 strerror(errno));
1076                 rc = EX_FILEIO;
1077         }
1078
1079         rc = sigfillset(&mask);
1080         if (rc) {
1081                 fprintf(stderr, "Unable to set filled signal mask\n");
1082                 return EX_FILEIO;
1083         }
1084
1085         rc = sigprocmask(SIG_SETMASK, &mask, &oldmask);
1086         if (rc) {
1087                 fprintf(stderr, "Unable to make process ignore signals\n");
1088                 return EX_FILEIO;
1089         }
1090
1091         atexit(unlock_mtab);
1092         rc = lock_mtab();
1093         if (rc) {
1094                 fprintf(stderr, "cannot lock mtab");
1095                 rc = EX_FILEIO;
1096                 goto add_mtab_exit;
1097         }
1098
1099         pmntfile = setmntent(MOUNTED, "a+");
1100         if (!pmntfile) {
1101                 fprintf(stderr, "could not update mount table\n");
1102                 unlock_mtab();
1103                 rc = EX_FILEIO;
1104                 goto add_mtab_exit;
1105         }
1106
1107         mountent.mnt_fsname = devname;
1108         mountent.mnt_dir = mountpoint;
1109         mountent.mnt_type = (char *)(void *)cifs_fstype;
1110         mountent.mnt_opts = (char *)calloc(MTAB_OPTIONS_LEN, 1);
1111         if (mountent.mnt_opts) {
1112                 if (flags & MS_RDONLY)
1113                         strlcat(mountent.mnt_opts, "ro", MTAB_OPTIONS_LEN);
1114                 else
1115                         strlcat(mountent.mnt_opts, "rw", MTAB_OPTIONS_LEN);
1116
1117                 if (flags & MS_MANDLOCK)
1118                         strlcat(mountent.mnt_opts, ",mand", MTAB_OPTIONS_LEN);
1119                 if (flags & MS_NOEXEC)
1120                         strlcat(mountent.mnt_opts, ",noexec", MTAB_OPTIONS_LEN);
1121                 if (flags & MS_NOSUID)
1122                         strlcat(mountent.mnt_opts, ",nosuid", MTAB_OPTIONS_LEN);
1123                 if (flags & MS_NODEV)
1124                         strlcat(mountent.mnt_opts, ",nodev", MTAB_OPTIONS_LEN);
1125                 if (flags & MS_SYNCHRONOUS)
1126                         strlcat(mountent.mnt_opts, ",sync", MTAB_OPTIONS_LEN);
1127                 if (mount_user) {
1128                         strlcat(mountent.mnt_opts, ",user=", MTAB_OPTIONS_LEN);
1129                         strlcat(mountent.mnt_opts, mount_user,
1130                                 MTAB_OPTIONS_LEN);
1131                 }
1132         }
1133         mountent.mnt_freq = 0;
1134         mountent.mnt_passno = 0;
1135         rc = addmntent(pmntfile, &mountent);
1136         endmntent(pmntfile);
1137         unlock_mtab();
1138         SAFE_FREE(mountent.mnt_opts);
1139 add_mtab_exit:
1140         sigprocmask(SIG_SETMASK, &oldmask, NULL);
1141         if (rc) {
1142                 fprintf(stderr, "unable to add mount entry to mtab\n");
1143                 rc = EX_FILEIO;
1144         }
1145
1146         return rc;
1147 }
1148
1149 #ifdef HAVE_LIBCAP
1150 static int
1151 drop_capabilities(int parent)
1152 {
1153         int rc = 0;
1154         cap_t caps;
1155         cap_value_t cap_list[2];
1156
1157         caps = cap_get_proc();
1158         if (caps == NULL) {
1159                 fprintf(stderr, "Unable to get current capability set: %s\n",
1160                         strerror(errno));
1161                 return EX_SYSERR;
1162         }
1163
1164         if (cap_clear(caps) == -1) {
1165                 fprintf(stderr, "Unable to clear capability set: %s\n",
1166                         strerror(errno));
1167                 rc = EX_SYSERR;
1168                 goto free_caps;
1169         }
1170
1171         /* parent needs to keep some capabilities */
1172         if (parent) {
1173                 cap_list[0] = CAP_SYS_ADMIN;
1174                 cap_list[1] = CAP_DAC_OVERRIDE;
1175                 if (cap_set_flag(caps, CAP_PERMITTED, 2, cap_list, CAP_SET) == -1) {
1176                         fprintf(stderr, "Unable to set permitted capabilities: %s\n",
1177                                 strerror(errno));
1178                         rc = EX_SYSERR;
1179                         goto free_caps;
1180                 }
1181                 if (cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_list, CAP_SET) == -1) {
1182                         fprintf(stderr, "Unable to set effective capabilities: %s\n",
1183                                 strerror(errno));
1184                         rc = EX_SYSERR;
1185                         goto free_caps;
1186                 }
1187         }
1188
1189         if (cap_set_proc(caps) != 0) {
1190                 fprintf(stderr, "Unable to set current process capabilities: %s\n",
1191                         strerror(errno));
1192                 rc = EX_SYSERR;
1193         }
1194 free_caps:
1195         cap_free(caps);
1196         return rc;
1197 }
1198 #else /* HAVE_LIBCAP */
1199 static int
1200 drop_capabilities(int parent)
1201 {
1202         return 0;
1203 }
1204 #endif /* HAVE_LIBCAP */
1205
1206 /* have the child drop root privileges */
1207 static int
1208 drop_child_privs(void)
1209 {
1210         int rc;
1211         uid_t uid = getuid();
1212         gid_t gid = getgid();
1213
1214         if (gid) {
1215                 rc = setgid(gid);
1216                 if (rc) {
1217                         fprintf(stderr, "Unable set group identity: %s\n",
1218                                         strerror(errno));
1219                         return EX_SYSERR;
1220                 }
1221         }
1222         if (uid) {
1223                 rc = setuid(uid);
1224                 if (rc) {
1225                         fprintf(stderr, "Unable set user identity: %s\n",
1226                                         strerror(errno));
1227                         return EX_SYSERR;
1228                 }
1229         }
1230
1231         return 0;
1232 }
1233
1234 static int
1235 assemble_mountinfo(struct parsed_mount_info *parsed_info,
1236                    const char *thisprogram, const char *mountpoint,
1237                    const char *orig_dev, char *orgoptions)
1238 {
1239         int rc;
1240
1241         rc = drop_capabilities(0);
1242         if (rc)
1243                 goto assemble_exit;
1244
1245         rc = drop_child_privs();
1246         if (rc)
1247                 goto assemble_exit;
1248
1249         if (getuid()) {
1250                 rc = check_fstab(thisprogram, mountpoint, orig_dev,
1251                                  &orgoptions);
1252                 if (rc)
1253                         goto assemble_exit;
1254
1255                 /* enable any default user mount flags */
1256                 parsed_info->flags |= CIFS_SETUID_FLAGS;
1257         }
1258
1259         rc = get_pw_from_env(parsed_info);
1260         if (rc)
1261                 goto assemble_exit;
1262
1263         if (orgoptions) {
1264                 rc = parse_options(orgoptions, parsed_info);
1265                 if (rc)
1266                         goto assemble_exit;
1267         }
1268
1269         if (getuid()) {
1270                 if (!(parsed_info->flags & (MS_USERS | MS_USER))) {
1271                         fprintf(stderr, "%s: permission denied\n", thisprogram);
1272                         rc = EX_USAGE;
1273                         goto assemble_exit;
1274                 }
1275         }
1276
1277         parsed_info->flags &= ~(MS_USERS | MS_USER);
1278
1279         rc = parse_unc(orig_dev, parsed_info);
1280         if (rc)
1281                 goto assemble_exit;
1282
1283         rc = resolve_host(parsed_info);
1284         if (rc)
1285                 goto assemble_exit;
1286
1287         if (!parsed_info->got_user) {
1288                 /*
1289                  * Note that the password will not be retrieved from the
1290                  * USER env variable (ie user%password form) as there is
1291                  * already a PASSWD environment varaible
1292                  */
1293                 if (getenv("USER"))
1294                         strlcpy(parsed_info->username, getenv("USER"),
1295                                 sizeof(parsed_info->username));
1296                 else
1297                         strlcpy(parsed_info->username, getusername(getuid()),
1298                                 sizeof(parsed_info->username));
1299                 parsed_info->got_user = 1;
1300         }
1301
1302         if (!parsed_info->got_password) {
1303                 /* getpass is obsolete, but there's apparently nothing that replaces it */
1304                 char *tmp_pass = getpass("Password: ");
1305                 if (!tmp_pass) {
1306                         fprintf(stderr, "Error reading password, exiting\n");
1307                         rc = EX_SYSERR;
1308                         goto assemble_exit;
1309                 }
1310                 rc = set_password(parsed_info, tmp_pass);
1311                 if (rc)
1312                         goto assemble_exit;
1313         }
1314
1315         /* copy in ver= string. It's not really needed, but what the hell */
1316         strlcat(parsed_info->options, ",ver=", sizeof(parsed_info->options));
1317         strlcat(parsed_info->options, OPTIONS_VERSION, sizeof(parsed_info->options));
1318
1319         /* copy in user= string */
1320         if (parsed_info->got_user) {
1321                 strlcat(parsed_info->options, ",user=",
1322                         sizeof(parsed_info->options));
1323                 strlcat(parsed_info->options, parsed_info->username,
1324                         sizeof(parsed_info->options));
1325         }
1326
1327         if (*parsed_info->domain) {
1328                 strlcat(parsed_info->options, ",domain=",
1329                         sizeof(parsed_info->options));
1330                 strlcat(parsed_info->options, parsed_info->domain,
1331                         sizeof(parsed_info->options));
1332         }
1333
1334 assemble_exit:
1335         return rc;
1336 }
1337
1338 int main(int argc, char **argv)
1339 {
1340         int c;
1341         char *orgoptions = NULL;
1342         char *mountpoint = NULL;
1343         char *options = NULL;
1344         char *dev_name = NULL, *orig_dev = NULL;
1345         char *currentaddress, *nextaddress;
1346         int rc = 0;
1347         int already_uppercased = 0;
1348         size_t options_size = MAX_OPTIONS_LEN;
1349         size_t dev_len;
1350         struct parsed_mount_info *parsed_info = NULL;
1351         pid_t pid;
1352
1353         rc = check_setuid();
1354         if (rc)
1355                 return rc;
1356
1357         rc = drop_capabilities(1);
1358         if (rc)
1359                 return EX_SYSERR;
1360
1361         /* setlocale(LC_ALL, "");
1362            bindtextdomain(PACKAGE, LOCALEDIR);
1363            textdomain(PACKAGE); */
1364
1365         if (!argc || !argv) {
1366                 rc = mount_cifs_usage(stderr);
1367                 goto mount_exit;
1368         }
1369
1370         thisprogram = argv[0];
1371         if (thisprogram == NULL)
1372                 thisprogram = "mount.cifs";
1373
1374         /* allocate parsed_info as shared anonymous memory range */
1375         parsed_info = mmap(0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
1376                            MAP_ANONYMOUS | MAP_SHARED, -1, 0);
1377         if (parsed_info == (struct parsed_mount_info *) -1) {
1378                 parsed_info = NULL;
1379                 fprintf(stderr, "Unable to allocate memory: %s\n",
1380                                 strerror(errno));
1381                 return EX_SYSERR;
1382         }
1383
1384         parsed_info->flags = MS_MANDLOCK;
1385
1386         /* add sharename in opts string as unc= parm */
1387         while ((c = getopt_long(argc, argv, "?fhno:rvVw",
1388                                 longopts, NULL)) != -1) {
1389                 switch (c) {
1390                 case '?':
1391                 case 'h':       /* help */
1392                         rc = mount_cifs_usage(stdout);
1393                         goto mount_exit;
1394                 case 'n':
1395                         ++parsed_info->nomtab;
1396                         break;
1397                 case 'o':
1398                         orgoptions = strndup(optarg, MAX_OPTIONS_LEN);
1399                         if (!orgoptions) {
1400                                 rc = EX_SYSERR;
1401                                 goto mount_exit;
1402                         }
1403                         break;
1404                 case 'r':       /* mount readonly */
1405                         parsed_info->flags |= MS_RDONLY;
1406                         break;
1407                 case 'v':
1408                         ++parsed_info->verboseflag;
1409                         break;
1410                 case 'V':
1411                         print_cifs_mount_version();
1412                         exit(0);
1413                 case 'w':
1414                         parsed_info->flags &= ~MS_RDONLY;
1415                         break;
1416                 case 'f':
1417                         ++parsed_info->fakemnt;
1418                         break;
1419                 default:
1420                         fprintf(stderr, "unknown command-line option: %c\n", c);
1421                         rc = mount_cifs_usage(stderr);
1422                         goto mount_exit;
1423                 }
1424         }
1425
1426         if (argc < 3 || argv[optind] == NULL || argv[optind + 1] == NULL) {
1427                 rc = mount_cifs_usage(stderr);
1428                 goto mount_exit;
1429         }
1430
1431         orig_dev = argv[optind];
1432         mountpoint = argv[optind + 1];
1433
1434         /* chdir into mountpoint as soon as possible */
1435         rc = chdir(mountpoint);
1436         if (rc) {
1437                 fprintf(stderr, "Couldn't chdir to %s: %s\n", mountpoint,
1438                         strerror(errno));
1439                 rc = EX_USAGE;
1440                 goto mount_exit;
1441         }
1442
1443         mountpoint = realpath(".", NULL);
1444         if (!mountpoint) {
1445                 fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
1446                         mountpoint, strerror(errno));
1447                 rc = EX_SYSERR;
1448                 goto mount_exit;
1449         }
1450
1451         /*
1452          * mount.cifs does privilege separation. Most of the code to handle
1453          * assembling the mount info is done in a child process that drops
1454          * privileges. The info is assembled in parsed_info which is a
1455          * shared, mmaped memory segment. The parent waits for the child to
1456          * exit and checks the return code. If it's anything but "0", then
1457          * the process exits without attempting anything further.
1458          */
1459         pid = fork();
1460         if (pid == -1) {
1461                 fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
1462                 rc = EX_SYSERR;
1463                 goto mount_exit;
1464         } else if (!pid) {
1465                 /* child */
1466                 rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
1467                                         orig_dev, orgoptions);
1468                 return rc;
1469         } else {
1470                 /* parent */
1471                 pid = wait(&rc);
1472                 if (!WIFEXITED(rc)) {
1473                         fprintf(stderr, "Child process terminated abnormally.\n");
1474                         rc = EX_SYSERR;
1475                         goto mount_exit;
1476                 }
1477                 rc = WEXITSTATUS(rc);
1478                 if (rc)
1479                         goto mount_exit;
1480         }
1481
1482         options = calloc(options_size, 1);
1483         if (!options) {
1484                 fprintf(stderr, "Unable to allocate memory.\n");
1485                 rc = EX_SYSERR;
1486                 goto mount_exit;
1487         }
1488
1489         dev_len = strnlen(parsed_info->host, sizeof(parsed_info->host)) +
1490             strnlen(parsed_info->share, sizeof(parsed_info->share)) +
1491             strnlen(parsed_info->prefix, sizeof(parsed_info->prefix)) +
1492             2 + 1 + 1 + 1;
1493         dev_name = calloc(dev_len, 1);
1494         if (!dev_name) {
1495                 rc = EX_SYSERR;
1496                 goto mount_exit;
1497         }
1498
1499         /* rebuild device name with forward slashes */
1500         strlcpy(dev_name, "//", dev_len);
1501         strlcat(dev_name, parsed_info->host, dev_len);
1502         strlcat(dev_name, "/", dev_len);
1503         strlcat(dev_name, parsed_info->share, dev_len);
1504         strlcat(dev_name, parsed_info->prefix, dev_len);
1505
1506         currentaddress = parsed_info->addrlist;
1507         nextaddress = strchr(currentaddress, ',');
1508         if (nextaddress)
1509                 *nextaddress++ = '\0';
1510
1511 mount_retry:
1512         if (!currentaddress) {
1513                 fprintf(stderr, "Unable to find suitable address.\n");
1514                 rc = EX_SYSERR;
1515                 goto mount_exit;
1516         }
1517         strlcpy(options, "ip=", options_size);
1518         strlcat(options, currentaddress, options_size);
1519
1520         strlcat(options, ",unc=\\\\", options_size);
1521         strlcat(options, parsed_info->host, options_size);
1522         strlcat(options, "\\", options_size);
1523         strlcat(options, parsed_info->share, options_size);
1524
1525         if (*parsed_info->options) {
1526                 strlcat(options, ",", options_size);
1527                 strlcat(options, parsed_info->options, options_size);
1528         }
1529
1530         if (*parsed_info->prefix) {
1531                 strlcat(options, ",prefixpath=", options_size);
1532                 strlcat(options, parsed_info->prefix, options_size);
1533         }
1534
1535         if (parsed_info->verboseflag)
1536                 fprintf(stderr, "mount.cifs kernel mount options: %s\n",
1537                         options);
1538
1539         if (parsed_info->got_password) {
1540                 /*
1541                  * Commas have to be doubled, or else they will
1542                  * look like the parameter separator
1543                  */
1544                 strlcat(options, ",pass=", options_size);
1545                 strlcat(options, parsed_info->password, options_size);
1546                 if (parsed_info->verboseflag)
1547                         fprintf(stderr, ",pass=********");
1548         }
1549
1550         if (parsed_info->verboseflag)
1551                 fprintf(stderr, "\n");
1552
1553         rc = check_mtab(thisprogram, dev_name, mountpoint);
1554         if (rc)
1555                 goto mount_exit;
1556
1557         if (!parsed_info->fakemnt
1558             && mount(dev_name, ".", cifs_fstype, parsed_info->flags, options)) {
1559                 switch (errno) {
1560                 case ECONNREFUSED:
1561                 case EHOSTUNREACH:
1562                         currentaddress = nextaddress;
1563                         nextaddress = strchr(currentaddress, ',');
1564                         if (nextaddress)
1565                                 *nextaddress++ = '\0';
1566                         goto mount_retry;
1567                 case ENODEV:
1568                         fprintf(stderr,
1569                                 "mount error: cifs filesystem not supported by the system\n");
1570                         break;
1571                 case ENXIO:
1572                         if (!already_uppercased &&
1573                             uppercase_string(parsed_info->host) &&
1574                             uppercase_string(parsed_info->share) &&
1575                             uppercase_string(parsed_info->prefix)) {
1576                                 fprintf(stderr,
1577                                         "Retrying with upper case share name\n");
1578                                 already_uppercased = 1;
1579                                 goto mount_retry;
1580                         }
1581                 }
1582                 fprintf(stderr, "mount error(%d): %s\n", errno,
1583                         strerror(errno));
1584                 fprintf(stderr,
1585                         "Refer to the mount.cifs(8) manual page (e.g. man "
1586                         "mount.cifs)\n");
1587                 rc = EX_FAIL;
1588                 goto mount_exit;
1589         }
1590
1591         if (!parsed_info->nomtab)
1592                 rc = add_mtab(dev_name, mountpoint, parsed_info->flags);
1593
1594 mount_exit:
1595         if (parsed_info) {
1596                 memset(parsed_info->password, 0, sizeof(parsed_info->password));
1597                 munmap(parsed_info, sizeof(*parsed_info));
1598         }
1599         SAFE_FREE(dev_name);
1600         SAFE_FREE(options);
1601         SAFE_FREE(orgoptions);
1602         return rc;
1603 }