shellsnoop revision 272461
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# $Id: shellsnoop 19 2007-09-12 07:47:59Z brendan $
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# COPYRIGHT: Copyright (c) 2005 Brendan Gregg.
43#
44# CDDL HEADER START
45#
46#  The contents of this file are subject to the terms of the
47#  Common Development and Distribution License, Version 1.0 only
48#  (the "License").  You may not use this file except in compliance
49#  with the License.
50#
51#  You can obtain a copy of the license at Docs/cddl1.txt
52#  or http://www.opensolaris.org/os/licensing.
53#  See the License for the specific language governing permissions
54#  and limitations under the License.
55#
56# CDDL HEADER END
57#
58# Author: Brendan Gregg  [Sydney, Australia]
59#
60# 28-Mar-2004	Brendan Gregg	Created this.
61# 21-Jan-2005	   "	  "	Wrapped in sh to provide options.
62# 30-Nov-2005	   "	  "	Fixed trailing buffer text bug.
63# 30-Nov-2005	   "	  "	Fixed sh no keystroke text in quiet bug.
64# 30-Nov-2005	   "	  "	Last update.
65# 
66
67
68##############################
69# --- Process Arguments ---
70#
71opt_pid=0; opt_uid=0; opt_time=0; opt_timestr=0; opt_quiet=0; opt_debug=0
72filter=0; pid=0; uid=0
73
74while getopts dhp:qsu:v name
75do
76	case $name in
77	d)	opt_debug=1 ;;
78	p)	opt_pid=1; pid=$OPTARG ;;
79	q)	opt_quiet=1 ;;
80	s)	opt_time=1 ;;
81	u)	opt_uid=1; uid=$OPTARG ;;
82	v)	opt_timestr=1 ;;
83	h|?)	cat <<-END >&2
84		USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
85		       shellsnoop		# default output
86		                -q		# quiet, only print data
87		                -s		# include start time, us
88		                -v		# include start time, string
89		                -p PID		# process ID to snoop
90		                -u UID		# user ID to snoop
91		END
92		exit 1
93	esac
94done
95
96if [ $opt_quiet -eq 1 ]; then
97	opt_time=0; opt_timestr=0
98fi
99if [ $opt_pid -eq 1 -o $opt_uid -eq 1 ]; then
100	filter=1
101fi
102
103
104#################################
105# --- Main Program, DTrace ---
106#
107dtrace -n '
108 /*
109  * Command line arguments
110  */
111 inline int OPT_debug 	= '$opt_debug';
112 inline int OPT_quiet 	= '$opt_quiet';
113 inline int OPT_pid 	= '$opt_pid';
114 inline int OPT_uid 	= '$opt_uid';
115 inline int OPT_time 	= '$opt_time';
116 inline int OPT_timestr	= '$opt_timestr';
117 inline int FILTER 	= '$filter';
118 inline int PID 	= '$pid';
119 inline int UID 	= '$uid';
120 
121 #pragma D option quiet
122 #pragma D option switchrate=20hz
123 
124 /*
125  * Print header
126  */
127 dtrace:::BEGIN /OPT_time == 1/
128 { 
129 	printf("%-14s ","TIME");
130 }
131 dtrace:::BEGIN /OPT_timestr == 1/
132 { 
133 	printf("%-20s ","STRTIME");
134 }
135 dtrace:::BEGIN /OPT_quiet == 0/
136 {
137	printf("%5s %5s %8s %3s  %s\n", "PID", "PPID", "CMD", "DIR", "TEXT");
138 }
139
140 /*
141  * Remember this PID is a shell child
142  */
143 syscall::exec:entry, syscall::exece:entry
144 /execname == "sh"   || execname == "ksh"  || execname == "csh"  || 
145  execname == "tcsh" || execname == "zsh"  || execname == "bash"/
146 {
147	child[pid] = 1;
148 
149	/* debug */
150	this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
151	OPT_debug == 1 ? printf("PID %d CMD %s started. (%s)\n",
152	    pid, execname, stringof(this->parent)) : 1;
153 }
154 syscall::exec:entry, syscall::exece:entry
155 /(OPT_pid == 1 && PID != ppid) || (OPT_uid == 1 && UID != uid)/
156 {
157	/* forget if filtered */
158	child[pid] = 0;
159 }
160
161 /*
162  * Print shell keystrokes
163  */
164 syscall::write:entry, syscall::read:entry
165 /(execname == "sh"   || execname == "ksh"  || execname == "csh"  ||
166  execname == "tcsh" || execname == "zsh"  || execname == "bash")
167  && (arg0 >= 0 && arg0 <= 2)/
168 {
169	self->buf = arg1;
170 }
171 syscall::write:entry, syscall::read:entry
172 /(OPT_pid == 1 && PID != pid) || (OPT_uid == 1 && UID != uid)/
173 {
174	self->buf = 0;
175 }
176 syscall::write:return, syscall::read:return
177 /self->buf && child[pid] == 0 && OPT_time == 1/
178 {
179 	printf("%-14d ", timestamp/1000);
180 }
181 syscall::write:return, syscall::read:return
182 /self->buf && child[pid] == 0 && OPT_timestr == 1/
183 {
184	printf("%-20Y ", walltimestamp);
185 }
186 syscall::write:return, syscall::read:return
187 /self->buf && child[pid] == 0 && OPT_quiet == 0/
188 {
189	this->text = (char *)copyin(self->buf, arg0);
190	this->text[arg0] = '\'\\0\'';
191 
192	printf("%5d %5d %8s %3s  %s\n", pid, curpsinfo->pr_ppid, execname, 
193	    probefunc == "read" ? "R" : "W", stringof(this->text));
194 }
195 syscall::write:return
196 /self->buf && child[pid] == 0 && OPT_quiet == 1/
197 {
198	this->text = (char *)copyin(self->buf, arg0);
199	this->text[arg0] = '\'\\0\'';
200	printf("%s", stringof(this->text));
201 }
202 syscall::read:return
203 /self->buf && execname == "sh" && child[pid] == 0 && OPT_quiet == 1/
204 {
205	this->text = (char *)copyin(self->buf, arg0);
206	this->text[arg0] = '\'\\0\'';
207	printf("%s", stringof(this->text));
208 }
209 syscall::write:return, syscall::read:return
210 /self->buf && child[pid] == 0/
211 {
212	self->buf = 0;
213 }
214
215 /*
216  * Print command output
217  */
218 syscall::write:entry, syscall::read:entry
219 /child[pid] == 1 && (arg0 == 1 || arg0 == 2)/
220 {
221	self->buf = arg1;
222 }
223 syscall::write:return, syscall::read:return
224 /self->buf && OPT_time == 1/
225 {
226 	printf("%-14d ", timestamp/1000);
227 }
228 syscall::write:return, syscall::read:return
229 /self->buf && OPT_timestr == 1/
230 {
231	printf("%-20Y ", walltimestamp);
232 }
233 syscall::write:return, syscall::read:return
234 /self->buf && OPT_quiet == 0/
235 {
236	this->text = (char *)copyin(self->buf, arg0);
237	this->text[arg0] = '\'\\0\'';
238 
239	printf("%5d %5d %8s %3s  %s", pid, curpsinfo->pr_ppid, execname,
240	    probefunc == "read" ? "R" : "W", stringof(this->text));
241 
242	/* here we check if a newline is needed */
243	this->length = strlen(this->text);
244	printf("%s", this->text[this->length - 1] == '\'\\n\'' ? "" : "\n");
245	self->buf = 0;
246 }
247 syscall::write:return, syscall::read:return
248 /self->buf && OPT_quiet == 1/
249 {
250	this->text = (char *)copyin(self->buf, arg0);
251	this->text[arg0] = '\'\\0\'';
252	printf("%s", stringof(this->text));
253	self->buf = 0;
254 }
255
256 /*
257  *  Cleanup
258  */
259 syscall::rexit:entry
260 {
261	child[pid] = 0;
262
263	/* debug */
264	this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
265	OPT_debug == 1 ? printf("PID %d CMD %s exited. (%s)\n",
266	 pid, execname, stringof(this->parent)) : 1;
267 }
268'
269