24

I'm currently setting up a fairly complex bash configuration which shall be used on multiple machines. I try to find out if it is possible to determine whether I'm logged in via SSH or on a local machine. This way I could, for instance, set some aliases depending on that fact. Like aliasing halt to restart since stopping a remote server might not be the best thing to do.

What I know so far is, that the environment variable SSH_CLIENT is set when I logged in via ssh. Unfortunately, this variable is discarded when I start a super user shell with sudo -s. I also know that I can pass a parameter to sudo that instructs sudo to copy all my environment variables to the new shell environment, but if I don't want to do this, is there an other way?

t6d
  • 527
  • 2
  • 5
  • 12

12 Answers12

17

You could use "w" or "who" command output. When you connect over ssh, they'll show your source IP.

facha
  • 1,368
  • 2
  • 18
  • 26
  • Genius idea - Just out of curiosity, if I'm logged in from multiple machines (really a rare situation), how can determine which line of "w " refers to my current shell? Can I somehow use the TTY field for that? – t6d Oct 05 '10 at 13:41
  • 1
    Make educated guesses. For example, run `ps afx` and the TTY for the shell not running `ps` will be the other login. – Warner Oct 05 '10 at 13:52
  • 6
    Use `who am i`. – Paul Tomblin Oct 05 '10 at 13:53
  • @Paul - Awesome! :) If I now only new, how to extract the hostname from the resulting string. – t6d Oct 05 '10 at 14:57
  • 1
    "uname -n" will give you the hostname – Hubert Kario Oct 05 '10 at 16:15
  • 2
    The question appears to be more related to extract it from `who am i`, so that you can determine from there if you're SSHing or not. This works: `hostname=$(who am i | cut -f2 -d\( | cut -f1 -d:)` – blueyed Apr 07 '11 at 00:34
  • @facha: not if you're using a grsec kernel – petrus Apr 07 '11 at 22:54
  • @Hubert: "uname -n" gives you the hostname you are logged into, not the hostname you came from. @blueyed has it right – Daniel Lawson Apr 07 '11 at 23:01
  • 1
    or you could be fun and use sed :) as in: `who am i | sed 's/.*(\(.*\))/\1/g'` – photoionized Apr 07 '11 at 23:04
  • 4
    @PaulTomblin Actually, you can use `who` with any two extra arguments. `who am i` is the same as `who is me` or `who is awesome` or `who potato potato`. A fact I found a little interesting. – kirkpatt Apr 29 '16 at 20:08
13

Here is a great answer I found on unix.stackexchange:


  • If one of the variables SSH_CLIENT or SSH_TTY is defined, it's an ssh session.
  • The login shell's parent process can be checked with ps -o comm= -p $PPID. If it is sshd, it's an ssh session.
if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then
  SESSION_TYPE=remote/ssh
else
  case $(ps -o comm= -p $PPID) in
    sshd|*/sshd) SESSION_TYPE=remote/ssh;;
  esac
fi
Nicole
  • 231
  • 2
  • 4
6

You could add SSH_* to env_keep in sudoers so that this can be detected while switched to the other user.

Ignacio Vazquez-Abrams
  • 45,939
  • 6
  • 79
  • 84
4

I think you want to rethink the way you're thinking of the problem. The question isn't "am I logged in via SSH, because I want to turn off certain commands." It's "am I logged in at the console, because then I will enable certain commands."

Andy Lester
  • 740
  • 5
  • 16
4

If you want to know if you bash shell is directly a child process of sshd (not n>1 layers deep) you can

cat /proc/$PPID/status | head -1 | cut -f2

it should give you sshd or whatever is the parent process name of your current shell.

rhlee
  • 141
  • 2
3

Yes, as others noted, the info is in the presence of your IP in parentheses in the output of who am i.

You can use Bash regular expressions to detect it:

if [[ $(who am i) =~ \([0-9\.]+\)$ ]]; then echo SSH; else echo no; fi
mivk
  • 4,004
  • 3
  • 37
  • 32
2

All of the other answers work if you are at the first level of login. But if, once login, you run 'su' or 'sudo' (in my case, to switch to a user account without shell for security reasons, I had to run: sudo su - <userid> -s /bin/bash -l), their solution fail.

Following is a universal solution; using pstree, you check for sshd as a parent.

if pstree -p | egrep --quiet --extended-regexp ".*sshd.*\($$\)"; then
  echo "I am remote."
else
  echo "I am local."
fi

Here is the output of the egrep, when --quiet is removed. It shows the whole hierarchy that matches if one is connected remotely.

   |            |-sshd(18599)---sshd(18603)---bash(18604)---sudo(18823)---bash(18824)-+-egrep(22417)
Hans Deragon
  • 133
  • 4
1

If you're on a system that uses logind, then loginctl will tell you if the session is remote:

$ loginctl show-session $XDG_SESSION_ID -P Remote
yes

However, there are cases that XDG_SESSION_ID is not defined (including after sudo), so I wrote a script to check more things. It tries loginctl, ssh, remote X display and if all fails, connects to logind via D-Bus and attempts to discover the current session by using the current process PID as a key:

#!/usr/bin/env python3
from os import environ, getpid, system
from sys import argv, stderr

from dbus import SystemBus
from dbus.exceptions import DBusException

DEBUG=0

LOGIND = ('org.freedesktop.login1', '/org/freedesktop/login1')
MANAGER = '%s.Manager' % LOGIND[0]
SESSION = '%s.Session' % LOGIND[0]
SESSION_NOT_FOUND = '%s.NoSessionForPID' % LOGIND[0]
REMOTE = 'Remote'
LOGINCTL = 'loginctl show-session %s -P %s'
PROPERTIES = 'org.freedesktop.DBus.Properties'
DISPLAY = 'DISPLAY'
SSH='SSH_CLIENT'

def debug(msg):
    if DEBUG: print(msg, file=stderr)

def try_loginctl():
    debug('Trying loginctl...')
    try:
        r = system(LOGINCTL % (environ['XDG_SESSION_ID'], REMOTE))
        # exit early if loginctl prints the result by itself
        if r == 0: raise SystemExit
    except KeyError:
        pass

    return False

def try_ssh():
    debug('Trying ssh...')
    return bool(environ.get(SSH))

def try_x():
    debug('Trying X...')
    try:
        display = environ[DISPLAY]
        return ':' in display and len(display.split(':', 1)[0]) > 0
    except KeyError:
        return False
        
def try_dbus():
    debug('Trying dbus...')
    bus = SystemBus()
    logind = bus.get_object(*LOGIND)

    try:
        pid = getpid()
        session_path = logind.GetSessionByPID(pid, dbus_interface=MANAGER)
    except DBusException as e:
        if e.get_dbus_name() == SESSION_NOT_FOUND:
            return False
        else:
            raise

    session = bus.get_object(LOGIND[0], session_path)
    remote = session.Get(SESSION, REMOTE, dbus_interface=PROPERTIES)

    return bool(remote)

def main():
    global DEBUG

    if '-d' in argv or '--debug' in argv:
        DEBUG=1

    remote = try_loginctl() or try_ssh() or try_x() or try_dbus()
    print(remote and 'yes' or 'no')

main()
hdante
  • 111
  • 3
1

I've came up with the following, based on tips from others here.

It uses a variable for caching - I am using it in my shell theme.

is_ssh() {
    (( $+SSH_CLIENT )) && return
    if ! (( $+_ZSH_IS_SSH )); then
        # "who am i" displays current user from utmp(5).  This will be empty for
        # a "normal" terminal.  With Konsole, it is ":0" for display :0,
        # for ssh it is the hostname and with tmux sth like "tmux(PID).ID".
        local whoami="$(who am i)"}
        local host="${whoami#*\(*}"
        [[ -n $host && $host != tmux* && $host != :* ]]
        _ZSH_IS_SSH=$?
    fi
    return $_ZSH_IS_SSH
}

Source: is_ssh in https://github.com/blueyed/oh-my-zsh/blob/master/themes/blueyed.zsh-theme#L51-63.

blueyed
  • 773
  • 8
  • 13
0

If you do not use the X11Forward feature. Maybe the most simple way to check is through

echo $DISPLAY

If you logging through SSH without X11Forward, it is empty normally, otherwise, like :1

0

Look for your shell's parent cmdline and recurse. Maybe something like the following:

#!/usr/bin/env bash

## Find out how I'm logged in
# Tested on RHEL5.5

PD=${1:-$$}
ME=`basename $0`

## Read the shell's PPID
PAR=`ps --no-headers -p $PD -o ppid`

## CMDLINE can contain stuff like the following:
# /sbin/getty-838400tty4 // logged in at a console
# gnome-terminal         // logged in Gnome Terminal
# -bash                  // in a subshell
# su-                    // we became another user using su
# sshd: jc@pts/1         // logged in over ssh
# login                  // logged in terminal or serial device

eval `python - << __EOF__
import re
f = open("/proc/${PAR}/cmdline", 'r')
ln = f.readline()
if re.search(r'^ssh', ln): 
    print "echo Logged in via ssh"
if re.search(r'getty.*?tty', ln):
    print "echo Logged in console"
if re.search("gnome-terminal", ln):
    print "echo Logged in Gnome"
if re.search(r'^login', ln):
    print "echo Logged in console"
if re.search(r'^-?bash', ln) or re.search(r'^su', ln): 
    print "./$ME $PAR"
f.close()
__EOF__
`

Edited to make it actually work :)

Justin
  • 141
  • 3
0

Please keep in mind that this answer is very, very Linux specific.

parent_pid=$$
while [[ -z "${tty_bits-}" || $tty_bits -ne 0 ]]; do
  read initiator_name parent_pid tty_bits < <(
    awk '{ print substr($2, 2, length($2) - 2) " " $4 " " $7 }' /proc/$parent_pid/stat
  )
done

echo $initiator_name

This makes a key assumption: the login process won't have a controlling TTY; you probably want to check if you have a controlling TTY before running this code (which, based upon your requirements, probably is a safe bet, anyway).

The code iterates upwards through the process tree, until it find the process that has no controlling TTY. $initiator_name will be the name of this process ("sshd", for example).