hal/tools Makefile.am, 1.35, 1.36 hal-policy-is-privileged.c, 1.2, 1.3 hal-storage-mount.c, NONE, 1.1

David Zeuthen david at kemper.freedesktop.org
Mon Mar 6 14:34:32 PST 2006


Update of /cvs/hal/hal/tools
In directory kemper:/tmp/cvs-serv16017/tools

Modified Files:
	Makefile.am hal-policy-is-privileged.c 
Added Files:
	hal-storage-mount.c 
Log Message:
2006-03-06  David Zeuthen  <davidz at redhat.com>

        * tools/hal-storage-mount.c: New file

        * tools/Makefile.am: Add rules for hal-storage-mount

        * fdi/policy/10osvendor/20-storage-methods.fdi: Use new C program
        hal-storage-mount instead of hal-system-storage-mount (shell
        script). Don't require ignored firmware partitions to be internal,
        otherwise 'bootstrap' would show up when attaching my Powerbook G4
        in target disk mode. Allow a bunch of uid= options for other file
        systems than vfat as storage-[fixed|removable]-mount-change-uid
        policies will refuse to mount.



Index: Makefile.am
===================================================================
RCS file: /cvs/hal/hal/tools/Makefile.am,v
retrieving revision 1.35
retrieving revision 1.36
diff -u -d -r1.35 -r1.36
--- Makefile.am	26 Feb 2006 23:43:41 -0000	1.35
+++ Makefile.am	6 Mar 2006 22:34:29 -0000	1.36
@@ -43,7 +43,12 @@
 hal_device_SOURCES = hal-device.c
 hal_device_LDADD = @DBUS_LIBS@ $(top_builddir)/libhal/libhal.la
 
-libexec_PROGRAMS = hal-system-power-pmu
+libexec_PROGRAMS =               \
+	hal-storage-mount	 \
+	hal-system-power-pmu
+
+hal_storage_mount_SOURCES = hal-storage-mount.c
+hal_storage_mount_LDADD = @GLIB_LIBS@ @DBUS_LIBS@ $(top_builddir)/libhal/libhal.la $(top_builddir)/libhal-storage/libhal-storage.la $(top_builddir)/libhal-policy/libhal-policy.la
 
 hal_system_power_pmu_SOURCES = hal-system-power-pmu.c
 hal_system_power_pmu_LDADD = @DBUS_LIBS@ $(top_builddir)/libhal/libhal.la

Index: hal-policy-is-privileged.c
===================================================================
RCS file: /cvs/hal/hal/tools/hal-policy-is-privileged.c,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- hal-policy-is-privileged.c	26 Feb 2006 23:43:41 -0000	1.2
+++ hal-policy-is-privileged.c	6 Mar 2006 22:34:29 -0000	1.3
@@ -1,7 +1,7 @@
 /***************************************************************************
  * CVSID: $Id$
  *
- * hal_.c : Show devices managed by HAL
+ * hal-policy-is-privileged.c : Small command line wrapper for libhal-policy
  *
  * Copyright (C) 2006 David Zeuthen, <david at fubar.dk>
  *

--- NEW FILE: hal-storage-mount.c ---
/***************************************************************************
 * CVSID: $Id: hal-storage-mount.c,v 1.1 2006/03/06 22:34:29 david Exp $
 *
 * hal-storage-mount.c : Mount wrapper
 *
 * Copyright (C) 2006 David Zeuthen, <david at fubar.dk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **************************************************************************/


#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <mntent.h>

#include <libhal/libhal.h>
#include <libhal-storage/libhal-storage.h>
#include <libhal-policy/libhal-policy.h>

/*#define DEBUG*/
#define DEBUG

static void
usage (void)
{
	fprintf (stderr, "This script should only be started by hald.\n");
	exit (1);
}

static void
unknown_error (void)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.UnknownError\n");
	fprintf (stderr, "An unexpected error occured\n");
	exit (1);
}

static void
permission_denied_volume_ignore (const char *device)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.PermissionDenied\n");
	fprintf (stderr, "Device has %s volume.ignore set to TRUE. Refusing to mount.\n", device);
	exit (1);
}

static void
permission_denied_etc_fstab (const char *device)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.PermissionDenied\n");
	fprintf (stderr, "Device %s is listed in /etc/fstab. Refusing to mount.\n", device);
	exit (1);
}

static void
already_mounted (const char *device)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.AlreadyMounted\n");
	fprintf (stderr, "Device %s is already mounted.\n", device);
	exit (1);
}

static void
invalid_mount_option (const char *option, uid_t uid)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.InvalidMountOption\n");
	fprintf (stderr, "The option '%s' is not allowed for uid=%d\n", option, uid);
	exit (1);
}

static void
unknown_filesystem (const char *filesystem)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.UnknownFilesystemType\n");
	fprintf (stderr, "Unknown file system '%s'\n", filesystem);
	exit (1);
}

static void
invalid_mount_point (const char *mount_point)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.InvalidMountpoint\n");
	fprintf (stderr, "The mount point '%s' is invalid\n", mount_point);
	exit (1);
}

static void
mount_point_not_available (const char *mount_point)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.MountPointNotAvailable\n");
	fprintf (stderr, "The mount point '%s' is already occupied\n", mount_point);
	exit (1);
}


static void
refused_by_policy (const char *policy, uid_t uid)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.PermissionDeniedByPolicy\n");
	fprintf (stderr, "%s refused uid %d\n", policy, uid);
	exit (1);
}



/* borrowed from gtk/gtkfilesystemunix.c in GTK+ on 02/23/2006 */
static void
canonicalize_filename (gchar *filename)
{
	gchar *p, *q;
	gboolean last_was_slash = FALSE;
	
	p = filename;
	q = filename;
	
	while (*p)
	{
		if (*p == G_DIR_SEPARATOR)
		{
			if (!last_was_slash)
				*q++ = G_DIR_SEPARATOR;
			
			last_was_slash = TRUE;
		}
		else
		{
			if (last_was_slash && *p == '.')
			{
				if (*(p + 1) == G_DIR_SEPARATOR ||
				    *(p + 1) == '\0')
				{
					if (*(p + 1) == '\0')
						break;
					
					p += 1;
				}
				else if (*(p + 1) == '.' &&
					 (*(p + 2) == G_DIR_SEPARATOR ||
					  *(p + 2) == '\0'))
				{
					if (q > filename + 1)
					{
						q--;
						while (q > filename + 1 &&
						       *(q - 1) != G_DIR_SEPARATOR)
							q--;
					}
					
					if (*(p + 2) == '\0')
						break;
					
					p += 2;
				}
				else
				{
					*q++ = *p;
					last_was_slash = FALSE;
				}
			}
			else
			{
				*q++ = *p;
				last_was_slash = FALSE;
			}
		}
		
		p++;
	}
	
	if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
		q--;
	
	*q = '\0';
}

static char *
resolve_symlink (const char *file)
{
	GError *error;
	char *dir;
	char *link;
	char *f;
	char *f1;

	f = g_strdup (file);

	while (g_file_test (f, G_FILE_TEST_IS_SYMLINK)) {
		link = g_file_read_link (f, &error);
		if (link == NULL) {
			g_warning ("Cannot resolve symlink %s: %s", f, error->message);
			g_error_free (error);
			g_free (f);
			f = NULL;
			goto out;
		}
		
		dir = g_path_get_dirname (f);
		f1 = g_strdup_printf ("%s/%s", dir, link);
		g_free (dir);
		g_free (link);
		g_free (f);
		f = f1;
	}

out:
	if (f != NULL)
		canonicalize_filename (f);
	return f;
}

static void
bailout_if_in_fstab (const char *device)
{
	FILE *fstab;
	struct mntent *mnt;

	/* check if /etc/fstab mentions this device... (with symlinks etc) */
	fstab = fopen ("/etc/fstab", "r");
	if (fstab == NULL) {
		printf ("cannot open /etc/fstab\n");
		unknown_error ();		
	}
	while ((mnt = getmntent (fstab)) != NULL) {
		char *resolved;

		resolved = resolve_symlink (mnt->mnt_fsname);
#ifdef DEBUG
		printf ("/etc/fstab: device %s -> %s \n", mnt->mnt_fsname, resolved);
#endif
		if (strcmp (device, resolved) == 0) {
			printf ("%s (-> %s) found in /etc/fstab. Not mounting.\n", mnt->mnt_fsname, resolved);
			permission_denied_etc_fstab (device);
		}

		g_free (resolved);
	}
	fclose (fstab);
}

static void
bailout_if_mounted (const char *device)
{
	FILE *mtab;
	struct mntent *mnt;

	/* check if /proc/mounts mentions this device... (with symlinks etc) */
	mtab = fopen ("/proc/mounts", "r");
	if (mtab == NULL) {
		printf ("cannot open /proc/mounts\n");
		unknown_error ();		
	}
	while ((mnt = getmntent (mtab)) != NULL) {
		char *resolved;

		resolved = resolve_symlink (mnt->mnt_fsname);
#ifdef DEBUG
		printf ("/proc/mounts: device %s -> %s \n", mnt->mnt_fsname, resolved);
#endif
		if (strcmp (device, resolved) == 0) {
			printf ("%s (-> %s) found in /proc/mounts. Not mounting.\n", mnt->mnt_fsname, resolved);
			already_mounted (device);
		}

		g_free (resolved);
	}
	fclose (mtab);
}


static void
handle_mount (LibHalContext *hal_ctx, LibHalPolicyContext *pol_ctx, const char *udi,
	      LibHalVolume *volume, LibHalDrive *drive, const char *device, uid_t invoked_by)
{
	int i, j;
	DBusError error;
	char mount_point[256];
	char mount_fstype[256];
	char mount_options[1024];
	char **allowed_options;
	char **given_options;
	gboolean wants_to_change_uid;
	char *mount_dir;
	char *cbh_path;
	FILE *cbh;
	GError *err = NULL;
	char *sout = NULL;
	char *serr = NULL;
	int exit_status;
	char *args[10];
	int na;
	GString *mount_option_str;
	gboolean pol_is_fixed;
	gboolean pol_change_uid;
	char *policy;
	gboolean allowed_by_policy;
	gboolean explicit_mount_point_given;
	const char *end;

#ifdef DEBUG
	printf ("device         = %s\n", device);
	printf ("invoked by uid = %d\n", (int) invoked_by);
#endif

	if (volume != NULL) {
		if (libhal_volume_is_mounted (volume)) {
			already_mounted (device);
		}
	} else {
		bailout_if_mounted (device);
	}

	if (volume != NULL) {
		dbus_error_init (&error);
		if (libhal_device_get_property_bool (hal_ctx, udi, "volume.ignore", &error) || 
		    dbus_error_is_set (&error)) {
			permission_denied_volume_ignore (device);
		}
	}

	bailout_if_in_fstab (device);

	/* TODO: sanity check that what hal exports is correct (cf. Martin Pitt's email) */

	/* read from stdin */
	fgets (mount_point,   sizeof (mount_point),   stdin);
	fgets (mount_fstype,  sizeof (mount_fstype),  stdin);
	fgets (mount_options, sizeof (mount_options), stdin);
	if (strlen (mount_point) > 0)
		mount_point   [strlen (mount_point)   - 1] = '\0';
	if (strlen (mount_fstype) > 0)
		mount_fstype  [strlen (mount_fstype)  - 1] = '\0';
	if (strlen (mount_options) > 0)
		mount_options [strlen (mount_options) - 1] = '\0';
	/* validate that input from stdin is UTF-8 */
	if (!g_utf8_validate (mount_point, -1, &end))
		unknown_error ();
	if (!g_utf8_validate (mount_fstype, -1, &end))
		unknown_error ();
	if (!g_utf8_validate (mount_options, -1, &end))
		unknown_error ();
#ifdef DEBUG
	printf ("mount_point    = '%s'\n", mount_point);
	printf ("mount_fstype   = '%s'\n", mount_fstype);
	printf ("mount_options  = '%s'\n", mount_options);
#endif

	/* delete any trailing whitespace options from splitting the string */
	given_options = g_strsplit (mount_options, "\t", 0);
	for (i = g_strv_length (given_options) - 1; i >= 0; --i) {
		if (strlen (given_options[i]) > 0)
			break;
		given_options[i] = NULL;
	}

	/* figure out mount point if no mount point is given... */
	explicit_mount_point_given = FALSE;
	if (strlen (mount_point) == 0) {
		char *p;
		const char *label;

		if (volume != NULL)
			label = libhal_volume_get_label (volume);
		else
			label = NULL;

		if (label != NULL) {
			/* best - use label */
			g_strlcpy (mount_point, label, sizeof (mount_point));

			/* TODO: use drive type */

		} else {
			/* fallback - use "disk" */
			g_snprintf (mount_point, sizeof (mount_point), "disk");
		}

		/* sanitize computed mount point name, e.g. replace invalid chars with '-' */
		p = mount_point;
		while (TRUE) {
			p = g_utf8_strchr (mount_point, -1, G_DIR_SEPARATOR);
			if (p == NULL)
				break;
			*p = '-';
		};

	} else {
		explicit_mount_point_given = TRUE;
	}

	/* check mount point name - only forbid separators */
	if (g_utf8_strchr (mount_point, -1, G_DIR_SEPARATOR) != NULL) {
		printf ("'%s' is an invalid mount point\n", mount_point);
		invalid_mount_point (mount_point);
	}

	/* check if mount point is available - append number to mount point */
	i = 0;
	mount_dir = NULL;
	while (TRUE) {
		g_free (mount_dir);
		if (i == 0)
			mount_dir = g_strdup_printf ("/media/%s", mount_point);
		else
			mount_dir = g_strdup_printf ("/media/%s-%d", mount_point, i);

#ifdef DEBUG
		printf ("trying dir %s\n", mount_dir);
#endif

		if (!g_file_test (mount_dir, G_FILE_TEST_EXISTS)) {
			break;
		}

		if (explicit_mount_point_given) {
			mount_point_not_available (mount_dir);
		}

		i++;
	}

	/* TODO: possible race here... need to have only one hal-storage-mount copy run at a time */

	dbus_error_init (&error);
	allowed_options = libhal_device_get_property_strlist (hal_ctx, udi, "volume.mount.valid_options", &error);
	if (dbus_error_is_set (&error)) {
		unknown_error ();
	}

#ifdef DEBUG
	for (i = 0; given_options[i] != NULL; i++)
		printf ("given_options[%d] = '%s'\n", i, given_options[i]);
	for (i = 0; allowed_options[i] != NULL; i++)
		printf ("allowed_options[%d] = '%s'\n", i, allowed_options[i]);
#endif

	wants_to_change_uid = FALSE;

	/* check mount options */
	for (i = 0; given_options[i] != NULL; i++) {
		char *given = given_options[i];

		for (j = 0; allowed_options[j] != NULL; j++) {
			char *allow = allowed_options[j];
			int allow_len = strlen (allow);

			if (strcmp (given, allow) == 0) {
				goto option_ok;
			}

			if ((allow[allow_len - 1] == '=') && 
			    (strncmp (given, allow, allow_len) == 0) &&
			    (int) strlen (given) > allow_len) {

				/* option matched allowed ending in '=', e.g.
				 * given == "umask=foobar" and allowed == "umask="
				 */
				if (strcmp (allow, "uid=") == 0) {
					uid_t uid;
					char *endp;
					/* check for uid=, it requires special handling */
					uid = (uid_t) strtol (given + allow_len, &endp, 10);
					if (*endp != '\0') {
						printf ("'%s' is not a number?\n", given);
						unknown_error ();
					}
#ifdef DEBUG
					printf ("%s with uid %d\n", allow, uid);
#endif
					wants_to_change_uid = TRUE;

					goto option_ok;
				} else {

					goto option_ok;
				}
			}
		}

		/* apparently option was not ok */
		invalid_mount_option (given, invoked_by);

	option_ok:
		;
	}

	/* Check policy */
	pol_is_fixed = TRUE;
	if (libhal_drive_is_hotpluggable (drive) || libhal_drive_uses_removable_media (drive))
		pol_is_fixed = FALSE;

	pol_change_uid = FALSE;
	/* don't consider uid= on non-pollable drives for the purpose of policy 
	 * (since these drives normally use vfat)
	 */
	if (volume != NULL) {
		/* don't consider uid= on vfat change-uid for the purpose of policy
		 * (since vfat doesn't contain uid/gid bits) 
		 */
		if (strcmp (libhal_volume_get_fstype (volume), "vfat") != 0) {
			pol_change_uid = wants_to_change_uid;
		}
	}

	if (pol_is_fixed) {
		if (pol_change_uid) {
			policy = "storage-fixed-mount-change-uid";
		} else {
			policy = "storage-fixed-mount";
		}
	} else {
		if (pol_change_uid) {
			policy = "storage-removable-mount-change-uid";
		} else {
			policy = "storage-removable-mount";
		}
	}

#ifdef DEBUG
	printf ("using policy %s for uid %d\n", policy, invoked_by);
#endif

	if (libhal_policy_is_uid_allowed_for_policy (pol_ctx, 
						     invoked_by,
						     policy,
						     udi,
						     &allowed_by_policy) != LIBHAL_POLICY_RESULT_OK) {
		printf ("cannot lookup policy\n");
		unknown_error ();
	}

	if (!allowed_by_policy) {
		printf ("refused by policy\n");
		refused_by_policy (policy, invoked_by);
	}

#ifdef DEBUG
	printf ("passed policy\n");
#endif

	/* create directory and the .created-by-hal file */
	if (g_mkdir (mount_dir, 0700) != 0) {
		printf ("Cannot create '%s'\n", mount_dir);
		unknown_error ();
	}

	cbh_path = g_strdup_printf ("%s/.created-by-hal", mount_dir);
	cbh = fopen (cbh_path, "w");
	if (cbh == NULL) {
		printf ("Cannot create '%s'\n", cbh_path);
		g_rmdir (mount_dir);
		unknown_error ();
	}
	fclose (cbh);

	/* construct arguments to mount */
	na = 0;
	args[na++] = "/bin/mount";
	if (strlen (mount_fstype) > 0) {
		args[na++] = "-t";
		args[na++] = mount_fstype;
	} else if (volume == NULL) {
		/* non-pollable drive; force auto */
		args[na++] = "-t";
		args[na++] = "auto";
	} else if (libhal_volume_get_fstype (volume) != NULL && strlen (libhal_volume_get_fstype (volume)) > 0) {
		args[na++] = "-t";
		args[na++] = (char *) libhal_volume_get_fstype (volume);
	}

	args[na++] = "-o";
	mount_option_str = g_string_new("noexec,nosuid,nodev");
	for (i = 0; given_options[i] != NULL; i++) {
		g_string_append (mount_option_str, ",");
		g_string_append (mount_option_str, given_options[i]);
	}
	args[na++] = g_string_free (mount_option_str, FALSE); /* leak! */
	args[na++] = (char *) device;
	args[na++] = mount_dir;
	args[na++] = NULL;

	/* now try to mount */
	if (!g_spawn_sync ("/",
			   args,
			   NULL,
			   0,
			   NULL,
			   NULL,
			   &sout,
			   &serr,
			   &exit_status,
			   &err)) {
		printf ("Cannot execute /bin/mount\n");
		g_unlink (cbh_path);
		g_rmdir (mount_dir);
		unknown_error ();
	}


	if (exit_status != 0) {
		char errstr[] = "mount: unknown filesystem type";

		printf ("/bin/mount error %d, stdout='%s', stderr='%s'\n", exit_status, sout, serr);

		g_unlink (cbh_path);
		g_rmdir (mount_dir);

		if (strncmp (errstr, serr, sizeof (errstr) - 1) == 0) {
			unknown_filesystem (strlen (mount_fstype) > 0 ? 
					    mount_fstype : 
					    (volume != NULL ? libhal_volume_get_fstype (volume) : "") );
		}
		unknown_error ();
	}

	dbus_error_init (&error);
	libhal_device_set_property_string (hal_ctx, udi, 
					   "info.hal_mount.created_mount_point",
					   mount_dir,
					   &error);

	dbus_error_init (&error);
	libhal_device_set_property_int (hal_ctx, udi, 
					"info.hal_mount.mounted_by_uid",
					(dbus_int32_t) invoked_by,
					&error);

	g_free (sout);
	g_free (serr);
	g_free (cbh_path);
	g_free (mount_dir);
	libhal_free_string_array (allowed_options);
	g_strfreev (given_options);
}


int
main (int argc, char *argv[])
{
	char *udi;
	char *device;
	LibHalVolume *volume;
	DBusError error;
	LibHalContext *hal_ctx = NULL;
	LibHalPolicyContext *pol_ctx = NULL;
	uid_t invoked_by;

	device = getenv ("HAL_PROP_BLOCK_DEVICE");
	if (device == NULL)
		usage ();

	udi = getenv ("HAL_PROP_INFO_UDI");
	if (udi == NULL)
		usage ();

	invoked_by = (uid_t) atoi (getenv ("HAL_METHOD_INVOKED_BY_UID"));

	dbus_error_init (&error);
	if ((hal_ctx = libhal_ctx_init_direct (&error)) == NULL) {
		printf ("Cannot connect to hald\n");
		usage ();
	}

	pol_ctx = libhal_policy_new_context ();
	if (pol_ctx == NULL) {
		printf ("Cannot get policy context\n");
		unknown_error ();
	}

	volume = libhal_volume_from_udi (hal_ctx, udi);
	if (volume == NULL) {
		LibHalDrive *drive;

		drive = libhal_drive_from_udi (hal_ctx, udi);
		if (drive == NULL) {
			usage ();
		} else {
			handle_mount (hal_ctx, pol_ctx, udi, NULL, drive, device, invoked_by);
		}

	} else {
		const char *drive_udi;
		LibHalDrive *drive;

		drive_udi = libhal_volume_get_storage_device_udi (volume);
		
		if (drive_udi == NULL)
			unknown_error ();
		drive = libhal_drive_from_udi (hal_ctx, drive_udi);
		if (drive == NULL)
			unknown_error ();

		handle_mount (hal_ctx, pol_ctx, udi, volume, drive, device, invoked_by);

	}

	return 0;
}






More information about the hal-commit mailing list