How to Run a Program as a Different User in Linux Mint

Share
In this tutorial, we'll learn how how to run a program, be it a terminal command or a graphical application, including a web browser, as a different user in Linux Mint. This same process may also work for other distros. We'll learn both how to do it in the terminal, and how to create an icon on the desktop to run the program as a different user. To do this, we'll use the terminal commands su, sudo, and machinectl. Let's learn how they work.

The Difference Between su and sudo

Both su and sudo commands are very similar. Their main difference is that sudo can only be used by an user in the sudoers group, i.e. by an admin user, while su can be used by anyone so long as they have the target user's password.

When sudo is used by user1 to run a command as user2, the terminal will ask for user1's password, and if user1 is in the sudoers group, it will run the command as user2. If they aren't in the sudoers group, the command won't run and an error will be shown.

When su is used by user1 to run a command as user2, the terminal will ask for user2's password. It's like if you had to login as a different user, but not exactly (we'll see why below). If you don't have user2's password, you won't be able to run the command.

Running a Single Command as Root

By default, both su and sudo will switch to the root user.

Other distros may operate differently, but for Linux Mint: during the installation process, you're asked to set up a password for your own account, which is automatically added to the sudoers group. This means you can use sudo and type your own password to run commands as root. For example, like this:

sudo apt install krita

If you type the above in the terminal, it will run the command apt install krita as root, which will install Krita using the Aptitude package manager found in various Debian-based distros, including Ubuntu. Note that it just runs everything after the sudo part as the other user.

You can't use the su command with root in Linux Mint by default. If you try, you'll be prompted to type a password, but no matter what you type, it won't work. That's because root doesn't have a password by default in Linux Mint.

You can set a password for root by typing sudo passwd root. There isn't much of a point in doing this, as you can just use sudo instead.

Running Multiple Commands as Root

Although it's a bad idea, it's possible to login as root inside a terminal. Doing this, every command you type will be executed as root, which means you don't need to type sudo every time, and you can potentially destroy your entire system by making a mistake.

To do this, we use the --login or -i flag:

sudo --login

This will ask for your own password, and if you're in the sudoers group, it will switch to the sudo user.

To switch back, you type:

exit

Running a Single Command as a Non-Root User

We can use sudo to run a single command as a non-root user through the -u or --user flag. (note: -U in upper-case is a different flag and does something else.)

sudo --user user2 touch /home/user2/test

Replace user2 with the username of the user you want to run the command as.

The command above will ask for your own password, and if you're in the sudoers group, it will run touch /home/user2/test as user2. This command, touch, creates an empty file. Naturally, we could have done this without --user since root would have access to their home directory, but in that case touch would create the test file with root as its owner. Because we used --user, it creates the file with user2 as its owner.

We can also use su in this case with the -c or --command flag. Note that we have two versions of this command, with --login and without.

su user2 --command "touch /home/user2/test"
su --login user2 --command "touch /home/user2/test"

This time, you'll be asked to type user2's password instead of your own, and whether you're a sudoer or not doesn't matter, so it can be run by any user.

It's a good idea to always use the --login flag. What this does is to simulate a new login from blank as the target user, including loading environment variables from scratch, rather than using information of the current terminal session.

For example, if you do this:

su user2 --command "ls"
su --login user2 --command "ls"

The --login version will display user2's home directory contents. That is because by default when you login the current directory is set to your home directory (in this case, /home/user2). Meanwhile, the version without --login will not do that, so if you are in /home/user1, and run the command above, and user2 doesn't have execute access on that directory (+x on a directory lets you list its files), then you'll get the message "ls: cannot open directory '.': Permission denied."

Expanding Variables

You must escape ~ and $USER to run them using su and sudo in single line.

For example, if you run:

su user2 --command "echo $USER"

The output will be your own username (e.g user1, not user2). That's because $USER is being expanded before the command executes. So what is being by user2 is echo user1, not echo $USER.

In this case, you we can use \ to escape $.

su user2 --command "echo \$USER"

Now the output will be user2.

Similarly, if you run:

su user2 --command "touch ~/test"

This will not work because ~/test will be expanded to /home/user1/test, which user2 won't have permission to access (more specifically, write permission to create files).

I'm not sure how to escape the tilde, to be honest, \~ doesn't seem to work. But we can use \$HOME to achieve the same thing.

su user2 --command "touch \$HOME/test"

Running Multiple Commands as a Different User

To switch to a different user in the terminal using sudo, we use the flag --login and the flag --user.

sudo --login --user user2

To switch to a different user using su, we just type the username after su. Once again, we have the option to use the --login flag which simulates a new session as we learned above.

su user2
su  --login user2

If you think --login is too long, you can type - instead:

su - user2

Note: this is discouraged1.

Once again, with sudo you'll be asked your own password, while with su you'll be asked the target user's password. When you're done, you can switch back to your own user by typing the command:

exit

Personally, I have multiple users to test things, and I prefer to set passwords for them and switch using su instead of sudo. I think it's a good practice to avoid using sudo and typing your admin password if you can. Just set a very simple password, like hunter2. So long as they aren't in the sudoers group, they can't actually do anything even if someone managed to log in as them. At least I think they can't. I hope they can't.

Running an Application as a Different User

To run an application as a different user, with a window and buttons, we'll have a bit of trouble, for a couple of reasons.

Note: the method is this section isn't sufficient to run a web browser or applications that use keyrings; we'll see how to do that below.

First off, we'll need to know what command we need to run to run the application in the first place. For some applications that is very easy. For example, to run Linux Mint's default file manager, Nemo, we just run nemo and that's it. However if we're trying to run something that was installed as flatpak, or an .appimage file, or a snap, or a Windows .exe through WINE or Bottles, things will get more complicated. So the first step is to figure out which command you need to type in order run the application as yourself from the terminal.

One method, if the application can be found in the start menu, is to type its name in the start menu, right click on it, and click Properties. This will open a window titled "Launcher Properties." The command field is what we want to execute. For example, I installed Obsidian via flatpak. This is the command I see for it:

/usr/bin/flatpak run --branch=stable --arch=x86_64 --command=obsidian.sh --file-forwarding md.obsidian.Obsidian @@u %U @@

This means that to run this with sudo, we would type:

sudo --user user2 /usr/bin/flatpak run --branch=stable --arch=x86_64 --command=obsidian.sh --file-forwarding md.obsidian.Obsidian @@u %U @@

And with su, we would type:

su --login user2 --command "/usr/bin/flatpak run --branch=stable --arch=x86_64 --command=obsidian.sh --file-forwarding md.obsidian.Obsidian @@u %U @@"

Unfortunately, this wouldn't actually work. Because an application with a window is a graphical application, with a GUI, not a text program, with a CLI, it needs to be able to position windows on the 2-dimensional screen, not just output text, which means it needs to interface with a window manager somehow.

How this works exactly depends on whether we're running X11 or a Wayland implementation.

By default, Linux Mint's desktop environment is Cinnamon. It also has Mate and XFCE flavors. None of these DE's support Wayland yet, so I only need to explain how to do it in X11.

If you try to run Nemo, which uses GTK, you'll get the error "Could not parse arguments: Cannot open display:." For Krita, which uses Qt, it's "qt.qpa.xcb: could not connect to display." For Obsidian, "Unable to autolaunch a dbus-daemon without a $DISPLAY for X11."

Fixing X11 System Applications with XHost

Here's one method to make it applications installed in the system work2. This method doesn't work with flatpaks.

Danger: some guides tell you to run xhost + which disables a very important security measure. If you do this, an attacker from the Internet may be able to see everything on your screen and everything you type3. We don't want this to happen, so we won't type this.

First, we need to allow user2 to connect to your X11 server, which what controls what is displayed on screen. We do this using the xhost command.

If you type just:

xhost

You'll see the current status of this service, whether it's enabled or not, and who is allowed to connect to the X11 server. By default, it should be only one user: you.

We'll type the following command to grant access to another user:

xhost +si:localuser:user2

Replace user2 with the user you want to run the application as. This command works as follows:

  • + means "allow." We can use - to revoke it later.
  • si:localuser means it's an user in the machine. This command can also be used to allow users from the Internet, which is why it's dangerous, but if we use it like this, there won't be much problem.
  • user2 is the name of the user.

This should output the message "localuser:user2 being added to access control list."

If we wanted to undo the command above, we would type:

xhost -si:localuser:user2

The second step we need to take is set the correct value for the DISPLAY environment variable.

First, as your own user (user1), type the following command:

echo $DISPLAY

This will show you the value of the variable DISPLAY. A possible value is :0 if you have only one screen, or :0.0 if you have multiple screens.

Switch to user2, e.g. using su - user2, and type the following command:

export DISPLAY=:0

Observe that:

  1. You must replace :0 with the value that was outputted when you echo'd as user1.
  2. Do not type $, just DISPLAY.
  3. Do not type spaces around =. If you type DISPLAY = :0 it won't work.

The command above sets the environment variable DISPLAY.

Now you should be able to run nemo, etc., and it will run just fine as user2. There's a couple obvious implications of this that are worth listing:

  1. You will have access to /home/user2 and any files within that are owned by user2.
  2. You will not have access to /home/user1, even though you are logged in in the desktop environment as user1, because nemo is running from the terminal as user2.
  3. Any application that you open from nemo will open as user2. If you right click on an empty space in a folder and click Open in Terminal, it will open a terminal that will say user2@your-computer-name. If you double click on a file, it will open the application associated with that file type as user2.
  4. Applications running as user1 and user2 will appear identical. There is generally no way to tell which user is running an application except for trying to access a file they wouldn't have access. A workaround for this is to use workspaces in Cinnamon. Place the terminal in a different workspace, and keep all applications and windows of user2 in that workspace, so you don't mix them up with your own applications.

Running Commands in the Background

By the way, if you want to run multiple applications from the command line for some reason, you may notice that you can't type the name of an application while another is running. There is a way to make the process run in the background so you can type in the terminal again4.

1: press Ctrl+Z in the terminal. This stops the running process and returns control to the terminal. The window will still appear on the screen but it will be unresponsive.

2: execute the command bg. This will resume the process in the background.

Creating an Icon on Desktop

It's possible to create an icon on the desktop to run an application as another use. To do this follow the following steps:

1: right click on the desktop and select "Create a new launcher here..."

2: give it a name such as "Nemo as User2"

3: check "Launch in Terminal?" This is important because we'll need to type user2's password.

4: in the command field, we need to run 4 different commands.

The xhost +si:localuser:user2 command; the su - user2 command; the export DISPLAY=:0 command; and the nemo command. You may notice we can't use multiple lines, so it's probably a better idea to make this a bash script, but we can still do it one line if we make it long enough.

The command we need to type is this:

bash -c "xhost +si:localuser:user2 && su - user2 -c \"export DISPLAY=$DISPLAY && nemo\""

Note: if you are editing the .desktop launcher file manually, you need to double escape the \ for the exec field like this:

[Desktop Entry]
Name=nemo as user2
Exec=bash -c "xhost +si:localuser:user2 && su - user2 -c \\"export DISPLAY=$DISPLAY && nemo\\""
Comment=
Terminal=true
PrefersNonDefaultGPU=false
Icon=cinnamon-panel-launcher
Type=Application

Let's understand what this command does.

First bash -c "<commands>" runs the commands in quotes in in bash.

The && is a bash operator. In A && B, bash will run the command A, and if it succeeds, it will run the command B. Note that xhost will succeed even if you try adding an user that has already been added.

Then su - user2 -c "<commands>" will run the commands as user2. We need to escape the quotes with \" because we're already inside a quotes from bash -c "...". These commands are the ones that need to be run as user2.

The first command is export DISPLAY=$DISPLAY. We need to set DISPLAY in user2 the same value as DISPLAY in user1. As we already know, $DISPLAY will be expanded by bash as user1 if we don't escape it with \$, so this should work perfectly.

The last thing we have is nemo, which is the program we'll run.

I don't know why, but the terminal window closes automatically when you run this, leaving behind only Nemo running. It doesn't look like bash keeps running after Nemo is closed, so I don't think that is a problem.

5: click OK to create the launcher on the desktop.

You can move it from the desktop to another folder later if you want.

Running a Web Browser as a Different User

It's possible to run a web browser as a different user via the terminal, but this is rather tricky. Mainly because of how passwords are stored in browsers on Linux. Essentially, the passwords are stored using a different program called a keyring. On Mint, this program is gnome-keyring-daemon, but on systems based on KDE like Kubuntu it would be kwallet.

If we run a web browser as a different user through the methods above, the web browser won't be able to access the keyring, and this means it won't be able to show you your passwords or other information. It could, for example, forget all your tabs, or all your cookies, so you wouldn't be able to login into any website without typing everything back again. It could also end up corrupted, so that even if you login normally as a different user and open the browser, everything will be gone.

On Vivaldi, it warns me if it can't open my keystore. I'm not sure if this happens in Firefox or Chrome. If you switch from a GNOME-based desktop environment to KDE Plasma, or vice-versa, you can also have problems like this. In fact, it can also happen due to issues with the password you use to log in.

In any case, I'm not sure the method above can work, so we need a different method.

Sorry, but "su" is a tool for changing user identities and very few other process credentials temporarily. It's not a tool for opening a completely new login session. A new login session has a very well defined, pristine setup, not inheriting anything from any other session, but this is really not the case for "su" uid changes: most of the execution environment is inherited over, in numerous and non-obvious ways, for example MAC contexts, audit contexts, cgroup contexts, namespace contexts, scheduling, timer granularity, …

if you want a full new session, use something like "machinectl login" or "machinectl shell", which will actually get your a fully clean, independent, detach environment, with no hidden process properties leaking from where you call it into it.

https://github.com/systemd/systemd/issues/7451#issuecomment-346787237 (acessed 2024-09-16)

Essentially, there are three ways to run a program as a different user:

  1. su, which simply runs the program in the current shell environment as a different user.
  2. su --login, which creates a new shell environment then runs the program.
  3. machinectl login, which creates a whole login session in a different tty.

On Linux Mint, if you press (danger: do not press it) Ctrl+Alt+F1, F2, F3, and so on, the system switches from one tty to another. What this means is that you'll see a black screen with just a terminal asking you to login, and even if you do login you'll still only see a black screen. If you ever do this, you can use the command loginctl to get a list of which tty's are being used and switch back to your original tty. For example, if it says tty7, you press Ctrl+Alt+F7 to go back to Cinnamon.

The machinectl program works the same way, except that instead of the terminal spanning the entire window it's confined to the terminal window.

One problem is that I don't know how to exit machinectl after running it, so it's a good idea to run it in a separate terminal window then you can just close the window to exit.

machinectl provides two sub-commands: login, which you can use to login as a different user, and shell, which you can use to execute commands as a different user. It seems that shell requires sudoer privileges, so we aren't going to use it.

Let's see what we need to do to run a web browser as a different user.

First, we need to perform some tasks we did above, such as:

  1. We need to grant the user access through xhost.
  2. We need to know what our DISPLAY environment variable is.

Then:

1: open the terminal and run the following command:

machinectl login

This will show you a screen asking for you login and then password.

2: your username, e.g. user2, and press Enter. You'll be prompted for your password.

3: type your password, e.g. hunter2, and press Enter. You should be logged in now.

4: set the DISPLAY environment variable so that it's the same value as echo $DISPLAY on user1. For example, if you have one monitor, you'll need to type this:

export DISPLAY=:0

Unlocking the Wallet Manually

1: open Seahorse (GNOME's Password and Keys application) to check it your "Login" wallet is unlocked.

seahorse

It seems that this takes a while to open, but it should open without problems if everything is configured correctly.

On the side pane of Seahorse, you should see a folder icon labelled "Login" under "Passwords." If you click on it, it should say that it's locked and there will be a button to unlock it. Normally, pressing this button should prompt you for your password (in this case, user2's password), but for some reason when I click on it it just doesn't do anything, it doesn't show an error message, and it doesn't even print anything on the terminal.

Our first goal is to make this wallet open, because it's in it that web browsers store your passwords by default.

Normally, it's your greeter that opens this wallet. The greeter is the program that asks you for your password when you turn on the computer, before you see your desktop. The problem is that since we've logged in from the terminal, the greeter's program didn't run, so the login wallet didn't unlock.

2: close Seahorse.

3: run the follow command:

dbus-update-activation-environment --all

An explanation of why this is necessary:

This command passes environment variables from the window manager to session dbus. Without this, GUI prompts cannot be triggered over DBus. For example, this is required for seahorse password prompt.

This is required because session dbus is started before graphical environment is started. Thus, session dbus does not know about the graphical environment you are in. Someone or something has to teach session dbus about the graphical environment by passing environment variables describing the graphical environment to session dbus.

https://wiki.archlinux.org/title/GNOME/Keyring#Launching (accessed 2024-09-16)

4: open Seahorse again.

seahorse

5: click the Unlock button to unlock your login wallet.

This time the password prompt should appear. If you type the user's password, the wallet should be unlocked.

Making the Web Browser Recognize Your Wallet

The next problem is that even if you run your web browser with your wallet unlocked, it may not realize it's supposed to use that wallet. For example, if you run—

Danger: it seems that some browsers may delete your cookies and passwords if they can't open your wallet, so it's a good idea to create a new user to test these things if you don't want to lose your data, or just use Vivaldi which at least warns you first.

vivaldi

—to open Vivaldi, Vivaldi will show a message that says "Vivaldi could not unlock your secure key store. Without the key store, you will lose your saved logins, secure cookies, and some features like Sync, Mail, and Calendar will stop working. Please quit now, unlock or repair your key store, and then try again. Do you really want to continue, but lose data?"

This happens because Vivaldi and other Chromium-based browsers are designed to work with any wallet, so they can run on both GNOME and KDE systems. Our environment is missing something that Vivaldi needs to figure out which wallet it should use. There are two ways to fix this.

Manual way: we can pass a command-line argument to Vivaldi to make it use the GNOME wallet.

vivaldi --password-store=gnome-libsecret

Possible values include5:

  • gnome-libsecret
  • kwallet5
  • kwallet6
  • basic
  • detect

The better way: would be to setup the environment so that the browser detects it correctly. Unfortunately I have no idea how to do that, so I'll just pass the command-line argument.

Success: after following these steps, you should be able to run a web browser as a different user in Linux Mint. Next we need to find a way to automate this.

Unlocking the Login Wallet Automatically

DANGER!

Danger: it seems that if you use this method the keyring won't unlock automatically on login anymore, which can be a massive inconvenience. You can still make it work by adding a script that runs when you login, but I'd rather it didn't cause this sort of problem, since then you would have to manually update the keyring password if you changed your user password. I suspect the problem is that there is a bug in gnome-keyring-daemon that changes the keyring password if you login successfully, but it saves the inputted password while using a modified password to check if you typed the correct password, e.g. it strips the trailing newline to match, and then saves the newline. Since you can't type a newline in the password dialog box, you will stop being able to type the correct password afterwards.

We can automate some of the process above with the following script6:

export DISPLAY=:0
dbus-update-activation-environment --all
killall -q -u "$(whoami)" gnome-keyring-daemon
unset -v password
IFS= read -rsp "Password: " password < /dev/tty
printf '%s\n' "$password" | gnome-keyring-daemon --daemonize --login
unset password

Let's understand how it works.

First, killall will terminate any gnome-keyring-daemon that is already running. This is necessary because we can only have one of them running at a time per user. Observe that -u restricts the command to a given user. That's because this will error if user2 tries to kill a process owned by user1, for example. The code "$(whoami)" will become "user2", because that's what the command whoami prints.

Next we have unset. This just makes sure the variable we'll use isn't already set.

Then we have the read command. This lets us read some input from the command line and put it in a variable. In this case, our variable is named password. The -p flags lets us show the message Password: , and -s hides what we're typing, and r allows backslashes to be typed. The < indicates the input is coming from the device /dev/tty. IFS= seems to prevent read from striping leading and trailing whitespace7? This syntax doesn't make a lot of sense to me, but I don't really grok bash so I'll assume it's true.

The command printf '%s\n' "$password" works like echo, except it doesn't print a newline character8. Since passwords typically don't contain a trailing newline character, so it sounds like it's a good idea to use this, HOWEVER, it turns out that if you don't have a newline character (represented by \n above), it won't work.

I think the one issue that the poster of that thread may have had left is this part of the documentation of "gnome-keyring-daemon --login":

"It reads all of stdin (including any newlines) as a login password"

It seems tl-sso-password adds a newline at the end of its output, and hence "gnome-keyring-daemon --login" will probably always fail because it takes that newline as part of the password.

https://lists.cendio.se/pipermail/thinlinc-technical/2018-May/005857.html (accessed 2024-09-16)

It seems that in Linux Mint (and probably Ubuntu as well) the user passwords have newlines built into them, so if you don't include this newline it will look like a different password. Incidentally, the read command won't read the Enter key as a newline, so the password typed in read will never match the password typed in a normal password box (it also doesn't work if you use <<< in bash). So we need the newline, and that's why the \n.

After that we have a pipe (|) which sends the output of this command to gnome-keyring-daemon. We need this because when it's run with the --login flag it reads the login password from standard input. In other words, the password comes out of printf and goes into gnome-keyring-daemon, and that should properly unlock the login wallet.

Note: when gnome-keyring-daemon runs, it prints to standard output names of environment variables and their values that should be exported for it to work correctly. It seems you can make it work by wrapping everything in eval $(...) and then calling export SSH_AUTH_SOCK, but it doesn't seem to be necessary just to make a web browser run.

Finally we unset the password, just as a good safety measure so we don't accidentally let everyone read it. This whole thing feels like it's very dangerous and unsafe, but I'm not really sure if a safer method to do this exists..

After saving this script as ~/my-init.sh we need to run:

source ~/my-init.sh

This will make the export DISPLAY=:0 statement work.

Making Audio Work with Multiple Users

You may notice that applications can't play audio even after we did all this. To make audio play as a different user on Linux, we need to configure PipeWire to send the audio from one user to the other. PipeWire is implements PulseAudio's interface9, so tutorials about PulseAudio work for PipeWire in most cases.

I managed to get audio to work by setting a TCP socket in PipeWire with this code on user110:

pactl load-module module-native-protocol-tcp listen=127.0.0.1

And this on user2:

export PULSE_SERVER=tcp:127.0.0.1:4713

After doing this, audio works: I can listen to music and videos on Youtube, Spotify, etc. Unless some problem occurs, I think this should be good enough?

Observation: I'm not sure if this is the right (or safe) way to do it. I'm not good with low-level networking in general. Shouldn't I be using a Unix socket for this with module-native-protocol-unix? This module seems to be missing on PipeWire, however. Also, what is a Unix socket? If Unix sockets are only for inter-process communication of the same user, then it won't work (unless you create a separate group for sharing the socket). But isn't TCP an Internet protocol? Do two users on the same computer count as a network? I'm pretty sure 127.0.0.1 can't be accessed externally11, so it should be safe enough, but it would be better if I could explicitly say "I want this exact user to access my audio" instead of "anyone on this machine can."

Future Work

I haven't found a way to make flatpaks work. I haven't tried appimages, snaps, or Windows programs. Nor have I tried it on a Wayland.

I'm not sure how to automate machinectl so that I can just create a desktop icon to run something as a different user. It could be that you can use systemd-run for this12.

It seems there is a way to do this using xauth instead of xhost which may be more secure13.

References

Comments

Leave a Reply

Leave your thoughts! Required fields are marked *