1235368Sgnn#!/usr/bin/sh
2235368Sgnn#
3235368Sgnn# shellsnoop - A program to print read/write details from shells,
4235368Sgnn#	       such as keystrokes and command outputs.
5235368Sgnn#	       Written using DTrace (Solaris 10 3/05).
6235368Sgnn#
7235368Sgnn# This program sounds somewhat dangerous (snooping keystrokes), but is
8235368Sgnn# no more so than /usr/bin/truss, and both need root or dtrace privileges to
9235368Sgnn# run. In fact, less dangerous, as we only print visible text (not password
10235368Sgnn# text, for example). Having said that, it goes without saying that this
11235368Sgnn# program shouldn't be used for breeching privacy of other users.
12235368Sgnn#
13235368Sgnn# This was written as a tool to demonstrate the capabilities of DTrace.
14235368Sgnn#
15235368Sgnn# $Id: shellsnoop 19 2007-09-12 07:47:59Z brendan $
16235368Sgnn#
17235368Sgnn# USAGE:	shellsnoop [-hqsv] [-p PID] [-u UID]
18235368Sgnn#
19235368Sgnn#		-q		# quiet, only print data
20235368Sgnn#		-s		# include start time, us
21235368Sgnn#		-v		# include start time, string
22235368Sgnn#		-p PID		# process ID to snoop
23235368Sgnn#		-u UID		# user ID to snoop
24235368Sgnn#  eg,
25235368Sgnn#		shellsnoop		# default output
26235368Sgnn#		shellsnoop -v		# human readable timestamps
27235368Sgnn#		shellsnoop -p 1892	# snoop this PID only
28235368Sgnn#		shellsnoop -qp 1892	# watch this PID data only
29235368Sgnn# 	
30235368Sgnn# FIELDS:
31235368Sgnn#		UID		User ID
32235368Sgnn#		PID		process ID
33235368Sgnn#		PPID		parent process ID
34235368Sgnn#		COMM		command name
35235368Sgnn#		DIR		direction (R read, W write)
36235368Sgnn#		TEXT		text contained in the read/write
37235368Sgnn#		TIME		timestamp for the command, us
38235368Sgnn#		STRTIME		timestamp for the command, string
39235368Sgnn#
40235368Sgnn# SEE ALSO: ttywatcher
41235368Sgnn#
42235368Sgnn# COPYRIGHT: Copyright (c) 2005 Brendan Gregg.
43235368Sgnn#
44235368Sgnn# CDDL HEADER START
45235368Sgnn#
46235368Sgnn#  The contents of this file are subject to the terms of the
47235368Sgnn#  Common Development and Distribution License, Version 1.0 only
48235368Sgnn#  (the "License").  You may not use this file except in compliance
49235368Sgnn#  with the License.
50235368Sgnn#
51235368Sgnn#  You can obtain a copy of the license at Docs/cddl1.txt
52235368Sgnn#  or http://www.opensolaris.org/os/licensing.
53235368Sgnn#  See the License for the specific language governing permissions
54235368Sgnn#  and limitations under the License.
55235368Sgnn#
56235368Sgnn# CDDL HEADER END
57235368Sgnn#
58235368Sgnn# Author: Brendan Gregg  [Sydney, Australia]
59235368Sgnn#
60235368Sgnn# 28-Mar-2004	Brendan Gregg	Created this.
61235368Sgnn# 21-Jan-2005	   "	  "	Wrapped in sh to provide options.
62235368Sgnn# 30-Nov-2005	   "	  "	Fixed trailing buffer text bug.
63235368Sgnn# 30-Nov-2005	   "	  "	Fixed sh no keystroke text in quiet bug.
64235368Sgnn# 30-Nov-2005	   "	  "	Last update.
65235368Sgnn# 
66235368Sgnn
67235368Sgnn
68235368Sgnn##############################
69235368Sgnn# --- Process Arguments ---
70235368Sgnn#
71235368Sgnnopt_pid=0; opt_uid=0; opt_time=0; opt_timestr=0; opt_quiet=0; opt_debug=0
72235368Sgnnfilter=0; pid=0; uid=0
73235368Sgnn
74235368Sgnnwhile getopts dhp:qsu:v name
75235368Sgnndo
76235368Sgnn	case $name in
77235368Sgnn	d)	opt_debug=1 ;;
78235368Sgnn	p)	opt_pid=1; pid=$OPTARG ;;
79235368Sgnn	q)	opt_quiet=1 ;;
80235368Sgnn	s)	opt_time=1 ;;
81235368Sgnn	u)	opt_uid=1; uid=$OPTARG ;;
82235368Sgnn	v)	opt_timestr=1 ;;
83235368Sgnn	h|?)	cat <<-END >&2
84235368Sgnn		USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
85235368Sgnn		       shellsnoop		# default output
86235368Sgnn		                -q		# quiet, only print data
87235368Sgnn		                -s		# include start time, us
88235368Sgnn		                -v		# include start time, string
89235368Sgnn		                -p PID		# process ID to snoop
90235368Sgnn		                -u UID		# user ID to snoop
91235368Sgnn		END
92235368Sgnn		exit 1
93235368Sgnn	esac
94235368Sgnndone
95235368Sgnn
96235368Sgnnif [ $opt_quiet -eq 1 ]; then
97235368Sgnn	opt_time=0; opt_timestr=0
98235368Sgnnfi
99235368Sgnnif [ $opt_pid -eq 1 -o $opt_uid -eq 1 ]; then
100235368Sgnn	filter=1
101235368Sgnnfi
102235368Sgnn
103235368Sgnn
104235368Sgnn#################################
105235368Sgnn# --- Main Program, DTrace ---
106235368Sgnn#
107235368Sgnndtrace -n '
108235368Sgnn /*
109235368Sgnn  * Command line arguments
110235368Sgnn  */
111235368Sgnn inline int OPT_debug 	= '$opt_debug';
112235368Sgnn inline int OPT_quiet 	= '$opt_quiet';
113235368Sgnn inline int OPT_pid 	= '$opt_pid';
114235368Sgnn inline int OPT_uid 	= '$opt_uid';
115235368Sgnn inline int OPT_time 	= '$opt_time';
116235368Sgnn inline int OPT_timestr	= '$opt_timestr';
117235368Sgnn inline int FILTER 	= '$filter';
118235368Sgnn inline int PID 	= '$pid';
119235368Sgnn inline int UID 	= '$uid';
120235368Sgnn 
121235368Sgnn #pragma D option quiet
122235368Sgnn #pragma D option switchrate=20hz
123235368Sgnn 
124235368Sgnn /*
125235368Sgnn  * Print header
126235368Sgnn  */
127235368Sgnn dtrace:::BEGIN /OPT_time == 1/
128235368Sgnn { 
129235368Sgnn 	printf("%-14s ","TIME");
130235368Sgnn }
131235368Sgnn dtrace:::BEGIN /OPT_timestr == 1/
132235368Sgnn { 
133235368Sgnn 	printf("%-20s ","STRTIME");
134235368Sgnn }
135235368Sgnn dtrace:::BEGIN /OPT_quiet == 0/
136235368Sgnn {
137235368Sgnn	printf("%5s %5s %8s %3s  %s\n", "PID", "PPID", "CMD", "DIR", "TEXT");
138235368Sgnn }
139235368Sgnn
140235368Sgnn /*
141235368Sgnn  * Remember this PID is a shell child
142235368Sgnn  */
143235368Sgnn syscall::exec:entry, syscall::exece:entry
144235368Sgnn /execname == "sh"   || execname == "ksh"  || execname == "csh"  || 
145235368Sgnn  execname == "tcsh" || execname == "zsh"  || execname == "bash"/
146235368Sgnn {
147235368Sgnn	child[pid] = 1;
148235368Sgnn 
149235368Sgnn	/* debug */
150235368Sgnn	this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
151235368Sgnn	OPT_debug == 1 ? printf("PID %d CMD %s started. (%s)\n",
152235368Sgnn	    pid, execname, stringof(this->parent)) : 1;
153235368Sgnn }
154235368Sgnn syscall::exec:entry, syscall::exece:entry
155235368Sgnn /(OPT_pid == 1 && PID != ppid) || (OPT_uid == 1 && UID != uid)/
156235368Sgnn {
157235368Sgnn	/* forget if filtered */
158235368Sgnn	child[pid] = 0;
159235368Sgnn }
160235368Sgnn
161235368Sgnn /*
162235368Sgnn  * Print shell keystrokes
163235368Sgnn  */
164235368Sgnn syscall::write:entry, syscall::read:entry
165235368Sgnn /(execname == "sh"   || execname == "ksh"  || execname == "csh"  ||
166235368Sgnn  execname == "tcsh" || execname == "zsh"  || execname == "bash")
167235368Sgnn  && (arg0 >= 0 && arg0 <= 2)/
168235368Sgnn {
169235368Sgnn	self->buf = arg1;
170235368Sgnn }
171235368Sgnn syscall::write:entry, syscall::read:entry
172235368Sgnn /(OPT_pid == 1 && PID != pid) || (OPT_uid == 1 && UID != uid)/
173235368Sgnn {
174235368Sgnn	self->buf = 0;
175235368Sgnn }
176235368Sgnn syscall::write:return, syscall::read:return
177235368Sgnn /self->buf && child[pid] == 0 && OPT_time == 1/
178235368Sgnn {
179235368Sgnn 	printf("%-14d ", timestamp/1000);
180235368Sgnn }
181235368Sgnn syscall::write:return, syscall::read:return
182235368Sgnn /self->buf && child[pid] == 0 && OPT_timestr == 1/
183235368Sgnn {
184235368Sgnn	printf("%-20Y ", walltimestamp);
185235368Sgnn }
186235368Sgnn syscall::write:return, syscall::read:return
187235368Sgnn /self->buf && child[pid] == 0 && OPT_quiet == 0/
188235368Sgnn {
189235368Sgnn	this->text = (char *)copyin(self->buf, arg0);
190235368Sgnn	this->text[arg0] = '\'\\0\'';
191235368Sgnn 
192235368Sgnn	printf("%5d %5d %8s %3s  %s\n", pid, curpsinfo->pr_ppid, execname, 
193235368Sgnn	    probefunc == "read" ? "R" : "W", stringof(this->text));
194235368Sgnn }
195235368Sgnn syscall::write:return
196235368Sgnn /self->buf && child[pid] == 0 && OPT_quiet == 1/
197235368Sgnn {
198235368Sgnn	this->text = (char *)copyin(self->buf, arg0);
199235368Sgnn	this->text[arg0] = '\'\\0\'';
200235368Sgnn	printf("%s", stringof(this->text));
201235368Sgnn }
202235368Sgnn syscall::read:return
203235368Sgnn /self->buf && execname == "sh" && child[pid] == 0 && OPT_quiet == 1/
204235368Sgnn {
205235368Sgnn	this->text = (char *)copyin(self->buf, arg0);
206235368Sgnn	this->text[arg0] = '\'\\0\'';
207235368Sgnn	printf("%s", stringof(this->text));
208235368Sgnn }
209235368Sgnn syscall::write:return, syscall::read:return
210235368Sgnn /self->buf && child[pid] == 0/
211235368Sgnn {
212235368Sgnn	self->buf = 0;
213235368Sgnn }
214235368Sgnn
215235368Sgnn /*
216235368Sgnn  * Print command output
217235368Sgnn  */
218235368Sgnn syscall::write:entry, syscall::read:entry
219235368Sgnn /child[pid] == 1 && (arg0 == 1 || arg0 == 2)/
220235368Sgnn {
221235368Sgnn	self->buf = arg1;
222235368Sgnn }
223235368Sgnn syscall::write:return, syscall::read:return
224235368Sgnn /self->buf && OPT_time == 1/
225235368Sgnn {
226235368Sgnn 	printf("%-14d ", timestamp/1000);
227235368Sgnn }
228235368Sgnn syscall::write:return, syscall::read:return
229235368Sgnn /self->buf && OPT_timestr == 1/
230235368Sgnn {
231235368Sgnn	printf("%-20Y ", walltimestamp);
232235368Sgnn }
233235368Sgnn syscall::write:return, syscall::read:return
234235368Sgnn /self->buf && OPT_quiet == 0/
235235368Sgnn {
236235368Sgnn	this->text = (char *)copyin(self->buf, arg0);
237235368Sgnn	this->text[arg0] = '\'\\0\'';
238235368Sgnn 
239235368Sgnn	printf("%5d %5d %8s %3s  %s", pid, curpsinfo->pr_ppid, execname,
240235368Sgnn	    probefunc == "read" ? "R" : "W", stringof(this->text));
241235368Sgnn 
242235368Sgnn	/* here we check if a newline is needed */
243235368Sgnn	this->length = strlen(this->text);
244235368Sgnn	printf("%s", this->text[this->length - 1] == '\'\\n\'' ? "" : "\n");
245235368Sgnn	self->buf = 0;
246235368Sgnn }
247235368Sgnn syscall::write:return, syscall::read:return
248235368Sgnn /self->buf && OPT_quiet == 1/
249235368Sgnn {
250235368Sgnn	this->text = (char *)copyin(self->buf, arg0);
251235368Sgnn	this->text[arg0] = '\'\\0\'';
252235368Sgnn	printf("%s", stringof(this->text));
253235368Sgnn	self->buf = 0;
254235368Sgnn }
255235368Sgnn
256235368Sgnn /*
257235368Sgnn  *  Cleanup
258235368Sgnn  */
259235368Sgnn syscall::rexit:entry
260235368Sgnn {
261235368Sgnn	child[pid] = 0;
262235368Sgnn
263235368Sgnn	/* debug */
264235368Sgnn	this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
265235368Sgnn	OPT_debug == 1 ? printf("PID %d CMD %s exited. (%s)\n",
266235368Sgnn	 pid, execname, stringof(this->parent)) : 1;
267235368Sgnn }
268235368Sgnn'
269