1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 *
21 *
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 *
25 * Copyright 2014 Andrew Stormont
26 */
27
28 /*
29 * Vold compatibility for rmvolmgr: emulate old commands as well as
30 * action_filemgr.so to notify legacy apps via /tmp/.removable pipes.
31 * A lot of this code is copied verbatim from vold sources.
32 *
33 * Here's the original description of action_filemgr.so:
34 *
35 * action_filemgr.so - filemgr interface routines for rmmount
36 *
37 * This shared object allows rmmount to communicate with filemgr.
38 * This is done by communicating over a named pipe that filemgr
39 * creates in directory NOTIFY_DIR. The name of the pipe must
40 * begin with NOTIFY_NAME. This source file contains #define
41 * compiler directives set the values of NOTIFY_DIR and NOTIFY_NAME.
42 *
43 * After a partition on a medium has been mounted as a result of
44 * either insertion or remounting of the medium, the action()
45 * method creates a file named with the symbolic name of the
46 * device in which the medium is inserted and the partition name
47 * (e.g. "jaz0-s2") in NOTIFY_DIR. The file consists of one text
48 * line containing a string naming the mount point of the partition,
49 * a string giving the raw device path to the partition, and a
50 * string naming the file system type on the partition. The action()
51 * method then sends a single character ('i' for insertion, 'r' for
52 * remounting) through the named pipe NOTIFY_NAME to tell filemgr to
53 * look for new files in NOTIFY_DIR.
54 *
55 * If a medium containing no mountable partitions is inserted
56 * or remounted in a device, the action() method creates a file
57 * named with the symbolic name of the device in NOTIFY_DIR.
58 * The file consists of one text line containing a string
59 * giving the symbolic name of the device and a string naming
60 * the reason that the medium couldn't be mounted. The action
61 * method then sends either an 'i' or an 'r' through the named
62 * pipe to tell filemgr to look for new files in NOTIFY_DIR.
63 *
64 * When a medium is ejected or unmounted, the action() method
65 * removes the files that were created in NOTIFY_DIR when the medium
66 * was inserted or remounted and sends a single character ('e' for
67 * ejection, 'u' for unmounting) through the named pipe.
68 *
69 * The following environment variables must be set before calling action():
70 *
71 * VOLUME_ACTION action that occurred (e.g. "insert", "eject")
72 * VOLUME_SYMDEV symbolic name (e.g. "cdrom0", "floppy1")
73 * VOLUME_NAME volume name (e.g. "unnamed_cdrom", "s2")
74 */
75
76
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <unistd.h>
80 #include <fcntl.h>
81 #include <string.h>
82 #include <strings.h>
83 #include <dirent.h>
84 #include <signal.h>
85 #include <errno.h>
86 #include <libintl.h>
87 #include <zone.h>
88 #include <pwd.h>
89 #include <sys/types.h>
90 #include <sys/stat.h>
91 #include <sys/dkio.h>
92 #include <sys/cdio.h>
93 #include <sys/vtoc.h>
94 #include <sys/param.h>
95 #include <sys/wait.h>
96 #include <libcontract.h>
97 #include <sys/contract/process.h>
98 #include <sys/ctfs.h>
99 #include <tsol/label.h>
100
101 #include "vold.h"
102 #include "rmm_common.h"
103
104 int rmm_debug = 0;
105 boolean_t rmm_vold_actions_enabled = B_FALSE;
106 boolean_t rmm_vold_mountpoints_enabled = B_FALSE;
107
108 static char *prog_name = NULL;
109 static pid_t prog_pid = 0;
110 static int system_labeled = 0;
111 static uid_t mnt_uid = (uid_t)-1;
112 static gid_t mnt_gid = (gid_t)-1;
113 static zoneid_t mnt_zoneid = -1;
114 static char mnt_zoneroot[MAXPATHLEN];
115 static char mnt_userdir[MAXPATHLEN];
116
117 /*
118 * Private attribute types and attributes.
119 */
120 static const char notify_characters[] = {
121 'e',
122 'i',
123 'r',
124 'u'
125 };
126
127 static const char *result_strings[] = {
128 "FALSE",
129 "TRUE"
130 };
131
132 #define NOTIFY_DIR "/tmp/.removable" /* dir where filemgr looks */
133 #define NOTIFY_NAME "notify" /* named pipe to talk over */
134
135 static void volcheck_usage();
136 static int vold_action(struct action_arg *aap);
137 static void vold_update_mountpoints(struct action_arg *aap);
138 static char *not_mountable(struct action_arg *aa);
139 static int create_one_notify_file(char *fstype,
140 char *mount_point,
141 char *notify_file,
142 char *raw_partitionp,
143 char *reason,
144 char *symdev);
145 static int create_notify_files(struct action_arg **aa);
146 static boolean_t notify_clients(action_t action, int do_notify);
147 static void popdir(int fd);
148 static int pushdir(const char *dir);
149 static boolean_t remove_notify_files(struct action_arg **aa);
150
151 /*
152 * should be called once from main()
153 */
154 /* ARGSUSED */
155 void
156 vold_init(int argc, char **argv)
157 {
158 system_labeled = is_system_labeled();
159 }
160
161 /*
162 * Old version of rmmount(1M)
163 */
164 /* ARGSUSED */
165 int
166 vold_rmmount(int argc, char **argv)
167 {
168 char *volume_action;
169 char *volume_mediatype;
170 char *volume_mount_mode;
171 char *volume_name;
172 char *volume_path;
173 char *volume_pcfs_id;
174 char *volume_symdev;
175 char *volume_zonename;
176 char *volume_user;
177 action_t action;
178 char mountpoint[MAXPATHLEN];
179 char *zonemountpoint;
180 char *arg_mountpoint = NULL;
181 LibHalContext *hal_ctx;
182 DBusError error;
183 rmm_error_t rmm_error;
184 int ret;
185
186 prog_name = argv[0];
187 prog_pid = getpid();
188
189 mnt_zoneroot[0] = '\0';
190 mnt_userdir[0] = '\0';
191
192 volume_action = getenv("VOLUME_ACTION");
193 volume_mediatype = getenv("VOLUME_MEDIATYPE");
194 volume_mount_mode = getenv("VOLUME_MOUNT_MODE");
195 volume_name = getenv("VOLUME_NAME");
196 volume_path = getenv("VOLUME_PATH");
197 volume_pcfs_id = getenv("VOLUME_PCFS_ID");
198 volume_symdev = getenv("VOLUME_SYMDEV");
199
200 if (system_labeled) {
201 volume_zonename = getenv("VOLUME_ZONE_NAME");
202 volume_user = getenv("VOLUME_USER");
203 }
204 if (volume_action == NULL) {
205 dprintf("%s(%ld): VOLUME_ACTION was null!!\n",
206 prog_name, prog_pid);
207 return (-1);
208 }
209 if (volume_mediatype == NULL) {
210 dprintf("%s(%ld): VOLUME_MEDIATYPE was null!!\n",
211 prog_name, prog_pid);
212 return (-1);
213 }
214 if (volume_mount_mode == NULL) {
215 volume_mount_mode = "rw";
216 }
217 if (volume_name == NULL) {
218 dprintf("%s(%ld): VOLUME_NAME was null!!\n",
219 prog_name, prog_pid);
220 return (-1);
221 }
222 if (volume_path == NULL) {
223 dprintf("%s(%ld): VOLUME_PATH was null!!\n",
224 prog_name, prog_pid);
225 return (-1);
226 }
227 if (volume_pcfs_id == NULL) {
228 volume_pcfs_id = "";
229 }
230 if (volume_symdev == NULL) {
231 dprintf("%s(%ld): VOLUME_SYMDEV was null!!\n",
232 prog_name, prog_pid);
233 return (-1);
234 }
235
236 if (system_labeled) {
237 if (volume_zonename != NULL &&
238 strcmp(volume_zonename, GLOBAL_ZONENAME) != 0) {
239 if ((mnt_zoneid =
240 getzoneidbyname(volume_zonename)) != -1) {
241 if (zone_getattr(mnt_zoneid, ZONE_ATTR_ROOT,
242 mnt_zoneroot, MAXPATHLEN) == -1) {
243 dprintf("%s(%ld): NO ZONEPATH!!\n",
244 prog_name, prog_pid);
245 return (-1);
246 }
247 }
248 } else {
249 mnt_zoneid = GLOBAL_ZONEID;
250 mnt_zoneroot[0] = '\0';
251 }
252 if (volume_user != NULL) {
253 struct passwd *pw;
254
255 if ((pw = getpwnam(volume_user)) == NULL) {
256 dprintf("%s(%ld) %s\n", prog_name, prog_pid,
257 ": VOLUME_USER was not a valid user!");
258 return (-1);
259 }
260 mnt_uid = pw->pw_uid;
261 mnt_gid = pw->pw_gid;
262
263 if (snprintf(mnt_userdir, sizeof (mnt_userdir),
264 "/%s-%s", volume_user, volume_symdev) >=
265 sizeof (mnt_userdir))
266 return (-1);
267 } else {
268 mnt_uid = 0;
269 mnt_userdir[0] = '\0';
270 }
271
272 rmm_vold_mountpoints_enabled = B_FALSE;
273 rmm_vold_actions_enabled = B_TRUE;
274 } else {
275 rmm_vold_mountpoints_enabled = B_TRUE;
276 rmm_vold_actions_enabled = B_TRUE;
277 }
278
279 if ((hal_ctx = rmm_hal_init(0, 0, 0, 0, &error, &rmm_error)) == NULL) {
280 rmm_dbus_error_free(&error);
281
282 /* if HAL's not running, must be root */
283 if (geteuid() != 0) {
284 (void) fprintf(stderr,
285 gettext("%s(%ld) error: must be root to execute\n"),
286 prog_name, prog_pid);
287 return (-1);
288 }
289 }
290
291 if (strcmp(volume_action, "eject") == 0) {
292 action = EJECT;
293 } else if (strcmp(volume_action, "insert") == 0) {
294 action = INSERT;
295
296 if (system_labeled) {
297 /*
298 * create mount point
299 */
300 if (strlen(mnt_userdir) > 0) {
301 if (snprintf(mountpoint, MAXPATHLEN,
302 "%s/%s%s", mnt_zoneroot, volume_mediatype,
303 mnt_userdir) > MAXPATHLEN) {
304 return (-1);
305
306 }
307 (void) makepath(mountpoint, 0700);
308 (void) chown(mountpoint, mnt_uid, mnt_gid);
309 /*
310 * set the top level directory bits to 0755
311 * so user can access it.
312 */
313 if (snprintf(mountpoint, MAXPATHLEN,
314 "%s/%s", mnt_zoneroot,
315 volume_mediatype) <= MAXPATHLEN) {
316 (void) chmod(mountpoint, 0755);
317 }
318 }
319 if (snprintf(mountpoint, MAXPATHLEN,
320 "%s/%s%s/%s", mnt_zoneroot, volume_mediatype,
321 mnt_userdir, volume_name) > MAXPATHLEN) {
322 (void) fprintf(stderr,
323 gettext("%s(%ld) error: path too long\n"),
324 prog_name, prog_pid);
325 return (-1);
326 }
327
328 /* make our mountpoint */
329 (void) makepath(mountpoint, 0755);
330
331 arg_mountpoint = mountpoint;
332 }
333 } else if (strcmp(volume_action, "remount") == 0) {
334 action = REMOUNT;
335 } else if (strcmp(volume_action, "unmount") == 0) {
336 action = UNMOUNT;
337 }
338
339 ret = rmm_action(hal_ctx, volume_symdev, action, 0, 0, 0,
340 arg_mountpoint) ? 0 : 1;
341
342 if (hal_ctx != NULL) {
343 rmm_hal_fini(hal_ctx);
344 }
345
346 return (ret);
347 }
348
349
350 /*
351 * this should be called after rmm_hal_{mount,unmount,eject}
352 */
353 int
354 vold_postprocess(LibHalContext *hal_ctx, const char *udi,
355 struct action_arg *aap)
356 {
357 int ret = 0;
358
359 /* valid mountpoint required */
360 if ((aap->aa_action == INSERT) || (aap->aa_action == REMOUNT)) {
361 rmm_volume_aa_update_mountpoint(hal_ctx, udi, aap);
362 if ((aap->aa_mountpoint == NULL) ||
363 (strlen(aap->aa_mountpoint) == 0)) {
364 return (1);
365 }
366 }
367
368 if (rmm_vold_mountpoints_enabled) {
369 vold_update_mountpoints(aap);
370 }
371 if (rmm_vold_actions_enabled) {
372 ret = vold_action(aap);
373 }
374
375 return (ret);
376 }
377
378 /*
379 * update legacy symlinks
380 *
381 * For cdrom:
382 *
383 * /cdrom/<name> -> original mountpoint
384 * /cdrom/cdrom0 -> ./<name>
385 * /cdrom/cdrom -> cdrom0 (only for cdrom0)
386 *
387 * If it's a slice or partition, /cdrom/<name> becomes a directory:
388 *
389 * /cdrom/<name>/s0
390 *
391 * Same for rmdisk and floppy.
392 *
393 * On labeled system (Trusted Solaris), links are in a user directory.
394 */
395 static void
396 vold_update_mountpoints(struct action_arg *aap)
397 {
398 boolean_t is_partition;
399 char part_dir[2 * MAXNAMELEN];
400 char symname_mp[2 * MAXNAMELEN];
401 char symcontents_mp[MAXNAMELEN];
402 char symname[2 * MAXNAMELEN];
403 char symcontents[MAXNAMELEN];
404
405 is_partition = (aap->aa_partname != NULL);
406
407 if (!system_labeled) {
408 if (!is_partition) {
409 /* /cdrom/<name> -> original mountpoint */
410 (void) snprintf(symcontents_mp, sizeof (symcontents_mp),
411 "%s", aap->aa_mountpoint);
412 (void) snprintf(symname_mp, sizeof (symname_mp),
413 "/%s/%s", aap->aa_media, aap->aa_name);
414 } else {
415 /* /cdrom/<name>/slice -> original mountpoint */
416 (void) snprintf(part_dir, sizeof (part_dir),
417 "/%s/%s", aap->aa_media, aap->aa_name);
418 (void) snprintf(symcontents_mp, sizeof (symcontents_mp),
419 "%s", aap->aa_mountpoint);
420 (void) snprintf(symname_mp, sizeof (symname_mp),
421 "/%s/%s/%s", aap->aa_media, aap->aa_name,
422 aap->aa_partname);
423
424 }
425 /* /cdrom/cdrom0 -> ./<name> */
426 (void) snprintf(symcontents, sizeof (symcontents),
427 "./%s", aap->aa_name);
428 (void) snprintf(symname, sizeof (symname),
429 "/%s/%s", aap->aa_media, aap->aa_symdev);
430 } else {
431 if (!is_partition) {
432 /* /cdrom/<user>/<name> -> original mountpoint */
433 (void) snprintf(symcontents_mp, sizeof (symcontents_mp),
434 "%s", aap->aa_mountpoint);
435 (void) snprintf(symname_mp, sizeof (symname_mp),
436 "%s/%s/%s", mnt_zoneroot, aap->aa_media,
437 aap->aa_symdev);
438 } else {
439 /* /cdrom/<user>/<name>/slice -> original mountpoint */
440 (void) snprintf(symcontents_mp, sizeof (symcontents_mp),
441 "%s", aap->aa_mountpoint);
442 (void) snprintf(symname_mp, sizeof (symname_mp),
443 "%s/%s/%s", mnt_zoneroot, aap->aa_media,
444 aap->aa_symdev, aap->aa_partname);
445 }
446
447 /* /cdrom/<user>/cdrom0 -> ./<user>/<name> */
448 (void) snprintf(symcontents, sizeof (symcontents),
449 ".%s/%s", mnt_userdir, aap->aa_name);
450 (void) snprintf(symname, sizeof (symname), "%s/%s/%s",
451 mnt_zoneroot, aap->aa_media, aap->aa_symdev);
452 }
453
454 (void) unlink(symname);
455 (void) unlink(symname_mp);
456 if (is_partition) {
457 (void) rmdir(part_dir);
458 }
459
460 if ((aap->aa_action == INSERT) || (aap->aa_action == REMOUNT)) {
461 (void) mkdir(aap->aa_media, 0755);
462 if (is_partition) {
463 (void) mkdir(part_dir, 0755);
464 }
465 (void) symlink(symcontents_mp, symname_mp);
466 (void) symlink(symcontents, symname);
467 }
468 }
469
470
471 static int
472 vold_action(struct action_arg *aap)
473 {
474 action_t action;
475 int result;
476 int do_notify = FALSE;
477 action_t notify_act = EJECT;
478 struct action_arg *aa[2];
479 struct action_arg a1;
480
481 dprintf("%s[%d]: entering action()\n", __FILE__, __LINE__);
482
483 /*
484 * on Trusted Extensions, actions are executed in the user's zone
485 */
486 if (mnt_zoneid > GLOBAL_ZONEID) {
487 pid_t pid;
488 int status;
489 int ifx;
490 int tmpl_fd;
491 int err = 0;
492
493 tmpl_fd = open64(CTFS_ROOT "/process/template",
494 O_RDWR);
495 if (tmpl_fd == -1)
496 return (1);
497
498 /*
499 * Deliver no events, don't inherit,
500 * and allow it to be orphaned.
501 */
502 err |= ct_tmpl_set_critical(tmpl_fd, 0);
503 err |= ct_tmpl_set_informative(tmpl_fd, 0);
504 err |= ct_pr_tmpl_set_fatal(tmpl_fd,
505 CT_PR_EV_HWERR);
506 err |= ct_pr_tmpl_set_param(tmpl_fd,
507 CT_PR_PGRPONLY |
508 CT_PR_REGENT);
509 if (err || ct_tmpl_activate(tmpl_fd)) {
510 (void) close(tmpl_fd);
511 return (1);
512 }
513 switch (pid = fork1()) {
514 case 0:
515 (void) ct_tmpl_clear(tmpl_fd);
516 for (ifx = 0; ifx < _NFILE; ifx++)
517 (void) close(ifx);
518
519 if (zone_enter(mnt_zoneid) == -1)
520 _exit(0);
521
522 /* entered zone, proceed to action */
523 break;
524 case -1:
525 dprintf("fork1 failed \n ");
526 return (1);
527 default :
528 (void) ct_tmpl_clear(tmpl_fd);
529 (void) close(tmpl_fd);
530 if (waitpid(pid, &status, 0) < 0) {
531 dprintf("%s(%ld): waitpid() "
532 "failed (errno %d) \n",
533 prog_name, prog_pid, errno);
534 return (1);
535 }
536 }
537 }
538
539 /* only support one action at a time XXX */
540 a1.aa_path = NULL;
541 aa[0] = aap;
542 aa[1] = &a1;
543
544 action = aa[0]->aa_action;
545
546 if (action == CLEAR_MOUNTS) {
547 /*
548 * Remove the notifications files, but don't
549 * notify the client. The "clear_mounts" action
550 * simply clears all existing mounts of a medium's
551 * partitions after a medium has been repartitioned.
552 * Then vold builds a new file system that reflects
553 * the medium's new partition structure and mounts
554 * the new partitions by calling rmmount, and therefore
555 * action(), with the VOLUME_ACTION environment variable
556 * set to "remount".
557 */
558 result = remove_notify_files(aa);
559 result = TRUE;
560 } else if (action == EJECT) {
561 result = remove_notify_files(aa);
562 if (result == TRUE) {
563 do_notify = TRUE;
564 notify_act = EJECT;
565 }
566 } else if (action = INSERT) {
567 result = create_notify_files(aa);
568 if (result == TRUE) {
569 do_notify = TRUE;
570 notify_act = INSERT;
571 }
572 } else if (action == REMOUNT) {
573 result = create_notify_files(aa);
574 if (result == TRUE) {
575 do_notify = TRUE;
576 notify_act = REMOUNT;
577 }
578 } else if (action == UNMOUNT) {
579 result = remove_notify_files(aa);
580 if (result == TRUE) {
581 do_notify = TRUE;
582 notify_act = UNMOUNT;
583 }
584 } else {
585 dprintf("%s[%d]: action(): invalid action: %s\n",
586 __FILE__, __LINE__, action);
587 result = FALSE;
588 }
589
590 if (result == TRUE) {
591 result = notify_clients(notify_act, do_notify);
592 }
593
594 dprintf("%s[%d]: leaving action(), result = %s\n",
595 __FILE__, __LINE__, result_strings[result]);
596
597 if (mnt_zoneid > GLOBAL_ZONEID) {
598 /* exit forked local zone process */
599 _exit(0);
600 }
601
602 if (result == TRUE) {
603 /*
604 * File Manager is running. return 0.
605 * see man page rmmount.conf(4).
606 */
607 return (0);
608 } else {
609 return (1);
610 }
611 }
612
613
614 /*
615 * Returns NULL if a medium or partition is mountable
616 * and a string stating the reason the medium or partition
617 * can't be mounted if the medium or partition isn't mountable.
618 *
619 * If the volume_name of the medium or partition is one of the
620 * following, the medium or partition isn't mountable.
621 *
622 * unlabeled_<media_type>
623 * unknown_format
624 * password_protected
625 */
626 /* ARGSUSED */
627 static char *
628 not_mountable(struct action_arg *aa)
629 {
630 return (NULL);
631 }
632
633 static int
634 create_notify_files(struct action_arg **aa)
635 {
636 int ai;
637 char *fstype;
638 char *mount_point;
639 char notify_file[64];
640 char *raw_partitionp;
641 char *reason; /* Why the medium wasn't mounted */
642 int result;
643 char *symdev;
644
645 dprintf("%s[%d]: entering create_notify_files()\n", __FILE__, __LINE__);
646
647 ai = 0;
648 result = FALSE;
649 symdev = aa[ai]->aa_symdev;
650 while ((aa[ai] != NULL) && (aa[ai]->aa_path != NULL)) {
651 if (aa[ai]->aa_mountpoint != NULL) {
652 if (aa[ai]->aa_type) {
653 fstype = aa[ai]->aa_type;
654 } else {
655 fstype = "unknown";
656 }
657 mount_point = aa[ai]->aa_mountpoint;
658 if (aa[ai]->aa_partname != NULL) {
659 /*
660 * Is aa_partname ever NULL?
661 * When time permits, check.
662 * If it is, the action taken
663 * in the else clause could produce
664 * file name conflicts.
665 */
666 sprintf(notify_file, "%s-%s", symdev,
667 aa[ai]->aa_partname);
668 } else {
669 sprintf(notify_file, "%s-0", symdev);
670 }
671 reason = NULL;
672 } else {
673 /*
674 * The partition isn't mounted.
675 */
676 fstype = "none";
677 mount_point = "none";
678 reason = not_mountable(aa[ai]);
679 if (reason != NULL) {
680 sprintf(notify_file, "%s-0", symdev);
681 } else {
682 /*
683 * Either the partition is a backup slice, or
684 * rmmount tried to mount the partition, but
685 * idenf_fs couldn't identify the file system
686 * type; that can occur when rmmount is
687 * trying to mount all the slices in a Solaris
688 * VTOC, and one or more partitions don't have
689 * file systems in them.
690 */
691 if (aa[0]->aa_partname != NULL) {
692 /*
693 * Is aa_partname ever NULL?
694 * When time permits, check.
695 * If it is, the action taken
696 * in the else clause could produce
697 * file name conflicts.
698 */
699 sprintf(notify_file, "%s-%s", symdev,
700 aa[0]->aa_partname);
701 } else {
702 sprintf(notify_file, "%s-0", symdev);
703 }
704 if ((aa[0]->aa_type != NULL) &&
705 (strcmp(aa[0]->aa_type, "backup_slice")
706 == 0)) {
707 reason = "backup_slice";
708 } else {
709 reason = "unformatted_media";
710 }
711 /*
712 * "unformatted_media" should be
713 * changed to "unformmated_medium" for
714 * grammatical correctness, but
715 * "unformatted_media" is now specified
716 * in the interface to filemgr, so the
717 * change can't be made without the
718 * approval of the CDE group.
719 */
720 }
721 }
722 raw_partitionp = aa[0]->aa_rawpath;
723 result = create_one_notify_file(fstype,
724 mount_point,
725 notify_file,
726 raw_partitionp,
727 reason,
728 symdev);
729 ai++;
730 }
731 dprintf("%s[%d]: leaving create_notify_files(), result = %s\n",
732 __FILE__, __LINE__, result_strings[result]);
733 return (result);
734 }
735
736 static int
737 create_one_notify_file(char *fstype,
738 char *mount_point,
739 char *notify_file,
740 char *raw_partitionp,
741 char *reason,
742 char *symdev)
743 {
744 /*
745 * For a mounted partition, create a notification file
746 * indicating the mount point, the raw device pathname
747 * of the partition, and the partition's file system
748 * type. For an unmounted partition, create a
749 * notification file containing the reason that the
750 * partition wasn't mounted and the raw device pathname
751 * of the partition.
752 *
753 * Create the file as root in a world-writable
754 * directory that resides in a world-writable directory.
755 *
756 * Handle two possible race conditions that could
757 * allow security breaches.
758 */
759
760 int current_working_dir_fd;
761 int file_descriptor;
762 FILE *filep;
763 int result;
764
765 dprintf("%s[%d]:Entering create_one_notify_file()\n",
766 __FILE__, __LINE__);
767 dprintf("\tcreate_one_notify_file(): fstype = %s\n", fstype);
768 dprintf("\tcreate_one_notify_file(): mount_point = %s\n", mount_point);
769 dprintf("\tcreate_one_notify_file(): notify_file = %s\n", notify_file);
770 dprintf("\tcreate_one_notify_file(): raw_partitionp = %s\n",
771 raw_partitionp);
772 if (reason != NULL) {
773 dprintf("\tcreate_one_notify_file(): reason = %s\n", reason);
774 } else {
775 dprintf("\tcreate_one_notify_file(): reason = NULL\n");
776 }
777 dprintf("\tcreate_one_notify_file(): symdev = %s\n", symdev);
778
779 result = TRUE;
780 /*
781 * Handle Race Condition One:
782 *
783 * If NOTIFY_DIR exists, make sure it is not a symlink.
784 * if it is, remove it and try to create it. Check
785 * again to make sure NOTIFY_DIR isn't a symlink.
786 * If it is, remove it and return without creating
787 * a notification file. The condition can only occur if
788 * someone is trying to break into the system by running
789 * a program that repeatedly creates NOTIFY_DIR as a
790 * symlink. If NOTIFY_DIR exists and isn't a symlink,
791 * change the working directory to NOTIFY_DIR.
792 */
793 current_working_dir_fd = pushdir(NOTIFY_DIR);
794 if (current_working_dir_fd < 0) {
795 (void) makepath(NOTIFY_DIR, 0777);
796 current_working_dir_fd = pushdir(NOTIFY_DIR);
797 if (current_working_dir_fd < 0) {
798 result = FALSE;
799 }
800 }
801 /*
802 * Handle Race Condition Two:
803 *
804 * Create the notification file in NOTIFY_DIR.
805 * Remove any files with the same name that may already be
806 * there, using remove(), as it safely removes directories.
807 * Then open the file O_CREAT|O_EXCL, which doesn't follow
808 * symlinks and requires that the file not exist already,
809 * so the new file actually resides in the current working
810 * directory. Create the file with access mode 644, which
811 * renders it unusable by anyone trying to break into the
812 * system.
813 */
814 if (result == TRUE) {
815 /*
816 * The current working directory is now NOTIFY_DIR.
817 */
818 (void) remove(notify_file);
819 file_descriptor =
820 open(notify_file, O_CREAT|O_EXCL|O_WRONLY, 0644);
821 if (file_descriptor < 0) {
822 dprintf("%s[%d]: can't create %s/%s; %m\n",
823 __FILE__, __LINE__, NOTIFY_DIR, notify_file);
824 result = FALSE;
825 } else {
826 filep = fdopen(file_descriptor, "w");
827 if (filep != NULL) {
828 if (reason == NULL) {
829 (void) fprintf(filep, "%s %s %s",
830 mount_point,
831 raw_partitionp,
832 fstype);
833 (void) fclose(filep);
834 dprintf("%s[%d]: Just wrote %s %s %s to %s\n",
835 __FILE__,
836 __LINE__,
837 mount_point,
838 raw_partitionp,
839 fstype,
840 notify_file);
841 } else {
842 (void) fprintf(filep, "%s %s",
843 reason, raw_partitionp);
844 (void) fclose(filep);
845 dprintf("%s[%d]: Just wrote %s %s to %s\n",
846 __FILE__,
847 __LINE__,
848 reason,
849 raw_partitionp,
850 notify_file);
851 }
852 } else {
853 dprintf("%s[%d]: can't write %s/%s; %m\n",
854 __FILE__, __LINE__,
855 NOTIFY_DIR, notify_file);
856 (void) close(file_descriptor);
857 result = FALSE;
858 }
859 }
860 popdir(current_working_dir_fd);
861 }
862 dprintf("%s[%d]: leaving create_one_notify_file, result = %s\n",
863 __FILE__, __LINE__, result_strings[result]);
864 return (result);
865 }
866
867 static boolean_t
868 notify_clients(action_t action, int do_notify)
869 {
870 /*
871 * Notify interested applications of changes in the state
872 * of removable media. Interested applications are those
873 * that create a named pipe in NOTIFY_DIR with a name that
874 * begins with "notify". Open the pipe and write a
875 * character through it that indicates the type of state
876 * change = 'e' for ejections, 'i' for insertions, 'r'
877 * for remounts of the file systems on repartitioned media,
878 * and 'u' for unmounts of file systems.
879 */
880
881 int current_working_dir_fd;
882 DIR *dirp;
883 struct dirent *dir_entryp;
884 size_t len;
885 int fd;
886 char namebuf[MAXPATHLEN];
887 char notify_character;
888 void (*old_signal_handler)();
889 int result;
890 struct stat sb;
891
892 dprintf("%s[%d]: entering notify_clients()\n", __FILE__, __LINE__);
893
894 result = TRUE;
895 /*
896 * Use relative pathnames after changing the
897 * working directory to the notification directory.
898 * Check to make sure that each "notify" file is a
899 * named pipe to make sure that it hasn't changed
900 * its file type, which could mean that someone is
901 * trying to use "notify" files to break into the
902 * system.
903 */
904 if ((current_working_dir_fd = pushdir(NOTIFY_DIR)) < 0) {
905 result = FALSE;
906 }
907 if (result == TRUE) {
908 dirp = opendir(".");
909 if (dirp == NULL) {
910 dprintf("%s[%d]:opendir failed on '.'; %m\n",
911 __FILE__, __LINE__);
912 popdir(current_working_dir_fd);
913 result = FALSE;
914 }
915 }
916 if (result == TRUE) {
917 /*
918 * Read through the directory and write a notify
919 * character to all files whose names start with "notify".
920 */
921 result = FALSE;
922 old_signal_handler = signal(SIGPIPE, SIG_IGN);
923 len = strlen(NOTIFY_NAME);
924 while (dir_entryp = readdir(dirp)) {
925 if (strncmp(dir_entryp->d_name, NOTIFY_NAME, len)
926 != 0) {
927 continue;
928 }
929 result = TRUE;
930 if (do_notify != TRUE) {
931 continue;
932 }
933 (void) sprintf(namebuf, "%s/%s",
934 NOTIFY_DIR, dir_entryp->d_name);
935 if ((fd = open(namebuf, O_WRONLY|O_NDELAY)) < 0) {
936 dprintf("%s[%d]: open failed for %s; %m\n",
937 __FILE__, __LINE__, namebuf);
938 continue;
939 }
940 /*
941 * Check to be sure that the entry is a named pipe.
942 * That closes a small security hole that could
943 * enable unauthorized access to the system root.
944 */
945 if ((fstat(fd, &sb) < 0) || (!S_ISFIFO(sb.st_mode))) {
946 dprintf("%s[%d]: %s isn't a named pipe\n",
947 __FILE__, __LINE__, namebuf);
948
949 (void) close(fd);
950 continue;
951 }
952 notify_character = notify_characters[action];
953 if (write(fd, ¬ify_character, 1) < 0) {
954 dprintf("%s[%d]: write failed for %s; %m\n",
955 __FILE__, __LINE__, namebuf);
956 (void) close(fd);
957 continue;
958 }
959 (void) close(fd);
960 }
961 (void) closedir(dirp);
962 (void) signal(SIGPIPE, old_signal_handler);
963 popdir(current_working_dir_fd);
964 }
965 dprintf("%s[%d]: leaving notify_clients(), result = %s\n",
966 __FILE__, __LINE__, result_strings[result]);
967 return (result);
968 }
969
970 static void
971 popdir(int fd)
972 {
973 /*
974 * Change the current working directory to the directory
975 * specified by fd and close the fd. Exit the program
976 * on failure.
977 */
978 if (fchdir(fd) < 0) {
979 dprintf("%s[%d]: popdir() failed\n", __FILE__, __LINE__);
980 exit(1);
981 }
982 (void) close(fd);
983 }
984
985 static int
986 pushdir(const char *dir)
987 {
988 /*
989 * Change the current working directory to dir and
990 * return a file descriptor for the old working
991 * directory.
992 *
993 * Exception handling:
994 *
995 * If dir doesn't exist, leave the current working
996 * directory the same and return -1.
997 *
998 * If dir isn't a directory, remove it, leave the
999 * current working directory the same, and return -1.
1000 *
1001 * If open() fails on the current working directory
1002 * or the chdir operation fails on dir, leave the
1003 * current working directory the same and return -1.
1004 */
1005
1006 int current_working_dir_fd;
1007 struct stat stat_buf;
1008
1009 if (lstat(dir, &stat_buf) < 0) {
1010 dprintf("%s[%d]: push_dir_and_check(): %s does not exist\n",
1011 __FILE__, __LINE__, dir);
1012 return (-1);
1013 }
1014
1015 if (!(S_ISDIR(stat_buf.st_mode))) {
1016 dprintf("%s[%d]: push_dir_and_check(): %s not a directory.\n",
1017 __FILE__, __LINE__, dir);
1018 (void) remove(dir);
1019 return (-1);
1020 }
1021 if ((current_working_dir_fd = open(".", O_RDONLY)) < 0) {
1022 dprintf("%s[%d]: push_dir_and_check(): can't open %s.\n",
1023 __FILE__, __LINE__, dir);
1024 return (-1);
1025 }
1026 if (chdir(dir) < 0) {
1027 (void) close(current_working_dir_fd);
1028 dprintf("%s[%d]: push_dir_and_check(): can't chdir() to %s.\n",
1029 __FILE__, __LINE__, dir);
1030 return (-1);
1031 }
1032 return (current_working_dir_fd);
1033 }
1034
1035 static boolean_t
1036 remove_notify_files(struct action_arg **aa)
1037 {
1038 int ai;
1039 int current_working_dir_fd;
1040 char notify_file[64];
1041 int result;
1042 char *symdev;
1043
1044 dprintf("%s[%d]: entering remove_notify_files()\n", __FILE__, __LINE__);
1045
1046 ai = 0;
1047 result = TRUE;
1048 symdev = aa[ai]->aa_symdev;
1049 while ((result == TRUE) &&
1050 (aa[ai] != NULL) &&
1051 (aa[ai]->aa_path != NULL)) {
1052
1053 if (not_mountable(aa[ai])) {
1054 sprintf(notify_file, "%s-0", symdev);
1055 } else if (aa[ai]->aa_partname != NULL) {
1056 /*
1057 * Is aa_partname ever NULL?
1058 * When time permits, check.
1059 * If it is, the action taken
1060 * in the else clause could produce
1061 * file name conflicts.
1062 */
1063 sprintf(notify_file, "%s-%s",
1064 symdev, aa[0]->aa_partname);
1065 } else {
1066 sprintf(notify_file, "%s-0", symdev);
1067 }
1068
1069 current_working_dir_fd = pushdir(NOTIFY_DIR);
1070 if (current_working_dir_fd < 0) {
1071 result = FALSE;
1072 }
1073 if ((result == TRUE) && (remove(notify_file) < 0)) {
1074 dprintf("%s[%d]: remove %s/%s; %m\n",
1075 __FILE__, __LINE__, NOTIFY_DIR, notify_file);
1076 result = FALSE;
1077 }
1078 if (current_working_dir_fd != -1) {
1079 popdir(current_working_dir_fd);
1080 }
1081 ai++;
1082 }
1083 dprintf("%s[%d]: leaving remove_notify_files(), result = %s\n",
1084 __FILE__, __LINE__, result_strings[result]);
1085
1086 return (result);
1087 }