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