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