1#!/usr/bin/sh
2#
3# shellsnoop - A program to print read/write details from shells,
4#	       such as keystrokes and command outputs.
5#	       Written using DTrace (Solaris 10 3/05).
6#
7# This program sounds somewhat dangerous (snooping keystrokes), but is
8# no more so than /usr/bin/truss, and both need root or dtrace privileges to
9# run. In fact, less dangerous, as we only print visible text (not password
10# text, for example). Having said that, it goes without saying that this
11# program shouldn't be used for breeching privacy of other users.
12#
13# This was written as a tool to demonstrate the capabilities of DTrace.
14#
15# 30-Nov-2005, ver 0.92  	(check for newer versions)
16#
17# USAGE:	shellsnoop [-hqsv] [-p PID] [-u UID]
18#
19#		-q		# quiet, only print data
20#		-s		# include start time, us
21#		-v		# include start time, string
22#		-p PID		# process ID to snoop
23#		-u UID		# user ID to snoop
24#  eg,
25#		shellsnoop		# default output
26#		shellsnoop -v		# human readable timestamps
27#		shellsnoop -p 1892	# snoop this PID only
28#		shellsnoop -qp 1892	# watch this PID data only
29# 	
30# FIELDS:
31#		UID		User ID
32#		PID		process ID
33#		PPID		parent process ID
34#		COMM		command name
35#		DIR		direction (R read, W write)
36#		TEXT		text contained in the read/write
37#		TIME		timestamp for the command, us
38#		STRTIME		timestamp for the command, string
39#
40# SEE ALSO: ttywatcher
41#
42# CDDL HEADER START
43#
44#  The contents of this file are subject to the terms of the
45#  Common Development and Distribution License, Version 1.0 only
46#  (the "License").  You may not use this file except in compliance
47#  with the License.
48#
49#  You can obtain a copy of the license at Docs/cddl1.txt
50#  or http://www.opensolaris.org/os/licensing.
51#  See the License for the specific language governing permissions
52#  and limitations under the License.
53#
54# CDDL HEADER END
55#
56# Author: Brendan Gregg  [Sydney, Australia]
57#
58# 28-Mar-2004	Brendan Gregg	Created this.
59# 21-Jan-2005	   "	  "	Wrapped in sh to provide options.
60# 30-Nov-2005	   "	  "	Fixed trailing buffer text bug.
61# 30-Nov-2005	   "	  "	Fixed sh no keystroke text in quiet bug.
62# 
63
64
65##############################
66# --- Process Arguments ---
67#
68opt_pid=0; opt_uid=0; opt_time=0; opt_timestr=0; opt_quiet=0; opt_debug=0
69filter=0; pid=0; uid=0
70
71while getopts dhp:qsu:v name
72do
73	case $name in
74	d)	opt_debug=1 ;;
75	p)	opt_pid=1; pid=$OPTARG ;;
76	q)	opt_quiet=1 ;;
77	s)	opt_time=1 ;;
78	u)	opt_uid=1; uid=$OPTARG ;;
79	v)	opt_timestr=1 ;;
80	h|?)	cat <<-END >&2
81		USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
82		       shellsnoop		# default output
83		                -q		# quiet, only print data
84		                -s		# include start time, us
85		                -v		# include start time, string
86		                -p PID		# process ID to snoop
87		                -u UID		# user ID to snoop
88		END
89		exit 1
90	esac
91done
92
93if [ $opt_quiet -eq 1 ]; then
94	opt_time=0; opt_timestr=0
95fi
96if [ $opt_pid -eq 1 -o $opt_uid -eq 1 ]; then
97	filter=1
98fi
99
100
101#################################
102# --- Main Program, DTrace ---
103#
104dtrace -n '
105 /*
106  * Command line arguments
107  */
108 inline int OPT_debug 	= '$opt_debug';
109 inline int OPT_quiet 	= '$opt_quiet';
110 inline int OPT_pid 	= '$opt_pid';
111 inline int OPT_uid 	= '$opt_uid';
112 inline int OPT_time 	= '$opt_time';
113 inline int OPT_timestr	= '$opt_timestr';
114 inline int FILTER 	= '$filter';
115 inline int PID 	= '$pid';
116 inline int UID 	= '$uid';
117 
118 #pragma D option quiet
119 #pragma D option switchrate=20hz
120 
121 /*
122  * Print header
123  */
124 dtrace:::BEGIN /OPT_time == 1/
125 { 
126 	printf("%-14s ","TIME");
127 }
128 dtrace:::BEGIN /OPT_timestr == 1/
129 { 
130 	printf("%-20s ","STRTIME");
131 }
132 dtrace:::BEGIN /OPT_quiet == 0/
133 {
134	printf("%5s %5s %8s %3s  %s\n", "PID", "PPID", "CMD", "DIR", "TEXT");
135 }
136
137 /*
138  * Remember this PID is a shell child
139  */
140 syscall::exec:entry, syscall::exece:entry
141 /execname == "sh"   || execname == "ksh"  || execname == "csh"  || 
142  execname == "tcsh" || execname == "zsh"  || execname == "bash"/
143 {
144	child[pid] = 1;
145 
146	/* debug */
147	this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
148	OPT_debug == 1 ? printf("PID %d CMD %s started. (%s)\n",
149	    pid, execname, stringof(this->parent)) : 1;
150 }
151 syscall::exec:entry, syscall::exece:entry
152 /(OPT_pid == 1 && PID != ppid) || (OPT_uid == 1 && UID != uid)/
153 {
154	/* forget if filtered */
155	child[pid] = 0;
156 }
157
158 /*
159  * Print shell keystrokes
160  */
161 syscall::write:entry, syscall::read:entry
162 /(execname == "sh"   || execname == "ksh"  || execname == "csh"  ||
163  execname == "tcsh" || execname == "zsh"  || execname == "bash")
164  && (arg0 >= 0 && arg0 <= 2)/
165 {
166	self->buf = arg1;
167 }
168 syscall::write:entry, syscall::read:entry
169 /(OPT_pid == 1 && PID != pid) || (OPT_uid == 1 && UID != uid)/
170 {
171	self->buf = 0;
172 }
173 syscall::write:return, syscall::read:return
174 /self->buf && child[pid] == 0 && OPT_time == 1/
175 {
176 	printf("%-14d ", timestamp/1000);
177 }
178 syscall::write:return, syscall::read:return
179 /self->buf && child[pid] == 0 && OPT_timestr == 1/
180 {
181	printf("%-20Y ", walltimestamp);
182 }
183 syscall::write:return, syscall::read:return
184 /self->buf && child[pid] == 0 && OPT_quiet == 0/
185 {
186	this->text = (char *)copyin(self->buf, arg0);
187	this->text[arg0] = '\'\\0\'';
188 
189	printf("%5d %5d %8s %3s  %s\n", pid, curpsinfo->pr_ppid, execname, 
190	    probefunc == "read" ? "R" : "W", stringof(this->text));
191 }
192 syscall::write:return
193 /self->buf && child[pid] == 0 && OPT_quiet == 1/
194 {
195	this->text = (char *)copyin(self->buf, arg0);
196	this->text[arg0] = '\'\\0\'';
197	printf("%s", stringof(this->text));
198 }
199 syscall::read:return
200 /self->buf && execname == "sh" && child[pid] == 0 && OPT_quiet == 1/
201 {
202	this->text = (char *)copyin(self->buf, arg0);
203	this->text[arg0] = '\'\\0\'';
204	printf("%s", stringof(this->text));
205 }
206 syscall::write:return, syscall::read:return
207 /self->buf && child[pid] == 0/
208 {
209	self->buf = 0;
210 }
211
212 /*
213  * Print command output
214  */
215 syscall::write:entry, syscall::read:entry
216 /child[pid] == 1 && (arg0 == 1 || arg0 == 2)/
217 {
218	self->buf = arg1;
219 }
220 syscall::write:return, syscall::read:return
221 /self->buf && OPT_time == 1/
222 {
223 	printf("%-14d ", timestamp/1000);
224 }
225 syscall::write:return, syscall::read:return
226 /self->buf && OPT_timestr == 1/
227 {
228	printf("%-20Y ", walltimestamp);
229 }
230 syscall::write:return, syscall::read:return
231 /self->buf && OPT_quiet == 0/
232 {
233	this->text = (char *)copyin(self->buf, arg0);
234	this->text[arg0] = '\'\\0\'';
235 
236	printf("%5d %5d %8s %3s  %s", pid, curpsinfo->pr_ppid, execname,
237	    probefunc == "read" ? "R" : "W", stringof(this->text));
238 
239	/* here we check if a newline is needed */
240	this->length = strlen(this->text);
241	printf("%s", this->text[this->length - 1] == '\'\\n\'' ? "" : "\n");
242	self->buf = 0;
243 }
244 syscall::write:return, syscall::read:return
245 /self->buf && OPT_quiet == 1/
246 {
247	this->text = (char *)copyin(self->buf, arg0);
248	this->text[arg0] = '\'\\0\'';
249	printf("%s", stringof(this->text));
250	self->buf = 0;
251 }
252
253 /*
254  *  Cleanup
255  */
256 fbt:genunix:exit:entry
257 {
258	child[pid] = 0;
259
260	/* debug */
261	this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
262	OPT_debug == 1 ? printf("PID %d CMD %s exited. (%s)\n",
263	 pid, execname, stringof(this->parent)) : 1;
264 }
265'
266