Scripts launched from udev do not have DISPLAY access anymore? - linux

I have a script that runs from udev when I plug in my external drive. It always worked. But after upgrading from Linux 3.8/Xorg 1.12/Mint 14 (Ubuntu 12.10 compatible) to Linux 3.11/Xorg 1.14/Mint 16 (Ubuntu 13.10 compatible), it doesn't work anymore.
The script still runs, but none of the commands that require the display work. I figured that out by quitting the udev daemon and manually run udevd --debug for verbose output (more below).
This script used to work in Mint 14/12.10:
export DISPLAY=:0
UUID=$1
DEV=$2
notify-send -t 700 "mounting $DEV ($UUID)"
gnome-terminal -t "Backing up home..." -x rsync long line of data
zenity --warning --text="Done."
But not anymore in Mint 16/13.10. In case you are wondering about possible solutions, I gradually added stuff and now it looks like this:
export DISPLAY=:0.0
xhost +local:
xhost +si:localuser:root
xhost +
DISPLAY=:0.0
export DISPLAY=:0.0
UUID=$1
DEV=$2
notify-send -t 700 "mounting $DEV ($UUID)"
gnome-terminal -t "Backing up home..." -x rsync long line of data
zenity --warning --text="Done." --display=:0.0
But it still doesn't work. udevd --debug still shows this:
'(err) 'No protocol specified'
'(err) ''
'(err) '** (gnome-terminal:24171): WARNING **: Could not open X display'
'(err) 'No protocol specified'
'(err) 'Failed to parse arguments: Cannot open display: '
'(err) 'No protocol specified'
'(err) ''
'(err) '** (zenity:24173): WARNING **: Could not open X display'
'(err) 'No protocol specified'
'(err) ''
'(err) '(zenity:24173): Gtk-WARNING **: cannot open display: :0.0'
'(err) 'No protocol specified'
Note that any bash logic works. Echoing test vars to >>/tmp/test.log works. It's just accessing the display that does not work anymore.
This is driving me crazy. What is the correct way to achieve this now?
Update 2013-12-20
So, in the previous Ubuntu, X commands would automatically find it's way to the current X using user.
Now, I seem to need these two things every time:
On the X using user:
xhost +si:localuser:root
On the root/udev side:
Copy X using users' ~/.Xauthority file to /root
This 'feels' like a step back in time. This only works scripted when I log in as the same user everytime, so I can copy the .Xauthority file from that users' home when the script executes.
What 'trick' did the old Ubuntu use to have this done auto'magic'ally?

Ok, I'm writing this answer to try and clarify the security model of the X server, as I understand it. I'm not an expert on the subject, so I may have got some (many?) things wrong. Also, many things are different in different distributions, or even different versions of the same distribution, as the OP noted.
There are two main ways to get authorized to connect to the X server:
The xhost way (Host Access): The server maintains a list of hosts, local users, groups, etc. that are allowed to connect to the server.
The xauth way (Cookie based): The server has a list of randomly generated cookies, and anybody showing one of these cookies will be granted access.
Now, the distribution specific stuff...
When the X server is launch by the start-up system, it is usually passed a command line of the form -auth <filename>. This file contains a list of initial cookies to be used for authorization. It is created before the X server is run using the xauth tool. Then just after the X server, the login manager is launch, and it is instructed to read the cookie from this same file, so it can connect.
Now, when user rodrigo logs in, it has to be authorized to connect to the server. That is done by the login manager, and it has two options:
It does the equivalent to: xhost +si:localuser:rodrigo.
It generates another cookie, adds it to the server and passes it to the user. This passing can be done in two ways:
It is written in the file $HOME/.Xauthority (home of the new user).
It is written somewhere else (/var/run/gdm/auth-for-rodrigo-xxxx) and the environment variable XAUTHORITY is set to the name of that file.
Also, it can do both things. Some login managers even add the root user to the list of authorized users by default (as if xhost +si:localuser:root).
But note that if you are not authorized to connect to the X server, you cannot add yourself to the list (running xhost + for example). The reason is the same as why you cannot open a house doof from the outside without a key... That's true even if you are root!
Does it mean that the root user cannot connect to the server? Absolutely not! But to get to that first you have to know how is the logged user configured to connect to the server. For that run as the logged user:
$ xhost
It will show a message and the list of authorized users, hosts or groups, if any:
access control enabled, only authorized clients can connect
SI:localuser:rodrigo
Then run:
$ echo $XAUTHORITY
To see where the authorization file is saved. If it is empty, then it will be ~/.Xauthority. Then:
$ xauth list :0
To see the list of your authorized cookies.
Now, if there are any cookie in the server, the root user should be able to connect making the XAUTHORITY environment variable point to the right cookie file. Note that in many setups, the cookie of the login manager is also kept around. Just look for it!
Another possibility for root access is to modify the Xsession files to add the command xhost +si:localuser:root and get permanent access. The details vary with the particular program used, but for gdm you would simply add an executable script in /etc/gdm/Init/ with the xhost command and it will be run automatically in the next boot.
PS: You can check your root access to the X server with sudo -i, but note that some sudo configurations may keep the DISPLAY, XAUTHORITY or HOME variables and modify the results of the tests.
EXAMPLE: This script should be able to connect you to the X server as root
export DISPLAY=:0
export XAUTHORITY=`ls /var/run/gdm/auth-for-gdm-*/database`
xrandr #just for show
Naturally, the path for the XAUTHORITY variable will depend on what login manager you are using (greeter). You can use the user file (you say it is in /home/redsandro/.Xauthority but I'm not so sure). Or you can use the greeter cookie. To get the greeter cookie you can use the following command:
$ pgrep -a Xorg
Which in my system gives:
408 /usr/bin/Xorg :0 -background none -verbose -auth /var/run/gdm/auth-for-gdm-gDg3Ij/database -seat seat0 -nolisten tcp vt1
So my file is /var/run/gdm/auth-for-gdm-gDg3Ij/database. The gDg3Ij is random and changes every time the server is restarted, that's why the ls ... trick.
The nice thing of using the GDM cookie instead of the user is that it does not depend on the user logged in. It will even work with no user at all!
UPDATE: From your latest comment I see that your X server command is:
/usr/bin/X :0 -audit 0 -auth /var/lib/mdm/:0.Xauth -nolisten tcp vt8
So there is the name of the cookie used to start the login manager. If I'm correct, that should be available all the time, if you are able to read the file. And you are root, so, the following lines should be enough to get you access to the display as root:
export DISPLAY=:0
export XAUTHORITY=/var/lib/mdm/:0.Xauth
zenity --info --text 'Happy New Year'

A quick search turned up the following:
X authentication is based on cookies -- secret little pieces of random
data that only you and the X server know... So, you need to let the
other user in on what your cookie is. One way to do this is as
follows: Before you issue the su or sudo (but after having ssh'ed into
the remote system if you are using ssh), request the cookie for the
current DISPLAY that's connecting to your X server:
$ xauth list $DISPLAY You'll get something like
somehost.somedomain:10 mit-magic-cookie-1
4d22408a71a55b41ccd1657d377923ae
Then, after having done su, tell the new user what the cookie is:
$ xauth add somehost.somedomain:10 MIT-MAGIC-COOKIE-1
4d22408a71a55b41ccd1657d377923ae
(just copy and paste the output of the above 'xauth list' onto 'xauth
add') That's it. Now, you should be able to start any X application.
For reference, here is the origin http://www.linuxquestions.org/questions/linux-newbie-8/xlib-connection-to-0-0-refused-by-server-xlib-no-protocol-specified-152556/

This is not pretty, but I have not seen any solutions yet. So it's the best one so far.
On the X using user:
xhost +si:localuser:root
On the root/udev side:
Copy X using users' ~/.Xauthority file to /root (* see note below)
Now it works. Try zenity --warning --text=Hooray
This only works when you know which user is going to be logged into X. So it's only acceptable when the computer is being used by a single user with a single user account.
*) Note
This is notable, because I tried the documented ways of xauth merge /home/redsandro/.Xauthority and $XAUTHORITY=/home/redsandro/.Xauthority. These documented methods just plain do nothing these days, even if root has permission to read it. You need to literally the whole .Xauthority file in stead of just pointing to it.

Newer versions of Ubuntu use different display managers, so you have to know which one you are using.
In Rodrigo's post, there is a hint showing how to discover it, using this command:
ls /var/run/gdm/auth-for-gdm-*/database
To check this, list the /var/run directory and use the "pgrep -a Xorg" command.
In Ubuntu 16* it'´s using sddm, so, you can use
ls /var/run/sddm* to export the XAUTHORITY variable.
The script would be like this:
#!/bin/bash
export DISPLAY=:0
export XAUTHORITY=`ls /var/run/sddm*`
HDMI_STATUS="$(cat /sys/class/drm/card0-HDMI-A-1/status)"
USER="your username"
export XAUTHORITY=/home/$USER/.Xauthority
export DISPLAY=:0
if [ "$HDMI_STATUS" = connected ];
then
sudo -u $USER pactl set-card-profile 0 output:hdmi-stereo+input:analog-stereo
else
sudo -u $USER pactl set-card-profile 0 output:analog-stereo+input:analog-stereo
fi
exit 0
then run:
sudo chmod 755 /usr/local/bin/toggle-sound
echo 'ACTION=="change", SUBSYSTEM=="drm", RUN+="/usr/local/bin/toggle-sound"' | sudo tee /etc/udev/rules.d/99-hdmi-sound.rules
sudo udevadm control --reload-rules

I had to use this in Kali Linux 2016 to get it to work:
#!/bin/bash
set -x
xhost local:root
export DISPLAY=:0.0
su root -c 'zenity --notification --text="I am a notification!"'

If calling the script directly from udev doesn't work, why not start a systemd service which calls that script?
Here's my solution:
First is the udev rule that runs media-storage-unplugged.service when a device (or partition) that has ID_PART_ENTRY_UUID is unplugged
/etc/udev/rules.d/storage-unplugged.rules:
ACTION=="remove", KERNEL=="sd[a-z][0-9]", ENV{ID_PART_ENTRY_UUID}=="replace-with-your-uuid", SYMLINK+="storage", RUN+="/usr/bin/systemctl --no-block start media-storage-unplugged.service"
/etc/systemd/system/media-storage-unplugged.service: (service file)
[Unit]
Description=Triggered when storage is unplugged
[Service]
Type=oneshot
ExecStart=/usr/local/bin/storage_unplugged
[Install]
WantedBy=multi-user.target
/usr/local/bin/storage_unplugged (get creative here)
#!/bin/bash
notify-send-to-user "storage unplugged"
exit 0
/usr/local/bin/notify-send-to-user
#!/bin/bash
function ns() {
#Detect the name of the display in use
local display=":$(ls /tmp/.X11-unix/* | sed 's#/tmp/.X11-unix/X##' | head -n 1)"
#Detect the user using such display (NOTE: Didn't work on Arch linux since the "who" command doesn't show which display the user is using)
#local user=$(who | grep '('$display')' | awk '{print $1}' | head -n 1)
#Statically assign user:
local user="user" # Replace with your user
#Detect the id of the user
local uid=$(id -u $user)
sudo -u $user DISPLAY=$display DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus notify-send "$#"
}
ns "$#"
Adapt this method to your needs :)

Related

Issuing Command Via SSH Prompts for Password

I'm having an issue with a script used in a project I inherited that has little to no documentation, and am in the process of documenting everything. I'm trying to debug an issue with one line of a script that is executed on the host machine to call out to a LAN-attached Raspberry Pi with SSH to return some information about the Pi.
We already have working versions of this Raspberry Pi which can execute the script without issue, and I'm not sure what the difference is. When executed on the new one, it prompts for the root password on the Pi, but it has not done this on previous versions of the device. I assume it has something to do with the SSH configuration but I don't know enough about SSH to say what would be the cause.
The line in particular causing the issue is:
ssh -o StrictHostKeyChecking=no {host_name} uname -a &>/dev/null
rc=$? #gets the return value of the remote command so we can read the uname info
{host_name} of course is the actual host name it's connecting to, but I've left that part out for privacy reasons. The script is the same on both machines.
Both Pi devices are the same model and I'm having trouble narrowing down what could cause me to not be able to execute this command. Does anyone know what I need to configure in order to be able to execute this command on the Pi remotely?
Quick fix:
sshpass -p 'password' ssh -o StrictHostKeyChecking=no user#server
Detailed fix:
Most likely you would need to set up Async keys (public/Private) for proper passwordless login. Your command does not show you are using keys so I'm assuming you are not (e.g. -A or -i /path/to/key). Generally root user is blocked (I guess not your problem), I would set up another user for this or change sshd config. You could also Compare the sshd configurations between the Pi Boxes.
See: https://www.raspberrypi.org/documentation/remote-access/ssh/passwordless.md
Okay, so after some more digging around, I discovered that there was a separate .ssh directory under /root that contained an authorized_keys file. After copying this to the new Pi, it worked. I had been wondering all this time if there was a separate config folder for root, but I've never gone digging around /root, so I wasn't aware that it was there.

How can a BASH script automatically elevate to root on a remote server, without using sudoers nopasswd option?

o's!
Maybe you can help me with this. I can't find an answer to my specific questions, because there is an obvious solution which I'm not allowed to use. But first things first, the context:
In my company, which is a service provider, we administrate a bunch of
Linux servers. Some of my colleagues has for a long time been running
a BASH script from a source server, that then performs some tasks over
SSH on a number of remote Linux servers. The tasks it performs has to
be executed as root, so what the script does is it authorizes the
source server as root on the remote Linux servers via SSH (the remote
servers has the source servers public SSH key). Then what happened is
a new security policy was enforced and now root login over SSH is
denied. So the mentioned method no longer works.
The solution I keep finding, which we are by policy not allowed to do, is to create an entry in the sudoers file allowing sudo to root without password for the specific user.
This is the terms and they have to obey that. The only procedure that is allowed is to log on to the target server with your personal user, and then sudo su - to root WITH password.
Cocky as I apparently was, I said, "It should be possible to have the script do that automatically", and the management was like "Cool, you do it then!" and now I'm here at Stack Overflow,
because I know this is where bright minds are.
So this is exactly what I want to do with a BASH script, and I do not know if it's possible or how it's done, I really hope you can help me out:
Imagine Bob, he's logged into the source server, and he wants to
execute the script against a target server. Knowing that root over SSH
doesn't work, the authorization part of the script has been upgraded.
When Bob runs the script, it prompts him for his password. The
password is then stored in a variable (encrypted would be amazing) and
the script then logs on the target server as his user (which is
allowed) and then automatically elevates him to root on the target
server using the password he entered on the source server. Now the
script is root and it runs its tasks as usual.
Can it be done with BASH? and how?
UPDATE:
The Script:
## define code to be run on the remote system
remote_script='sudo -S hostname'
## local system
# on the local machine: prompt the user for the password
read -r -p "Enter password for $host: " password
# ...and write the password, followed by a NUL delimiter, to stdin of ssh
ssh -t 10.0.1.40 "$remote_script" < <(printf '%s\0' "$password")
The error:
[worker#source ~]$ sh elevate.sh
Enter password for : abc123
elevate.sh: line 10: syntax error near unexpected token `<'
elevate.sh: line 10: `ssh -t 10.0.1.40 "$remote_script" < <(printf '%s\0' "$password")'
First: Because it exposes plaintext passwords to the remote system (where they can be read by an attacker using diagnostic tools such as strace or sysdig), this is less secure than correctly using the NOPASSWD: flag in sudoers. If your security team aren't absolute idiots, they'll approve a policy exemption (perhaps with some appropriate controls, such as having a dedicated account with access to a setuid binary specific to the command being run, with authentication to that account being performed via public key authentication w/ the private key stored encrypted) rather than approving use of this hack.
Second: Here's your hack.
## define code to be run on the remote system
remote_script='sudo -S remote_command_here'
## local system
# on the local machine: prompt the user for the password
read -r -p "Enter password for $host: " password
# ...and write the password, followed by a NUL delimiter, to stdin of ssh
ssh "$host" "$remote_script" < <(printf '%s\0' "$password")
Allright, this is not the final answer, but I think I'm getting close, with the great help of CharlesDuffy.
So far I can run the script without errors on a remote server, that already has the publickey of my source server. However the command I execute doesn't create a file as I tell it to on the remote system.
However the script seems to run and the password seems to be accepted by the remote system.
Also I have to change in the sudoers on the remote host the line "Defaults requiretty" to "Defaults !requiretty", else it will tell me that I need a TTY to run sudo.
#!/bin/bash
## define code to be run on the remote system
remote_script='sudo -S touch /elevatedfile'
## local system
# on the local machine: prompt the user for the password
read -r -p "Enter password for $host: " password
# ...and write the password, followed by a NUL delimiter, to stdin of ssh
ssh -T 10.0.1.40 "$remote_script" < <(printf '%s\0' "$password")
UPDATE: When I tail /var/log/secure on the remote host I get the following after executing the script, which seems like the password is not being accepted.
May 11 20:15:20 target sudo: pam_unix(sudo:auth): conversation failed
May 11 20:15:20 target sudo: pam_unix(sudo:auth): auth could not identify password for [worker]
May 11 20:15:20 target sshd[3634]: Received disconnect from 10.0.1.39: 11: disconnected by user
May 11 20:15:20 target sshd[3631]: pam_unix(sshd:session): session closed for user worker
What I see on the source server, from where I launch the script:
[worker#source ~]$ bash elevate.sh
Enter password for : abc123
[sudo] password for worker:
[worker#source ~]$
Just make a daemon or cron script running as root, that in turn will check for any new scripts in specified secure location (ie. DB that it only has READ access to), and if they exist, it will download and execute them.

Background shell script can't reach directories after ssh logout, even with nohup

I want to run a shell script in the background on a server machine and starts that shell script from an ssh connection. Even though I run the background process script with nohup, the background script fails due to an directory unreachable error as soon as I close my ssh connection (and no sooner).
runInBackground.sh:
#!/bin/bash
...
nohup ./run.sh > /dev/null 2> local/errorLog.txt < /dev/null &
run.sh:
#!/bin/bash
...
while [ true ] ; do
...
cd optaplanner-examples
mvn exec:exec // calls java process
cd ..
done
So when I run runInBackground.sh, everything works fine for hours, until I disconnect my ssh connection.
As soon as I log out, the errorlog.txt fills up with:
java.io.FileNotFoundException: /home/myUser/server/optaplanner-simple-benchmark-daemon/local/output/
./run.sh: line 64: /home/myUser/server/optaplanner-simple-benchmark-daemon/local/processed/failed_machineReassignmentBenchmarkConfig.xml: No such file or directory
fatal: Could not change back to '(unreachable)/server/optaplanner-simple-benchmark-daemon/local/optaplannerGitClone/optaplanner': No such file or directory
ls: cannot access /home/myUser/server/optaplanner-simple-benchmark-daemon/local/input: No such file or directory
ls: cannot access /home/myUser/server/optaplanner-simple-benchmark-daemon/local/input: No such file or directory
ls: cannot access /home/myUser/server/optaplanner-simple-benchmark-daemon/local/input: No such file or directory
... // 1000+ more of that ls error
(Full source code)
well, it's not necessarily an encrypted home directory, but likely it's an auto-mounted home directory (e.g over NFS or so). It's mounted upon session startup, and unmounted upon exit. An encrypted home dir is only one of the possible reasons to use such a technique.
The main question is what's the rule that determines whether a user needs home dir or not. I would expect that it could be an allocated pty. You could test if it's actually true by starting a non-interactive SSH session w/o a pseudo-terminal: ssh -T user#host ls /home/myUser/server. I could expect that in this case you won't get a proper directory listing.
Then I would use a program like screen to prolongate interactive session lifetime beyond SSH session limits.
The server might use some other mechanism to provide the home directory for interactive SSH sessions. E.g. monitor interactive sessions listed in utmp In this case you will need a program that would keep the record as long as you need for your service. Perhaps you could use an automatically re-established SSH session. For example I use the following systemd unit to automatically keep a ssh tunnel from one of my workstations in different private networks:
[Unit]
Description=A tunnel to SOME_HOST
PartOf=sshd.service
Requires=network.service
[Service]
ExecStart=/usr/bin/ssh -N -q -R 2222:localhost:22 SOME_HOST
Restart=on-failure
RestartSec=5
User=tunnel
Group=tunnel
[Install]
WantedBy=sshd.service
WantedBy=network.service
When a failure occurs, systemd automatically restarts the unit and SSH session is re-established.
I always use the screen utility to run my scripts instead of nohup.
With screen your process will keep running in even your current ssh session times out or gets disconnected.
Use as follows -
apt-get install screen (On Debian based Systems)
OR
yum install screen (On RedHat based Systems)
To run your application and check the output live (provided your script file does not start a background process and it outputs to the stdout and/or stderr
cd your_app_directory_path
screen ./your_script.sh
Once you are done and want to leave (without stopping the process), use CTRL + A + D to detach the screen.
To check your processes which are run using the screen utility -
screen -r
to reattach a running process
screen -r <screen id or name>
Hope this was useful.
One workaround is to use screen to keep the ssh session open. You can use screen -r to reconnect to the session if you get disconnected.

OpenSSH on Cygwin

I have a Linux box (Ubuntu Server 13.04) which needs to run a job on a Windows 7 box (with cygwin installed) under a specific user's account. I have set up a password-less login to access the Windows machine through openSSH.
The problem I face is the following: when I manually ssh into the Win7 machine and launch the job everything is fine. However, when I launch the job using ssh winuser#winmachine command, I end up connecting to the Windows machine under the privileged sshd user 'cyg_server':
$ whoami
linuxuser
$ ssh winuser#Win7
$ whoami
winuser
$ exit
$ ssh winuser#Win7 "whoami; exit"
cyg_server
>> This should be 'winuser' too.
Why could this be happening? I have tried running ssh-host-config again to no avail. I don't see what parameters might influence this in sshd_config either.
Any help is greatly appreciated!
I had similar issues when I was connecting to a Cygwin machine using SSH. I used to have no problems logging on until one day I noticed that my path wasn't set correctly. I spent ages recreating the configuration files with ssh-host-config only to find my answer in the man page for ssh:
If command is specified, it is executed on the remote host instead of
a login shell.
The problem was the alias I had used to connect to the machine had been changed to connect to a screen session automatically (screen -DR). That meant that if there wasn't already a screen session to attach to, screen was not being run as a child process of a user login shell and not inheriting any of the relevant user environment.
When you provide a command as an argument to ssh, the resulting command is run as a process started by cyg_server. Ensuring the SSH command is being run as part of a login shell should do what you want:
ssh winuser#Win7 "bash -l -c 'whoami; exit'"
Explanation (from the bash man page):
-c string If the -c option is present, then commands are read from string.
-l Make bash act as if it had been invoked as a login shell.

How to send data to local clipboard from a remote SSH session

Borderline ServerFault question, but I'm programming some shell scripts, so I'm trying here first :)
Most *nixes have a command that will let you pipe/redirect output to the local clipboard/pasteboard, and retrieve from same. On OS X these commands are
pbcopy, pbpaste
Is there anyway to replicate this functionality while SSHed into another server? That is,
I'm using Computer A.
I open a terminal window
I SSH to Computer B
I run a command on Computer B
The output of Computer B is redirected or automatically copied to Computer A's clipboard.
And yes, I know I could just (shudder) use my mouse to select the text from the command, but I've gotten so used to the workflow of pipping output directly to the clipboard that I want the same for my remote sessions.
Code is useful, but general approaches are appreciated as well.
My favorite way is ssh [remote-machine] "cat log.txt" | xclip -selection c. This is most useful when you don't want to (or can't) ssh from remote to local.
Edit: on Cygwin ssh [remote-machine] "cat log.txt" > /dev/clipboard.
Edit: A helpful comment from nbren12:
It is almost always possible to setup a reverse ssh connection using SSH port forwarding. Just add RemoteForward 127.0.0.1:2222 127.0.0.1:22 to the server's entry in your local .ssh/config, and then execute ssh -p 2222 127.0.0.1 on the remote machine, which will then redirect the connection to the local machine. – nbren12
I'm resurrecting this thread because I've been looking for the same kind of solution, and I've found one that works for me. It's a minor modification to a suggestion from OSX Daily.
In my case, I use Terminal on my local OSX machine to connect to a linux server via SSH. Like the OP, I wanted to be able to transfer small bits of text from terminal to my local clipboard, using only the keyboard.
The essence of the solution:
commandThatMakesOutput | ssh desktop pbcopy
When run in an ssh session to a remote computer, this command takes the output of commandThatMakesOutput (e.g. ls, pwd) and pipes the output to the clipboard of the local computer (the name or IP of "desktop"). In other words, it uses nested ssh: you're connected to the remote computer via one ssh session, you execute the command there, and the remote computer connects to your desktop via a different ssh session and puts the text to your clipboard.
It requires your desktop to be configured as an ssh server (which I leave to you and google). It's much easier if you've set up ssh keys to facilitate fast ssh usage, preferably using a per-session passphrase, or whatever your security needs require.
Other examples:
ls | ssh desktopIpAddress pbcopy
pwd | ssh desktopIpAddress pbcopy
For convenience, I've created a bash file to shorten the text required after the pipe:
#!/bin/bash
ssh desktop pbcopy
In my case, i'm using a specially named key
I saved it with the file name cb (my mnemonic (ClipBoard). Put the script somewhere in your path, make it executable and voila:
ls | cb
Found a great solution that doesn't require a reverse ssh connection!
You can use xclip on the remote host, along with ssh X11 forwarding & XQuartz on the OSX system.
To set this up:
Install XQuartz (I did this with soloist + pivotal_workstation::xquartz recipe, but you don't have to)
Run XQuartz.app
Open XQuartz Preferences (+,)
Make sure "Enable Syncing" and "Update Pasteboard when CLIPBOARD changes" are checked
ssh -X remote-host "echo 'hello from remote-host' | xclip -selection clipboard"
Reverse tunnel port on ssh server
All the existing solutions either need:
X11 on the client (if you have it, xclip on the server works great) or
the client and server to be in the same network (which is not the case if you're at work trying to access your home computer).
Here's another way to do it, though you'll need to modify how you ssh into your computer.
I've started using this and it's nowhere near as intimidating as it looks so give it a try.
Client (ssh session startup)
ssh username#server.com -R 2000:localhost:2000
(hint: make this a keybinding so you don't have to type it)
Client (another tab)
nc -l 2000 | pbcopy
Note: if you don't have pbcopy then just tee it to a file.
Server (inside SSH session)
cat some_useful_content.txt | nc localhost 2000
Other notes
Actually even if you're in the middle of an ssh session there's a way to start a tunnel but i don’t want to scare people away from what really isn’t as bad as it looks. But I'll add the details later if I see any interest
There are various tools to access X11 selections, including xclip and XSel. Note that X11 traditionally has multiple selections, and most programs have some understanding of both the clipboard and primary selection (which are not the same). Emacs can work with the secondary selection too, but that's rare, and nobody really knows what to do with cut buffers...
$ xclip -help
Usage: xclip [OPTION] [FILE]...
Access an X server selection for reading or writing.
-i, -in read text into X selection from standard input or files
(default)
-o, -out prints the selection to standard out (generally for
piping to a file or program)
-l, -loops number of selection requests to wait for before exiting
-d, -display X display to connect to (eg localhost:0")
-h, -help usage information
-selection selection to access ("primary", "secondary", "clipboard" or "buffer-cut")
-noutf8 don't treat text as utf-8, use old unicode
-version version information
-silent errors only, run in background (default)
-quiet run in foreground, show what's happening
-verbose running commentary
Report bugs to <astrand#lysator.liu.se>
$ xsel -help
Usage: xsel [options]
Manipulate the X selection.
By default the current selection is output and not modified if both
standard input and standard output are terminals (ttys). Otherwise,
the current selection is output if standard output is not a terminal
(tty), and the selection is set from standard input if standard input
is not a terminal (tty). If any input or output options are given then
the program behaves only in the requested mode.
If both input and output is required then the previous selection is
output before being replaced by the contents of standard input.
Input options
-a, --append Append standard input to the selection
-f, --follow Append to selection as standard input grows
-i, --input Read standard input into the selection
Output options
-o, --output Write the selection to standard output
Action options
-c, --clear Clear the selection
-d, --delete Request that the selection be cleared and that
the application owning it delete its contents
Selection options
-p, --primary Operate on the PRIMARY selection (default)
-s, --secondary Operate on the SECONDARY selection
-b, --clipboard Operate on the CLIPBOARD selection
-k, --keep Do not modify the selections, but make the PRIMARY
and SECONDARY selections persist even after the
programs they were selected in exit.
-x, --exchange Exchange the PRIMARY and SECONDARY selections
X options
--display displayname
Specify the connection to the X server
-t ms, --selectionTimeout ms
Specify the timeout in milliseconds within which the
selection must be retrieved. A value of 0 (zero)
specifies no timeout (default)
Miscellaneous options
-l, --logfile Specify file to log errors to when detached.
-n, --nodetach Do not detach from the controlling terminal. Without
this option, xsel will fork to become a background
process in input, exchange and keep modes.
-h, --help Display this help and exit
-v, --verbose Print informative messages
--version Output version information and exit
Please report bugs to <conrad#vergenet.net>.
In short, you should try xclip -i/xclip -o or xclip -i -sel clip/xclip -o -sel clip or xsel -i/xsel -o or xsel -i -b/xsel -o -b, depending on what you want.
If you use iTerm2 on the Mac, there is an easier way. This functionality is built into iTerm2's Shell Integration capabilities via the it2copy command:
Usage: it2copy
Copies to clipboard from standard input
it2copy filename
Copies to clipboard from file
To make it work, choose iTerm2-->Install Shell Integration menu item while logged into the remote host, to install it to your own account. Once that is done, you'll have access to it2copy, as well as a bunch of other aliased commands that provide cool functionality.
The other solutions here are good workarounds but this one is so painless in comparison.
This is my solution based on SSH reverse tunnel, netcat and xclip.
First create script (eg. clipboard-daemon.sh) on your workstation:
#!/bin/bash
HOST=127.0.0.1
PORT=3333
NUM=`netstat -tlpn 2>/dev/null | grep -c " ${HOST}:${PORT} "`
if [ $NUM -gt 0 ]; then
exit
fi
while [ true ]; do
nc -l ${HOST} ${PORT} | xclip -selection clipboard
done
and start it in background.
./clipboard-daemon.sh&
It will start nc piping output to xclip and respawning process after receiving portion of data
Then start ssh connection to remote host:
ssh user#host -R127.0.0.1:3333:127.0.0.1:3333
While logged in on remote box, try this:
echo "this is test" >/dev/tcp/127.0.0.1/3333
then try paste on your workstation
You can of course write wrapper script that starts clipboard-daemon.sh first and then ssh session. This is how it works for me. Enjoy.
Allow me to add a solution that if I'm not mistaken was not suggested before.
It does not require the client to be exposed to the internet (no reverse connections), nor does it use any xlibs on the server and is implemented completely using ssh's own capabilities (no 3rd party bins)
It involves:
Opening a connection to the remote host, then creating a fifo file on it and waiting on that fifo in parallel (same actual TCP connection for everything).
Anything you echo to that fifo file ends up in your local clipboard.
When the session is done, remove the fifo file on the server and cleanly terminate the connections together.
The solution utilizes ssh's ControlMaster functionality to use just one TCP connection for everything so it will even support hosts that require a password to login and prompt you for it just once.
Edit: as requested, the code itself:
Paste the following into your bashrc and use sshx host to connect.
On the remote machine echo SOMETHING > ~/clip and hopefully, SOMETHING will end up in the local host's clipboard.
You will need the xclip utility on your local host.
_dt_term_socket_ssh() {
ssh -oControlPath=$1 -O exit DUMMY_HOST
}
function sshx {
local t=$(mktemp -u --tmpdir ssh.sock.XXXXXXXXXX)
local f="~/clip"
ssh -f -oControlMaster=yes -oControlPath=$t $# tail\ -f\ /dev/null || return 1
ssh -S$t DUMMY_HOST "bash -c 'if ! [ -p $f ]; then mkfifo $f; fi'" \
|| { _dt_term_socket_ssh $t; return 1; }
(
set -e
set -o pipefail
while [ 1 ]; do
ssh -S$t -tt DUMMY_HOST "cat $f" 2>/dev/null | xclip -selection clipboard
done &
)
ssh -S$t DUMMY_HOST \
|| { _dt_term_socket_ssh $t; return 1; }
ssh -S$t DUMMY_HOST "rm $f"
_dt_term_socket_ssh $t
}
More detailed explanation is on my website:
https://xicod.com/2021/02/09/clipboard-over-ssh.html
The simplest solution of all, if you're on OS X using Terminal and you've been ssh'ing around in a remote server and wish to grab the results of a text file or a log or a csv, simply:
1) Cmd-K to clear the output of the terminal
2) cat <filename> to display the contents of the file
3) Cmd-S to save the Terminal Output
You'll have the manually remove the first line and last line of the file, but this method is a bit simpler than relying on other packages to be installed, "reverse tunnels" and trying to have a static IP, etc.
This answer develops both upon the chosen answer by adding more security.
That answer discussed the general form
<command that makes output> | \
ssh <user A>#<host A> <command that maps stdin to clipboard>
Where security may be lacking is in the ssh permissions allowing <user B> on host B> to ssh into host A and execute any command.
Of course B to A access may already be gated by an ssh key, and it may even have a password. But another layer of security can restrict the scope of allowable commands that B can execute on A, e.g. so that rm -rf / cannot be called. (This is especially important when the ssh key doesn't have a password.)
Fortunately, ssh has a built-in feature called command restriction or forced command. See ssh.com, or
this serverfault.com question.
The solution below shows the general form solution along with ssh command restriction enforced.
Example Solution with command restriction added
This security enhanced solution follows the general form - the call from the ssh session on host-B is simply:
cat <file> | ssh <user-A>#<host A> to_clipboard
The rest of this shows the setup to get that to work.
Setup of ssh command restriction
Suppose the user account on B is user-B, and B has an ssh key id-clip, that has been created in the usual way (ssh-keygen).
Then in user-A's ssh directory there is a file
/home/user-A/.ssh/authorized_keys
that recognizes the key id-clip and allows ssh connection.
Usually the contents of each line authorized_keys is exactly the public key being authorized, e.g., the contents of id-clip.pub.
However, to enforce command restriction that public key content is prepended (on the same line) by the command to be executed.
In our case:
command="/home/user-A/.ssh/allowed-commands.sh id-clip",no-agent-forwarding,no-port-forwarding,no-user-rc,no-x11-forwarding,no-pty <content of file id-clip.pub>
The designated command "/home/user-A/.ssh/allowed-commands.sh id-clip", and only that designated command, is executed whenever key id-clip is used initiate an ssh connection to host-A - no matter what command is written the ssh command line.
The command indicates a script file allowed-commands.sh, and the contents of that that script file is
#/bin/bash
#
# You can have only one forced command in ~/.ssh/authorized_keys. Use this
# wrapper to allow several commands.
Id=${1}
case "$SSH_ORIGINAL_COMMAND" in
"to-clipboard")
notify-send "ssh to-clipboard, from ${Id}"
cat | xsel --display :0 -i -b
;;
*)
echo "Access denied"
exit 1
;;
esac
The original call to ssh on machine B was
... | ssh <user-A>#<host A> to_clipboard
The string to-clipboard is passed to allowed-commands.sh by the environment variable SSH_ORIGINAL_COMMAND.
Addition, we have passed the name of the key, id-clip, from the line in authorized_keyswhich is only accessed by id-clip.
The line
notify-send "ssh to-clipboard, from ${Id}"
is just a popup messagebox to let you know the clipboard is being written - that's probably a good security feature too. (notify-send works on Ubuntu 18.04, maybe not others).
In the line
cat | xsel --display :0 -i -b
the parameter --display :0 is necessary because the process doesn't have it's own X display with a clipboard,
so it must be specificied explicitly. This value :0 happens to work on Ubuntu 18.04 with Wayland window server. On other setups it might not work. For a standard X server this answer might help.
host-A /etc/ssh/sshd_config parameters
Finally a few parameters in /etc/ssh/sshd_config on host A that should be set to ensure permission to connect, and permission to use ssh-key only without password:
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
AllowUsers user-A
To make the sshd server re-read the config
sudo systemctl restart sshd.service
or
sudo service sshd.service restart
conclusion
It's some effort to set it up, but other functions besides to-clipboard can be constructed in parallel the same framework.
Not a one-liner, but requires no extra ssh.
install netcat if necessary
use termbin: cat ~/some_file.txt | nc termbin.com 9999. This will copy the output to the termbin website and prints the URL to your output.
visit that url from your computer, you get your output
Of course, do not use it for sensitive content.
#rhileighalmgren solution is good, but pbcopy will annoyingly copy last "\n" character, I use "head" to strip out last character to prevent this:
#!/bin/bash
head -c -1 | ssh desktop pbcopy
My full solution is here : http://taylor.woodstitch.com/linux/copy-local-clipboard-remote-ssh-server/
Far Manager Linux port supports synchronizing clipboard between local and remote host. You just open local far2l, do "ssh somehost" inside, run remote far2l in that ssh session and get remote far2l working with your local clipboard.
It supports Linux, *BSD and OS X; I made a special putty build to utilize this functionality from windows also.
For anyone googling their way to this:
The best solution in this day and age seem to be lemonade
Various solutions is also mentioned in the neovim help text for clipboard-tool
If you're working over e.g. a pod in a Kubernetes cluster and not direct SSH, so that there is no way for your to do a file transfer, you could use cat and then save the terminal output as text. For example in macOS you can do Shell -> Export as text.

Resources