/*
 * SPDX-FileCopyrightText: 2026 Didier Kryn <kryn@in2p3.fr>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
/* This program must run under root permission.  It creates a directory named */
/* after  the user's  name,  in  /media,  if it doesn't exist  yet,  then yet */
/* another  directory in the previous one,  to serve as a mount point for the */
/* partition.  If the name  of this directory is not specified,  the label of */
/* the filesystem or the name of the device is used instead.                  */
/* Then the specified filesystem is mounted at that directory.                */
/* When the first argument is  -u,  and the second is the name of an existing */
/* mountpoint  under  /media/$USER,  the  filesystem  is  unmounted  and  the */
/* mountpoint is deleted. */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <libgen.h>
#include <sys/mount.h>
#include <limits.h>
#include <blkid/blkid.h>
#include <locale.h>
#include <sys/capability.h>
#include <signal.h>
#include "strLcpy.h"
#include "slash_sys.h"
/*------------- Internationalization -------------*/
#ifndef PACKAGE
 #define PACKAGE "qmount"
#endif
#define LOCALEDIR "/usr/local/share/locale"
#include <libintl.h>
#define _(StRiNg) gettext(StRiNg)
#define gettext_noop(String) String
#define N_(String) gettext_noop(String)
/*------------------------------------------------*/

static cap_t caps = NULL;

/* function to create target directory in /media/username/ */
int install_target(const char *username, const char *targetname, uid_t, gid_t);

/* Function to obtain filesystem type */
int fsinfo(blkid_cache cache, const char *devname,
           char **fstype, char **fslabel);

int dmount(const char *username, const char *targetname);

/**
 * drop_privileges - Free up and remove process capabilities.
 * @caps: cap_t struct previously gotten with cap_get_proc.
 *
 * This function is safe to call even if caps is NULL.
 */
void drop_privileges(cap_t caps)
{
  if(caps == NULL) return;
  cap_clear(caps);
  /* We synchronize with the kernel to lose real privileges */
  if(cap_set_proc(caps) == -1) perror("cap_set_proc (drop)");
  cap_free(caps);
}

static void signal_handler(int sig)
{
  if (caps) drop_privileges(caps);
  exit(sig);
}

int main(int argc, char **argv)
{
  const unsigned long mntflags = MS_RELATIME | MS_NOEXEC | MS_NOSUID | MS_NODEV;
  static blkid_cache cache;
  static char username[40]={0}, device[256]={0}, label[256]={0};
  static char targetpath[PATH_MAX], options[160], devicename[256];
  char *fstype, *fslabel, *data, *temp;
  const char *targetname;
  uid_t uid, euid;
  gid_t gid, egid;
  int num_caps;
  int targetfd, i, n, q=0;
  {
    /*----- First set the locale from available environment variables -----*/
    /*               and configure message translation                     */
    /* Use the first available of LC_MESSAGES, LC_ALL, LANG, in this order */
    int j;
    const char *var[3] = {"LC_MESSAGES", "LC_ALL", "LANG"};
    const char *value, *mylocale;
    for(j=0; j<3; j++)
      {
	value=getenv(var[j]);
	if(value) break;
      }
    mylocale = setlocale(LC_ALL, value);
    /* Need to set LC_ALL. If only LC_MESSAGES, then utf-8 doesn't work. */
    /*    fprintf(stderr, "Set LC_ALL to %s.\n", value); */
    /*  Initialize gettext operation  */
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
  }

  signal(SIGINT, signal_handler);
  signal(SIGTERM, signal_handler);

  /*-------------------- set and get necessary bits -----------------------*/
  //umask(022);
  uid = getuid();
  gid = getgid();
  //euid = geteuid();

  cap_value_t cap_list[] = {
    CAP_SYS_ADMIN,    /* for mount */
    CAP_DAC_OVERRIDE, /* to create the directory in /media */
    CAP_CHOWN         /* to assing the uid/gid */
  };

  num_caps = sizeof(cap_list) / sizeof(cap_list[0]);

  /*---------- Acquire the desired capabilitites --------*/
  caps = cap_get_proc();
  if(caps == NULL)
    {
      perror(_("cap_get_proc"));
      return 1;
    }

  if(cap_set_flag(caps, CAP_EFFECTIVE, num_caps, cap_list, CAP_SET) == -1)
    {
      perror(_("cap_set_flag"));
      cap_free(caps);
      return 1;
    }

  if(cap_set_proc(caps) == -1)
    {
      perror(_("cap_set_proc "
             "(Did you run: sudo setcap cap_sys_admin,"
             "cap_dac_override,cap_chown+p qmount?)"));
      cap_free(caps);
      return 2;
    }

  {
    /*---------------------------- Get user name ---------------------------*/
    struct passwd *p;
    p = getpwuid(uid);
    strLcpy(username, p->pw_name, sizeof(username));
  }
  /*---------- Check the presence and validity of the first argument --------*/
  if(argc<2) goto noarg;
  /*------------------ Special case for unmount -----------------*/
  if(!strcmp("-u", argv[1]))
    {
      if(argc<3)
        {
          fputs(_("Please specify mountpoint to unmount.\n"), stderr);
          q = 1;
        }
      else
        {
          q = dmount(username, basename(argv[2]));
        }
      goto cleanup;
    }

  /*- Otherwise consider it is a device spec and find the device -*/
  if( blkid_get_cache(&cache, 0) ) goto cache_error;
  temp = blkid_evaluate_spec(argv[1], &cache);
  if(!temp) goto unknown;
  strLcpy(device, temp, sizeof(device));
  free(temp);

  /*---------------- sanitize the second argument if it exists --------------*/
  if(argc>2)
    {
      char *a = argv[2];
      /* remove potential trailing slashes */
      int n = strlen(a);
      for(i=n-1; i>=0 && a[i]=='/'; i--) a[i] = 0;
    }

  /*----------------- Collect filesystem's type and label -------------------*/
  if( fsinfo(cache, device, &fstype, &fslabel) ) goto fs_error;

  /* --- NEW VALIDATION: Filter encrypted containers --- */
  if(strcmp(fstype, "crypto_LUKS") == 0)
    {
      fprintf(stderr,
              _("Error: The device %s is encrypted (LUKS)\n"
              "You must first unlock it with cryptsetup.\n"),
              device);
      goto cleanup;
    }

  /* sanitize label as a possible directory name */
  if(*fslabel) sscanf(fslabel, "%*[ /]%255[^ /]", label);

  /*------------------ Print collected information --------------------------*/
  /*printf("uid=%d, gid=%d, username=\"%s\","
    " device=\"%s\", filesystem-type=%s, label=\"%s\".\n",
    uid, gid, username, device, fstype, fslabel);
  */

  /*--- Check the block device doesn't belong to a disk used by the system --*/
  /*                   and it is not already mounted                         */
  strLcpy(devicename, device, sizeof(devicename));
  q = osuse(basename(devicename));
  if(q) goto forbidden;
  /* Uncomment for dry test */
  /*  printf("Ready to mount %s!\n", device);
    goto error;
  */

  /*------------------------ Decide mount-point name ------------------------*/
  if(argc>2) targetname = basename(argv[2]);
  else if(fslabel && !strchr(fslabel, '/'))
    {
      sscanf(fslabel, " %255s", label);
      targetname = label;
    }
  else targetname = basename(argv[1]);
  /* printf("mount-point name = \"%s\".\n", targetname); */

  /*--- Make sure the target drectory exists -- create it if non-existing ---*/
  /*           and also verify it is not already a mount point               */
  targetfd = install_target(username, targetname, uid, gid);
  if(targetfd<0) goto error; /* Message already printed by install_target() */
  snprintf(targetpath, sizeof(targetpath), "/media/%s/%s", username, targetname);

  /*---------------------- Set fstype-specific options   --------------------*/
  if(!strcmp("vfat", fstype))
    {
      long luid = uid, lgid = gid;
      /* uid_t and gid_t are int but their size may be arch-dependant */
      /* since we must print them correctly, we first convert them    */
      sprintf(options,
              "uid=%ld,gid=%ld,fmask=0177,dmask=0077,codepage=437"
              ",iocharset=iso8859-1,shortname=mixed,quiet,utf8"
              ",errors=remount-ro", luid, lgid);
      /* The options above are OK for vfat */
      data = options;
    }
  else if(!strcmp("exfat", fstype))
    {
      long luid = uid, lgid = gid;
      /* uid_t and gid_t are int but their size may be arch-dependant */
      /* since we must print them correctly, we first convert them */
      sprintf(options, "uid=%ld,gid=%ld,fmask=0177,dmask=0077", luid, lgid);
      /* The options above are OK for xfat */
      data = options;
    }
  else data = 0;

  /*------------------------------ do mount ---------------------------------*/
  q = mount(device, targetpath, fstype, mntflags, data);
  if(!q)
    {
      printf(_("Successfully mounted %s on %s\n"), device, targetpath);
      goto cleanup; /* Complete success */
    }

  /*--------------------------- Failed to mount -----------------------------*/
  {
    /* PATH_MAX * 3 (for device + targetpath + options and text) */
    char buf[PATH_MAX * 3];
    snprintf(buf, sizeof(buf),
             "mount -t %s -o relatime,nosuid,nodev,noexec,%s %s %s",
             fstype, data ? data : "defaults", device, targetpath);
    perror(buf);
  }
  goto error;

  /*---------------------------- Other errors -------------------------------*/
 forbidden: /* Explain why it is forbidden by our policy */
  switch(q)
    {
    case -2:
      perror(_("Cannot open /proc/mounts"));
      break;
    case -1:
      fprintf(stderr, _("%s is not a block device.\n"), device);
      break;
    case 1:
      fprintf(stderr,
              _("%s, or the disk containing it, is used by the system.\n"),
              device);
      break;
    case 2:
      fprintf(stderr, _("%s is already mounted.\n"), device);
      break;
    }
  goto error;

  /* Other errors */
 cache_error:
  perror(_("blkid error"));
  goto error;

 unknown:
  fprintf(stderr, _("Device not found: %s.\n"), argv[1]);
  goto error;
 fs_error:
  fputs(_("Cannot determine file system type.\n"), stderr);
  goto error;
 noarg:
  /* Translators: the word before ':'is the name of the program. */
  fprintf(stderr, _("%s: please specify the device.\n"), PACKAGE);
  goto error;

 error:
  if(!q) q = 1;

 cleanup:
  if(cache) blkid_put_cache(cache);
  drop_privileges(caps);
  return q ? EXIT_FAILURE : EXIT_SUCCESS;
}
