File systemtap-CVE-2009-4273.diff of Package systemtap-docs

Subject: VUL-0: systemtap-server code exec
References: bnc#574243
Signed-Off-By: Tony Jones <tonyj@suse.de>


'stap-client -p1 -B\;ai2' will print the usage help followed by a message 
similar to /usr/local/bin/stap-server: line 340: ai2: command not found
which indicates that server tried to run the 'ai2' command.

cases such as stap-client -B CC=/bin/rm CFLAGS=/ ...  where the execution is 
done by make


Comprises the following commits.


commit 148297a5de2231e50262ec28a400b89b2d7c21f4
Author: Dave Brolley <brolley@redhat.com>
Date:   Tue Dec 15 15:13:13 2009 -0500

    Remove unused variable.

commit 5d4ea4cc1d1f7531fb0ff4d0498957e0333a61eb
Author: Dave Brolley <brolley@redhat.com>
Date:   Wed Dec 2 16:40:13 2009 -0500

    No need to pass -c option to the server.

commit a0626e2e2ea13b6fc974157fb71fe6d48f4c7ec0
Author: Dave Brolley <brolley@redhat.com>
Date:   Thu Jan 7 13:58:11 2010 -0500

    Client argument handling:
    
    Pass partial options to the server instead of complaining about
    them in the client.
    
    Update known failures from buildok in server.exp.

commit 12091330be193cd0836d48c525bab015fcec2c75
Author: Dave Brolley <brolley@redhat.com>
Date:   Thu Jan 7 17:10:30 2010 -0500

    Take care when echoing something that could start with a -.

commit ed03894041aedf79811d5ad5c41caedbf90052cd
Author: Dave Brolley <brolley@redhat.com>
Date:   Fri Jan 8 16:25:59 2010 -0500

    New test suite for client/server argument handling.

commit 3c07041760dccbb3151ef21602b8bc5da4b32197
Author: Dave Brolley <brolley@redhat.com>
Date:   Mon Jan 11 14:34:27 2010 -0500

    Filter options for unprivileged use after --stap-client is seen.

commit a0ace4915e5d963c28fa3b54f87afef34b82b6a5
Author: Dave Brolley <brolley@redhat.com>
Date:   Mon Jan 11 20:13:40 2010 -0500

    Rework filtering of client options. Add testsuite.

commit 75c2a31ddbe337af46ec4dad8a4dbbbd790c47b7
Author: Frank Ch. Eigler <fche@elastic.org>
Date:   Tue Jan 12 14:12:04 2010 -0500

    tweak stap argument checking
    
    * main.cxx (checkOptions): Inline into main(), abeam other option checks.

commit 5f03ebf5b2acccb652c9135627184479bc8d7d47
Author: Dave Brolley <brolley@redhat.com>
Date:   Mon Jan 11 20:19:54 2010 -0500

    Invalid casess can be tested for 'make check'.

commit f2aadddae0d01fa5a676404e49c6c36825b40512
Author: Dave Brolley <brolley@redhat.com>
Date:   Mon Jan 11 22:14:36 2010 -0500

    Add some additional test cases.

commit f73d5cad4e9aa5baa0a763a76cf4516721d29b2a
Author: Dave Brolley <brolley@redhat.com>
Date:   Wed Jan 13 15:07:52 2010 -0500

    Test newline characters as part of fuzzing argument strings.

commit 622fa74aa720b3eda55c81530d458e3ea7792bb2
Author: Dave Brolley <brolley@redhat.com>
Date:   Thu Jan 14 15:44:09 2010 -0500

    Allow / as a random argyment character when fuzzing.

commit e4d80588594a7495a3efedbd3a4281df13ff253b
Author: Dave Brolley <brolley@redhat.com>
Date:   Fri Jan 15 00:47:32 2010 -0500

    PR11105: stap-client wire protocol change

commit cf4a6df840531c1b30f8cfa7d10981d071911b98
Author: Frank Ch. Eigler <fche@elastic.org>
Date:   Fri Jan 15 03:06:52 2010 -0500

    PR11105: robustify stap-server
    
    * main.cxx (main): Always downgrade client-provided -p5 to -p4.
    * stap-client (unpack_response): Sanitize stdout due to same.
    * stap-server-connect.c: Eliminate a bunch of globals.
      (handle_connection): Make things locals instead.  Base tmp files
      on $TMPDIR.
      (spawn_and_wait): New helper function.
      (handleRequest): New monster function to inline rest of old
      stap-server-request.

commit 36d1c134edc4bd4ee20225003041188c13b7f36f
Author: Frank Ch. Eigler <fche@elastic.org>
Date:   Fri Jan 15 03:12:53 2010 -0500

    testsuite: fix wording of invalid-entry test group

commit 86f99ad8206574dc6400d48563db58341cb50f52
Author: Frank Ch. Eigler <fche@elastic.org>
Date:   Fri Jan 15 03:27:34 2010 -0500

    PR11105: remove extraneous \n from localized foo.stp script file name

commit 2a1c9b5db533fe7d2d2d4bac572195c490de62fb
Author: Frank Ch. Eigler <fche@elastic.org>
Date:   Fri Jan 15 12:34:39 2010 -0500

    PR11105: support default unset --prefix
    
    * configure.ac (STAP_PREFIX): Map NONE -> /usr/local.

commit b75067caf1bb416af21473e40c917d953531e9f9
Author: Dave Brolley <brolley@redhat.com>
Date:   Mon Jan 18 11:56:13 2010 -0500

    Correct client-side quoting issues discovered by fche during the server-side reimplementation.
    
    Also add the test cases to the test suite.

commit 4240be911e37b817727ecb33f321f0ea389ede61
Author: Dave Brolley <brolley@redhat.com>
Date:   Mon Feb 15 15:01:28 2010 -0500

    Don't pass client-only options to the server.
    
    Also correct parsing of the --server option.

commit 241443ad36a5a2cacb9e8e6f12f808d304835f2a
Author: Dave Brolley <brolley@redhat.com>
Date:   Tue Feb 2 08:26:01 2010 -0500

    PR 11105: Remaining client-side problems:
    
    stap-client: Correct handling of embedded newlines in arguments.
    server_args.exp: Add additional cases discovered by fche and by fuzzing.

commit c0d1b5a004b9949bb455b7dbe17b335b7cab9ead
Author: Frank Ch. Eigler <fche@elastic.org>
Date:   Fri Feb 12 10:25:43 2010 -0500

    PR11105 part 2: tighten constraints on stap-server parameters passed to make
    
    * util.h, util.cxx (assert_match_regexp): New function.
    * main.cxx (main): Constrain -R, -r, -a, -D, -S, -q, -B flags.
    * stap-serverd (listen): Harden stap-server-connect with ulimit/loop.
    * testsuite/systemtap.server/{client,server}_args.exp: Revised.

commit cc9e5488d82b728e568bca1f8d6094856fc8e641
Author: Frank Ch. Eigler <fche@elastic.org>
Date:   Fri Feb 12 10:39:58 2010 -0500

    PR11105 part 2a, fix buggy \\. in -r option regexp

---
 configure                                  |    4 
 configure.ac                               |    7 
 main.cxx                                   |  100 +++--
 stap-client                                |  326 ++++++++++---------
 stap-server-connect.c                      |  496 +++++++++++++++++++++++------
 stap-serverd                               |   22 -
 testsuite/systemtap.server/client_args.exp |  119 ++++++
 testsuite/systemtap.server/hello.stp       |    2 
 testsuite/systemtap.server/server.exp      |    4 
 testsuite/systemtap.server/server_args.exp |  182 ++++++++++
 testsuite/systemtap.server/test.stp        |    1 
 util.cxx                                   |   36 ++
 util.h                                     |    2 
 13 files changed, 994 insertions(+), 307 deletions(-)

--- a/configure
+++ b/configure
@@ -8937,9 +8937,11 @@ done
 
 fi
 
+stap_prefix=$prefix
+test "$stap_prefix" = NONE && stap_prefix=$ac_default_prefix
 
 cat >>confdefs.h <<_ACEOF
-#define STAP_PREFIX "$prefix"
+#define STAP_PREFIX "$stap_prefix"
 _ACEOF
 
 
--- a/configure.ac
+++ b/configure.ac
@@ -576,8 +576,11 @@ fi
 dnl This is here mainly to make sure that configure --prefix=... changes
 dnl the config.h files so files depending on it are recompiled
 dnl prefix is passed through indirectly in the Makefile.am AM_CPPFLAGS.
-dnl Don't use this directly (when not given it is set to NONE).
-AC_DEFINE_UNQUOTED(STAP_PREFIX, "$prefix", [configure prefix location])
+dnl Formerly: Don't use this directly (when not given it is set to NONE).
+dnl Currently: inline autoconf's later defaulting
+stap_prefix=$prefix
+test "$stap_prefix" = NONE && stap_prefix=$ac_default_prefix
+AC_DEFINE_UNQUOTED(STAP_PREFIX, "$stap_prefix", [configure prefix location])
 
 AC_CONFIG_HEADERS([config.h:config.in])
 AC_CONFIG_FILES(Makefile doc/Makefile doc/SystemTap_Tapset_Reference/Makefile grapher/Makefile stap.1 stapprobes.3stap stapfuncs.3stap stapvars.3stap stapex.3stap staprun.8 stap-server.8 man/stapprobes.iosched.3stap man/stapprobes.netdev.3stap man/stapprobes.nfs.3stap man/stapprobes.nfsd.3stap man/stapprobes.pagefault.3stap man/stapprobes.kprocess.3stap man/stapprobes.rpc.3stap man/stapprobes.scsi.3stap man/stapprobes.signal.3stap man/stapprobes.socket.3stap man/stapprobes.tcp.3stap man/stapprobes.udp.3stap man/stapprobes.snmp.3stap initscript/systemtap)
--- a/main.cxx
+++ b/main.cxx
@@ -1,5 +1,5 @@
 // systemtap translator/driver
-// Copyright (C) 2005-2009 Red Hat Inc.
+// Copyright (C) 2005-2010 Red Hat Inc.
 // Copyright (C) 2005 IBM Corp.
 // Copyright (C) 2006 Intel Corporation.
 //
@@ -56,7 +56,7 @@ version ()
     << "SystemTap translator/driver "
     << "(version " << VERSION << "/" << dwfl_version (NULL)
     << " " << GIT_MESSAGE << ")" << endl
-    << "Copyright (C) 2005-2009 Red Hat, Inc. and others" << endl
+    << "Copyright (C) 2005-2010 Red Hat, Inc. and others" << endl
     << "This is free software; see the source for copying conditions." << endl;
 }
 
@@ -362,43 +362,6 @@ setup_kernel_release (systemtap_session
         s.kernel_build_tree = "/lib/modules/" + s.kernel_release + "/build";
       }
 }
-
-static void
-checkOptions (systemtap_session &s)
-{
-  bool optionsConflict = false;
-
-  if((s.cmd != "") && (s.target_pid))
-    {
-      cerr << "You can't specify -c and -x options together." <<endl;
-      optionsConflict = true;
-    }
-
-  if (s.unprivileged)
-    {
-      if (s.guru_mode)
-	{
-	  cerr << "You can't specify -g and --unprivileged together." << endl;
-	  optionsConflict = true;
-	}
-    }
-
-  if (!s.kernel_symtab_path.empty())
-    {
-      if (s.consult_symtab)
-      {
-        cerr << "You can't specify --kelf and --kmap together." << endl;
-	optionsConflict = true;
-      }
-      s.consult_symtab = true;
-      if (s.kernel_symtab_path == PATH_TBD)
-        s.kernel_symtab_path = string("/boot/System.map-") + s.kernel_release;
-    }
-
-  if (optionsConflict)
-    usage (s, 1);
-}
-
 int
 main (int argc, char * const argv [])
 {
@@ -467,6 +430,8 @@ main (int argc, char * const argv [])
   s.load_only = false;
   s.skip_badvars = false;
   s.unprivileged = false;
+  bool client_options = false;
+  string client_options_disallowed;
 
   // Location of our signing certificate.
   // If we're root, use the database in SYSCONFDIR, otherwise
@@ -538,7 +503,6 @@ main (int argc, char * const argv [])
     setup_kernel_release(s, s_kr);
   }
 
-
   while (true)
     {
       int long_opt;
@@ -550,6 +514,7 @@ main (int argc, char * const argv [])
 #define LONG_OPT_VERBOSE_PASS 5
 #define LONG_OPT_SKIP_BADVARS 6
 #define LONG_OPT_UNPRIVILEGED 7
+#define LONG_OPT_CLIENT_OPTIONS 8
       // NB: also see find_hash(), usage(), switch stmt below, stap.1 man page
       static struct option long_options[] = {
         { "kelf", 0, &long_opt, LONG_OPT_KELF },
@@ -559,6 +524,7 @@ main (int argc, char * const argv [])
 	{ "skip-badvars", 0, &long_opt, LONG_OPT_SKIP_BADVARS },
         { "vp", 1, &long_opt, LONG_OPT_VERBOSE_PASS },
         { "unprivileged", 0, &long_opt, LONG_OPT_UNPRIVILEGED },
+        { "client-options", 0, &long_opt, LONG_OPT_CLIENT_OPTIONS },
         { NULL, 0, NULL, 0 }
       };
       int grc = getopt_long (argc, argv, "hVvtp:I:e:o:R:r:a:m:kgPc:x:D:bs:uqwl:d:L:FS:B:",
@@ -599,6 +565,8 @@ main (int argc, char * const argv [])
           break;
 
         case 'I':
+	  if (client_options)
+	    client_options_disallowed += client_options_disallowed.empty () ? "-I" : ", -I";
           s.include_path.push_back (string (optarg));
           break;
 
@@ -621,16 +589,21 @@ main (int argc, char * const argv [])
           break;
 
         case 'o':
+          // NB: client_options not a problem, since pass 1-4 does not use output_file.
           s.output_file = string (optarg);
           break;
 
         case 'R':
+          if (client_options) { cerr << "ERROR: -R invalid with --client-options" << endl; usage(s,1); }
           s.runtime_path = string (optarg);
           break;
 
         case 'm':
+	  if (client_options)
+	    client_options_disallowed += client_options_disallowed.empty () ? "-m" : ", -m";
           s.module_name = string (optarg);
 	  save_module = true;
+          // XXX: convert to assert_regexp_match()
 	  {
 	    string::size_type len = s.module_name.length();
 
@@ -675,10 +648,13 @@ main (int argc, char * const argv [])
           break;
 
         case 'r':
+          if (client_options) // NB: no paths!
+            assert_regexp_match("-r parameter from client", optarg, "^[a-z0-9_.-]+$");
           setup_kernel_release(s, optarg);
           break;
 
         case 'a':
+          assert_regexp_match("-a parameter", optarg, "^[a-z0-9_-]+$");
           s.architecture = string(optarg);
           break;
 
@@ -726,14 +702,19 @@ main (int argc, char * const argv [])
 	  break;
 
 	case 'D':
+          assert_regexp_match ("-D parameter", optarg, "^[a-z_][a-z_0-9]*(=[a-z_0-9]+)?$");
+	  if (client_options)
+	    client_options_disallowed += client_options_disallowed.empty () ? "-D" : ", -D";
 	  s.macros.push_back (string (optarg));
 	  break;
 
 	case 'S':
+          assert_regexp_match ("-S parameter", optarg, "^[0-9]+(,[0-9]+)?$");
 	  s.size_option = string (optarg);
 	  break;
 
 	case 'q':
+          if (client_options) { cerr << "ERROR: -q invalid with --client-options" << endl; usage(s,1); }
 	  s.tapset_compile_coverage = true;
 	  break;
 
@@ -764,6 +745,7 @@ main (int argc, char * const argv [])
 	  break;
 
 	case 'B':
+          if (client_options) { cerr << "ERROR: -B invalid with --client-options" << endl; usage(s,1); }
           s.kbuildflags.push_back (string (optarg));
 	  break;
 
@@ -816,6 +798,11 @@ main (int argc, char * const argv [])
 	      break;
 	    case LONG_OPT_UNPRIVILEGED:
 	      s.unprivileged = true;
+              /* NB: for server security, it is essential that once this flag is
+                 set, no future flag be able to unset it. */
+	      break;
+	    case LONG_OPT_CLIENT_OPTIONS:
+	      client_options = true;
 	      break;
             default:
               cerr << "Internal error parsing command arguments." << endl;
@@ -830,8 +817,37 @@ main (int argc, char * const argv [])
     }
 
   // Check for options conflicts.
-  checkOptions (s);
 
+  if (client_options && s.last_pass > 4)
+    {
+      s.last_pass = 4; /* Quietly downgrade.  Server passed through -p5 naively. */
+    }
+  if (client_options && s.unprivileged && ! client_options_disallowed.empty ())
+    {
+      cerr << "You can't specify " << client_options_disallowed << " when --unprivileged is specified." << endl;
+      usage (s, 1);
+    }
+  if ((s.cmd != "") && (s.target_pid))
+    {
+      cerr << "You can't specify -c and -x options together." << endl;
+      usage (s, 1);
+    }
+  if (s.unprivileged && s.guru_mode)
+    {
+      cerr << "You can't specify -g and --unprivileged together." << endl;
+      usage (s, 1);
+    }
+  if (!s.kernel_symtab_path.empty())
+    {
+      if (s.consult_symtab)
+      {
+        cerr << "You can't specify --kelf and --kmap together." << endl;
+        usage (s, 1);
+      }
+      s.consult_symtab = true;
+      if (s.kernel_symtab_path == PATH_TBD)
+        s.kernel_symtab_path = string("/boot/System.map-") + s.kernel_release;
+    }
   // Warn in case the target kernel release doesn't match the running one.
   if (s.last_pass > 4 &&
       (string(buf.release) != s.kernel_release ||
@@ -1279,6 +1295,8 @@ pass_5:
   else
     {
       if (s.keep_tmpdir)
+        // NB: the format of this message needs to match the expectations
+        // of stap-server-connect.c.
         clog << "Keeping temporary directory \"" << s.tmpdir << "\"" << endl;
       else
         {
--- a/stap-client
+++ b/stap-client
@@ -2,7 +2,7 @@
 
 # Compile server client for systemtap
 #
-# Copyright (C) 2008, 2009 Red Hat Inc.
+# Copyright (C) 2008-2010 Red Hat Inc.
 #
 # This file is part of systemtap, and is free software.  You can
 # redistribute it and/or modify it under the terms of the GNU General
@@ -57,7 +57,6 @@ function initialization {
     p_phase=5
     v_level=0
     keep_temps=0
-    b_specified=0
     m_name=
     module_name=stap_$$
     uname_r="`uname -r`"
@@ -82,52 +81,51 @@ function initialization {
 # output.
 #
 function parse_options {
-    cmdline=
-    cmdline1=
-    cmdline2=
+    # Each command line argument will be written to its own file within the
+    # request package.
+    argc=1
+    packed_options='-'
+    arg_subst=
 
     while test $# != 0
     do
-	advance_p=0
+	advance=0
 	dash_seen=0
+	client_arg=0
 
         # Start of a new token.
-	first_token=$1
-	until test $advance_p != 0
+	first_token="$1"
+	until test $advance != 0
 	do
             # Identify the next option
-	    first_char=`expr "$first_token" : '\(.\).*'`
+	    first_char="${first_token:0:1}"
 	    second_char=
 	    if test $dash_seen = 0; then
 		if test "$first_char" = "-"; then
 		    if test "$first_token" != "-"; then
 	                # It's not a lone dash, so it's an option.
 			# Is it a long option (i.e. --option)?
-			second_char=`expr "$first_token" : '.\(.\).*'`
+			second_char="${first_token:1:1}"
 			if test "X$second_char" = "X-"; then
-			    long_option=`expr "$first_token" : '--\(.*\)=.*'`
-			    test "X$long_option" != "X" || long_option=`expr "$first_token" : '--\(.*\)'`
-			    case $long_option in
-				ssl)
-				    process_ssl $first_token
+			    case "$first_token" in
+				--ssl=*)
+				    process_ssl "$first_token"
 				    ;;
-				server)
-				    process_server $first_token
+				--server=*)
+				    process_server "$first_token"
 				    ;;
 				*)
-		                    # An unknown or unimportant option.
-				    # Ignore it, but pass it on to the server.
-				    cmdline2="$cmdline2 $first_token"
+		                    # An unknown or unimportant option. Ignore it.
 				    ;;
 			    esac
-			    advance_p=$(($advance_p + 1))
+			    advance=$(($advance + 1))
 			    break
 			fi
 	                # It's not a lone dash, or a long option, so it's a short option string.
 			# Remove the dash.
-			first_token=`expr "$first_token" : '-\(.*\)'`
+			first_token="${first_token:1}"
 			dash_seen=1
-			first_char=`expr "$first_token" : '\(.\).*'`
+			first_char="${first_token:0:1}"
 		    fi
 		fi
 		if test $dash_seen = 0; then
@@ -137,14 +135,9 @@ function parse_options {
 	            # then it could be the name of the script file.
 		    if test "X$e_script" = "X" -a "X$script_file" = "X"; then
 			script_file="$first_token"
-			cmdline1="$cmdline2"
-			cmdline2=
-		    elif test "$first_char" != "'"; then
-			cmdline2="$cmdline2 '$first_token'"
-		    else
-			cmdline2="$cmdline2 $first_token"
+			script_file_argc=$argc
 		    fi
-		    advance_p=$(($advance_p + 1))
+		    advance=$(($advance + 1))
 		    break
 		fi
 	    fi
@@ -152,115 +145,129 @@ function parse_options {
             # We are at the start of an option. Look at the first character.
 	    case $first_char in
 		a)
-		    get_arg $first_token $2
-		    process_a $stap_arg
+		    get_arg "$first_token" "$2"
+		    process_a "$stap_arg"
 		    ;;	
-		b)
-		    b_specified=1
-		    ;;
 		B)
-		    get_arg $first_token $2
-		    cmdline2="${cmdline2} -$first_char '$stap_arg'"
+		    get_arg "$first_token" "$2"
 		    ;;	
 		c)
-		    get_arg $first_token "$2"
+		    get_arg "$first_token" "$2"
 		    process_c "$stap_arg"
 		    ;;
 		D)
-		    get_arg $first_token $2
-		    cmdline2="${cmdline2} -$first_char '$stap_arg'"
+		    get_arg "$first_token" "$2"
 		    ;;
 		e)
-		    get_arg $first_token "$2"
+		    get_arg "$first_token" "$2"
 		    process_e "$stap_arg"
 		    ;;
 		I)
-		    get_arg $first_token $2
-		    process_I $stap_arg
+		    get_arg "$first_token" "$2"
+		    process_I "$stap_arg"
 		    ;;	
 		k)
 		    keep_temps=1
 		    ;;
 		l)
-		    get_arg $first_token $2
-		    cmdline2="${cmdline2} -$first_char '$stap_arg'"
+		    get_arg "$first_token" "$2"
 		    p_phase=2
 		    ;;
 		L)
-		    get_arg $first_token $2
-		    cmdline2="${cmdline2} -$first_char '$stap_arg'"
+		    get_arg "$first_token" "$2"
 		    p_phase=2
 		    ;;
 		m)
-		    get_arg $first_token $2
-		    process_m $stap_arg
+		    get_arg "$first_token" "$2"
+		    process_m "$stap_arg"
 		    ;;
 		o)
-		    get_arg $first_token $2
-		    process_o $stap_arg
+		    get_arg "$first_token" "$2"
+		    process_o "$stap_arg"
 		    ;;
 		p)
-		    get_arg $first_token $2
-		    process_p $stap_arg
+		    get_arg "$first_token" "$2"
+		    process_p "$stap_arg"
 		    ;;
 		r)
-		    get_arg $first_token $2
-		    process_r $stap_arg
+		    get_arg "$first_token" "$2"
+		    process_r "$stap_arg"
 		    ;;	
 		R)
-		    get_arg $first_token $2
-		    process_R $stap_arg
+		    get_arg "$first_token" "$2"
+		    process_R "$stap_arg"
 		    ;;	
 		s)
-		    get_arg $first_token $2
-		    cmdline2="${cmdline2} -$first_char '$stap_arg'"
+		    get_arg "$first_token" "$2"
 		    ;;	
 		S)
-		    get_arg $first_token $2
-		    cmdline2="${cmdline2} -$first_char '$stap_arg'"
+		    get_arg "$first_token" "$2"
 		    ;;	
 		v)
 		    v_level=$(($v_level + 1))
 		    ;;
 		x)
-		    get_arg $first_token $2
-		    cmdline2="${cmdline2} -$first_char '$stap_arg'"
+		    get_arg "$first_token" "$2"
 		    ;;
 		*)
-		    # An unknown or unimportant flag. Ignore it, but pass it on to the server.
+		    # An unknown or unimportant flag.
 		    ;;
 	    esac
 
-	    if test $advance_p = 0; then
+	    if test $advance = 0; then
 	        # Just another flag character. Consume it.
-		cmdline2="$cmdline2 -$first_char"
-		first_token=`expr "$first_token" : '.\(.*\)'`
+		first_token="${first_token:1}"
 		if test "X$first_token" = "X"; then
-		    advance_p=$(($advance_p + 1))
+		    advance=$(($advance + 1))
 		fi
 	    fi
 	done
 
         # Consume the arguments we just processed.
-	while test $advance_p != 0
-	do
+	while test $advance != 0; do
+	    # Does the final argument file contain a client-side file
+	    # name which must be changed to a server-side name?
+	    local arg
+	    if test "X$arg_subst" != "X" -a $advance = 1; then
+		arg="$arg_subst"
+		arg_subst=
+	    else
+		arg="$1"
+	    fi
+
+	    # If it's not client-only argument,
+	    # place the argument in a numbered file within our temp
+	    # directory.
+	    # o We don't write a newline at the end, since newline could be
+	    #   part of the argument.
+	    # o We add an X to the beginning of the file
+	    #   in order to avoid having 'echo' interpret the output as
+	    #   its own option. We then remove the X.
+	    #   There must be a better way.
+	    if test $client_arg = 0; then
+		echo -n "X$arg" > "$tmpdir_client/argv$argc"
+		sed -i "s|^X||" "$tmpdir_client/argv$argc"
+		argc=$(($argc + 1))
+	    fi
+
+	    # Get the next argument.
 	    shift
-	    advance_p=$(($advance_p - 1))
+	    advance=$(($advance - 1))
+	    packed_options='-'
 	done
     done
 
     # If the script file was given and it's not '-', then replace it with its
-    # client-temp-name in the command string.
+    # client-temp-name in its argument file.
     if test "X$script_file" != "X"; then
 	local local_name
 	if test "$script_file" != "-"; then
-	    local_name=`generate_client_temp_name "$script_file"`
+	    generate_client_temp_name "$script_file"
+	    local_name="$client_temp_name"
 	else
-	    local_name="$script_file"
+	    local_name="-"
 	fi
-	cmdline="$cmdline1 'script/$local_name' $cmdline2"
-    else
-	cmdline="$cmdline2"
+	echo -n "script/$local_name" > "$tmpdir_client/argv$script_file_argc"
     fi
 
     # Processing based on final options settings
@@ -270,12 +277,6 @@ function parse_options {
     # We must have at least one usable certificate database.
     test "X$local_ssl_dbs" != "X " -o "X$public_ssl_dbs" != "X" || \
 	fatal "No usable certificate databases found"
-
-    # We can use any server if the phase is less than 4
-    # But don't for now....
-    #if test $p_phase -lt 4; then
-    #	find_all="--all"
-    #fi
 }
 
 # function: get_arg FIRSTWORD SECONDWORD
@@ -283,33 +284,31 @@ function parse_options {
 # Collect an argument to the given option
 function get_arg {
     # Remove first character.
-    local opt=`expr "$1" : '\(.\).*'`
-    local first=`expr "$1" : '.\(.*\)'`
+    local opt="${1:0:1}"
+    local first="${1:1}"
+    packed_options="${packed_options}$opt"
 
     # Advance to the next token, if the first one is exhausted.
     if test "X$first" = "X"; then
-	shift
-	advance_p=$(($advance_p + 1))
-	first=$1
+	advance=$(($advance + 1))
+	first="$2"
     fi
 
-    test "X$first" != "X" || \
-	fatal "Missing argument to -$opt"
-
     stap_arg="$first"
-    advance_p=$(($advance_p + 1))
+    test "X$first" != "X" && advance=$(($advance + 1))
 }
 
 # function: process_ssl ARGUMENT
 #
 # Process the --ssl option.
 function process_ssl {
-    local db=`expr "$1" : '--ssl=\(.*\)'`
+    client_arg=1
+    local db="${1:6}"
 
     test "X$db" != "X" || \
 	fatal "Missing argument to --ssl"
 
-    check_db $db || return
+    check_db "$db" || return
 
     additional_local_ssl_dbs="$additional_local_ssl_dbs $db"
 }
@@ -318,7 +317,8 @@ function process_ssl {
 #
 # Process the --server option.
 function process_server {
-    local spec=`expr "$1" : '--server=\(.*\)'`
+    client_arg=1
+    local spec="${1:9}"
 
     test "X$spec" != "X" || \
 	fatal "Missing argument to --server"
@@ -331,7 +331,6 @@ function process_server {
 # Process the -c flag.
 function process_c {
     c_cmd="$1"
-    cmdline2="${cmdline2} -c '$1'"
 }
 
 # function: process_e ARGUMENT
@@ -342,22 +341,23 @@ function process_e {
     # which may have already been identified.
     if test "X$e_script" = "X"; then
 	e_script="$1"
-	if test "X$script_file" != "X"; then
-	    cmdline2="$cmdline1 '$script_file' $cmdline2"
-	    cmdline1=
-	    script_file=
-	fi
+	script_file=
     fi
-    cmdline2="${cmdline2} -e '$1'"
 }
 
-# function: process_I ARGUMENT
+# function: process_I ARGUMENT ORIGINAL_ARGUMENT
 #
 # Process the -I flag.
 function process_I {
-    local local_name=`include_file_or_directory tapsets $1`
-    test "X$local_name" != "X" || return
-    cmdline2="${cmdline2} -I 'tapsets/$local_name'"
+    test "X$1" = "X" && return
+    test "${1:0:1}" = "
+" && return
+    include_file_or_directory tapsets "$1"
+    if test $advance = 1; then
+	arg_subst="${packed_options}tapsets/$included_name"
+    else
+	arg_subst="tapsets/$included_name"
+    fi
 }
 
 # function: process_m ARGUMENT
@@ -366,7 +366,6 @@ function process_I {
 function process_m {
     module_name="$1"
     m_name="$1"
-    cmdline2="${cmdline2} -m '$1'"
 }
 
 # function: process_o ARGUMENT
@@ -374,40 +373,38 @@ function process_m {
 # Process the -o flag.
 function process_o {
     stdout_redirection="$1"
-    cmdline2="${cmdline2} -o '$1'"
 }
 
 # function: process_p ARGUMENT
 #
 # Process the -p flag.
 function process_p {
-    p_phase=$1
-    cmdline2="${cmdline2} -p '$1'"
+    p_phase="$1"
 }
 
 # function: process_r ARGUMENT
 #
 # Process the -r flag.
 function process_r {
-    local first_char=`expr "$1" : '\(.\).*'`
+    local first_char="${1:0:1}"
 
     if test "$first_char" = "/"; then # fully specified path
-        kernel_build_tree=$1
+        kernel_build_tree="$1"
         version_file_name="$kernel_build_tree/include/config/kernel.release"
         # The file include/config/kernel.release within the
         # build tree is used to pull out the version information
-	release=`cat $version_file_name 2>/dev/null`
+	release=`cat "$version_file_name" 2>/dev/null`
 	if test "X$release" = "X"; then
 	    fatal "Missing $version_file_name"
 	    return
 	fi
     else
 	# kernel release specified directly
-	release=$1
+	release="$1"
     fi
 
     if test "X$release" != "X$uname_r"; then
-	uname_r=$release
+	uname_r="$release"
 	find_all="--all"
     fi
 }
@@ -417,47 +414,65 @@ function process_r {
 # Process the -a flag.
 function process_a {
     if test "X$1" != "X$arch"; then
-	arch=$1
+	arch="$1"
 	find_all="--all"
     fi
 }
 
-# function: process_R ARGUMENT
+# function: process_R ARGUMENT ORIGINAL_ARGUMENT
 #
 # Process the -R flag.
 function process_R {
-    local local_name=`include_file_or_directory runtime $1`
-    test "X$local_name" != "X" || return
-    cmdline2="${cmdline2} -R 'runtime/$local_name'"
+    test "X$1" = "X" && return
+    test "${1:0:1}" = "
+" && return
+    include_file_or_directory runtime "$1"
+    if test $advance = 1; then
+	arg_subst="${packed_options}runtime/$included_name"
+    else
+	arg_subst="runtime/$included_name"
+    fi
 }
 
 # function: include_file_or_directory PREFIX NAME
 #
 # Include the given file or directory in the client's temporary
-# tree to be sent to the server.
+# tree to be sent to the server and save it's name in the variable
+# included_name. We use a global variable instread of echoing the
+# result since the use of `include_file_or_directory` loses a trailing
+# newline.
 function include_file_or_directory {
-    # Add a symbolic link of the named file or directory to our temporary directory
-    local local_name=`generate_client_temp_name "$2"`
+    # Add a symbolic link of the named file or directory to our temporary
+    # directory, but only if the file or directory exists.
+    generate_client_temp_name "$2"
+    local local_name="$client_temp_name"
+    included_name="$local_name"
+    test -e "/$local_name" || return
+
     local local_dirname=`dirname "$local_name"`
     mkdir -p "$tmpdir_client/$1/$local_dirname" || \
 	fatal "Could not create $tmpdir_client/$1/$local_dirname"
     ln -s "/$local_name" "$tmpdir_client/$1/$local_name" || \
 	fatal "Could not link $tmpdir_client/$1/$local_name to /$local_name"
-    echo "$local_name"
 }
 
 # function: generate_client_temp_name NAME
 #
 # Generate the name to be used for the given file/directory relative to the
-# client's temporary directory.
+# client's temporary directory and stores it in the variable
+# client_temp_name. We use a global variable instread of echoing the
+# result since the use of `generate_client_temp_name` loses a trailing
+# newline.
 function generate_client_temp_name {
     # Transform the name into a fully qualified path name
-    local full_name=`echo "$1" | sed "s,^\\\([^/]\\\),$wd/\\\\1,"`
+    local full_name="$1"
+    test "${full_name:0:1}" != "/" && full_name="$wd/$full_name"
 
     # The same name without the initial / or trailing /
-    local local_name=`echo "$full_name" | sed 's,^/\(.*\),\1,'`
-    local_name=`echo "$local_name" | sed 's,\(.*\)/$,\1,'`
-    echo "$local_name"
+    local local_name="${full_name:1}"
+    test "${local_name: -1:1}" = "/" && local_name="${local_name:0:$((${#local_name}-1))}"
+
+    client_temp_name="$local_name"
 }
 
 # function: create_request
@@ -474,12 +489,11 @@ function create_request {
 		fatal "Cannot create temporary directory " $tmpdir_client/script
 	    cat > "$tmpdir_client/script/$script_file"
 	else
-	    include_file_or_directory script "$script_file" > /dev/null
+	    include_file_or_directory script "$script_file"
 	fi
     fi
 
     # Add the necessary info to special files in our temporary directory.
-    echo "cmdline: $cmdline" > cmdline
     echo "sysinfo: `client_sysinfo`" > sysinfo
 }
 
@@ -502,7 +516,8 @@ function package_request {
     zip_client=$tmpdir_env/`mktemp $tmpdir_client_base.zip.XXXXXX` || \
 	fatal "Cannot create temporary file " $zip_client
 
-    (rm $zip_client && zip -r $zip_client $tmpdir_client_base > /dev/null) || \
+    cd $tmpdir_client
+    (rm -f $zip_client && zip -r $zip_client * > /dev/null) || \
 	fatal "zip of request tree, $tmpdir_client, failed"
 }
 
@@ -518,23 +533,12 @@ function unpack_response {
     unzip -d $tmpdir_server $zip_server > /dev/null || \
 	fatal "Cannot unpack server response, $zip_server"
 
-    # Check the contents of the expanded directory. It should contain a
-    # single directory whose name matches $stap_tmpdir_prefix_server.??????
-    local num_files=`ls $tmpdir_server | wc -l`
-    test $num_files = 1 || \
-	fatal "Wrong number of files in server's temp directory"
-    test -d $tmpdir_server/$stap_tmpdir_prefix_server.?????? || \
-	fatal "`ls $tmpdir_server` does not match the expected name or is not a directory"
-    # Move the contents of the directory down one level.
-    mv $tmpdir_server/$stap_tmpdir_prefix_server.??????/* $tmpdir_server
-    rm -fr $tmpdir_server/$stap_tmpdir_prefix_server.??????
-
     # Check the contents of the directory. It should contain:
     # 1) a file called stdout
     # 2) a file called stderr
     # 3) a file called rc
     # 4) optionally a directory named to match stap??????
-    num_files=`ls $tmpdir_server | wc -l`
+    local num_files=`ls $tmpdir_server | wc -l`
     test $num_files = 4 -o $num_files = 3 || \
 	fatal "Wrong number of files in server's temp directory"
     test -f $tmpdir_server/stdout || \
@@ -544,7 +548,8 @@ function unpack_response {
     test -f $tmpdir_server/rc || \
 	fatal "`pwd`/$tmpdir_server/rc does not exist or is not a regular file"
 
-    # See if there is a systemtap temp directory
+    # See if there is a systemtap temp directory.  There should be at least an empty one.
+    # ls -l $tmpdir_server
     tmpdir_stap=`cd $tmpdir_server && ls | grep stap......\$ 2>/dev/null`
     if test "X$tmpdir_stap" != "X"; then
 	test -d $tmpdir_server/$tmpdir_stap || \
@@ -567,6 +572,16 @@ function unpack_response {
 	    test $EUID = 0 && chown $EUID:$EUID $tmpdir_stap
 	fi
     fi
+
+    if test $keep_temps = 0; then
+      # Remove the output line due to the synthetic server-side -k
+      sed -i "/^Keeping temporary directory.*/ d" $tmpdir_server/stderr
+    fi
+
+    if test $p_phase = 5; then
+      # Remove the output line due to the synthetic server-side -p4
+      sed -i "/^.*\.ko$/ d" $tmpdir_server/stdout
+    fi
 }
 
 # function: find_and_connect_to_server
@@ -652,16 +667,21 @@ function find_and_connect_to_server {
 	# Remember which ssl certificate database was used to authenticate the chosen
         # server.
 	ssl_db=`${stap_exec_prefix}stap-find-servers $find_all | choose_server`
-	test "X$ssl_db" != "X" && return
+	if test "X$ssl_db" != "X"; then
+	    rm -f $tmpdir_client/connect
+	    return
+	fi
 
 	num_servers=`${stap_exec_prefix}stap-find-servers $find_all | wc -l`
     fi
 
     if test $num_servers = 0; then
+	rm -f $tmpdir_client/connect
 	fatal "Unable to find a server"
     fi
 
     cat $tmpdir_client/connect >&2
+    rm -f $tmpdir_client/connect
     fatal "Unable to connect to a server"
 }
 
@@ -703,8 +723,8 @@ function choose_server {
 # echo the name of the ssl certificate database used to successfully authenticate
 # the server.
 function send_receive {
-    local server=$1
-    local port=$2
+    local server="$1"
+    local port="$2"
 
     # The server must match the dns name on the certificate
     # and must be 'localhost' if the server is on the local host.
@@ -838,7 +858,7 @@ function maybe_call_staprun {
 	    # Run it in the background and wait for it. This
 	    # way any signals sent to us can be caught.
 	    if test $v_level -ge 2; then
-		echo "running `which staprun` $staprun_opts $module_name.ko" >&2
+		echo "running `staprun_PATH` $staprun_opts $module_name.ko" >&2
 	    fi
 	    eval `staprun_PATH` "$staprun_opts" $module_name.ko
 	    rc=$?
@@ -883,9 +903,9 @@ function staprun_PATH {
 #
 # Check the security of the given database directory.
 function check_db {
-    local dir=$1
-    local euid=$2
-    local user=$3
+    local dir="$1"
+    local euid="$2"
+    local user="$3"
     local rc=0
 
     # Check that we have been given a directory
@@ -960,7 +980,7 @@ function check_db {
 #
 # Check the security of the given database file.
 function check_db_file {
-    local file=$1
+    local file="$1"
     local rc=0
 
     # Check that we have been given a file
--- a/stap-server-connect.c
+++ b/stap-server-connect.c
@@ -1,9 +1,9 @@
 /*
   SSL server program listens on a port, accepts client connection, reads
-  the data into a temporary file, calls the systemtap server script and
-  then transmits the resulting fileback to the client.
+  the data into a temporary file, calls the systemtap translator and
+  then transmits the resulting file back to the client.
 
-  Copyright (C) 2008, 2009 Red Hat Inc.
+  Copyright (C) 2008-2010 Red Hat Inc.
 
   This file is part of systemtap, and is free software.  You can
   redistribute it and/or modify it under the terms of the GNU General Public
@@ -23,25 +23,36 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 #include <errno.h>
+#include <spawn.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <wordexp.h>
+#include <sys/param.h>
 
 #include <ssl.h>
 #include <nspr.h>
 #include <plgetopt.h>
 #include <nss.h>
 #include <pk11func.h>
+#include "config.h"
 
-#define READ_BUFFER_SIZE (60 * 1024)
 
 /* Global variables */
 static char             *password = NULL;
 static CERTCertificate  *cert = NULL;
 static SECKEYPrivateKey *privKey = NULL;
 static char             *dbdir   = NULL;
-static char requestFileName[] = "/tmp/stap.server.client.zip.XXXXXX";
-static char responseDirName[] = "/tmp/stap.server.XXXXXX";
-static char responseZipName[] = "/tmp/stap.server.XXXXXX.zip.XXXXXX";
-static const char *stapOptions = "";
+static const char       *stapOptions = "";
+
+
+static PRStatus spawn_and_wait (char **argv,
+                                const char* fd0, const char* fd1, const char* fd2, const char *pwd);
+
 
 static void
 Usage(const char *progName)
@@ -71,24 +82,27 @@ exitErr(char *function)
   exit(1);
 }
 
+
+
+
 /* Function:  readDataFromSocket()
  *
  * Purpose:  Read data from the socket into a temporary file.
  *
  */
-static SECStatus
-readDataFromSocket(PRFileDesc *sslSocket)
+static SECStatus readDataFromSocket(PRFileDesc *sslSocket, const char *requestFileName)
 {
   PRFileDesc *local_file_fd;
   PRFileInfo  info;
   PRInt32     numBytesRead;
   PRInt32     numBytesWritten;
   PRInt32     totalBytes;
+#define READ_BUFFER_SIZE 4096
   char        buffer[READ_BUFFER_SIZE];
 
   /* Open the output file.  */
   local_file_fd = PR_Open(requestFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
-			  PR_IRUSR | PR_IWUSR | PR_IRGRP | PR_IWGRP | PR_IROTH);
+			  PR_IRUSR | PR_IWUSR);
   if (local_file_fd == NULL)
     {
       fprintf (stderr, "could not open output file %s\n", requestFileName);
@@ -96,6 +110,7 @@ readDataFromSocket(PRFileDesc *sslSocket
     }
 
   /* Read the number of bytes to be received.  */
+  /* XXX: impose a limit to prevent disk space consumption DoS */
   numBytesRead = PR_Read(sslSocket, & info.size, sizeof (info.size));
   if (numBytesRead == 0) /* EOF */
     {
@@ -122,10 +137,11 @@ readDataFromSocket(PRFileDesc *sslSocket
 
       /* Write to stdout */
       numBytesWritten = PR_Write(local_file_fd, buffer, numBytesRead);
-      if (numBytesWritten < 0)
-	fprintf (stderr, "could not write to output file %s\n", requestFileName);
-      if (numBytesWritten != numBytesRead)
-	fprintf (stderr, "could not write to output file %s\n", requestFileName);
+      if (numBytesWritten < 0 || (numBytesWritten != numBytesRead))
+        {
+          fprintf (stderr, "could not write to output file %s\n", requestFileName);
+          break;
+        }
 #if DEBUG
       fprintf(stderr, "***** Connection read %d bytes.\n", numBytesRead);
 #if 0
@@ -316,27 +332,15 @@ authenticateSocket(PRFileDesc *sslSocket
  *
  */
 static SECStatus
-writeDataToSocket(PRFileDesc *sslSocket)
+writeDataToSocket(PRFileDesc *sslSocket, const char *responseFileName)
 {
   int         numBytes;
   PRFileDesc *local_file_fd;
-  PRFileInfo  info;
-  PRStatus    prStatus;
 
-  /* Try to open the local file named.	
-   * If successful, then write it to the client.
-   */
-  prStatus = PR_GetFileInfo(responseZipName, &info);
-  if (prStatus != PR_SUCCESS || info.type != PR_FILE_FILE || info.size < 0)
-    {
-      fprintf (stderr, "Input file %s not found\n", responseZipName);
-      return SECFailure;
-    }
-
-  local_file_fd = PR_Open(responseZipName, PR_RDONLY, 0);
+  local_file_fd = PR_Open(responseFileName, PR_RDONLY, 0);
   if (local_file_fd == NULL)
     {
-      fprintf (stderr, "Could not open input file %s\n", responseZipName);
+      fprintf (stderr, "Could not open input file %s\n", responseFileName);
       return SECFailure;
     }
 
@@ -356,7 +360,7 @@ writeDataToSocket(PRFileDesc *sslSocket)
 #if DEBUG
   /* Transmitted bytes successfully. */
   fprintf(stderr, "PR_TransmitFile wrote %d bytes from %s\n",
-	  numBytes, responseZipName);
+	  numBytes, responseFileName);
 #endif
 
   PR_Close(local_file_fd);
@@ -364,10 +368,303 @@ writeDataToSocket(PRFileDesc *sslSocket)
   return SECSuccess;
 }
 
+
+/* Run the translator on the data in the request directory, and produce output
+   in the given output directory. */
+static void handleRequest (const char* requestDirName, const char* responseDirName)
+{
+  char stapstdout[PATH_MAX];
+  char stapstderr[PATH_MAX];
+  char staprc[PATH_MAX];
+#define MAXSTAPARGC 1000 /* sorry, too lazy to dynamically allocate */
+  char* stapargv[MAXSTAPARGC];
+  int stapargc=0;
+  int rc;
+  wordexp_t words;
+  int i;
+  FILE* f;
+  int unprivileged = 0;
+  int stapargv_freestart = 0;
+
+  stapargv[stapargc++]= STAP_PREFIX "/bin/stap";
+
+  /* Transcribe stapOptions.  We use plain wordexp(3), since these
+     options are coming from the local trusted user, so malicious
+     content is not a concern. */
+
+  rc = wordexp (stapOptions, & words, WRDE_NOCMD|WRDE_UNDEF);
+  if (rc)
+    {
+      errWarn("cannot parse -s stap options");
+      return;
+    }
+  if (words.we_wordc+10 >= MAXSTAPARGC)  /* 10: padding for literal entries */
+    {
+      errWarn("too many -s options; MAXSTAPARGC");
+      return;
+    }
+
+  for (i=0; i<words.we_wordc; i++)
+    stapargv[stapargc++] = words.we_wordv[i];
+
+  stapargv[stapargc++] = "-k"; /* Need to keep temp files in order to package them up again. */
+
+  /* Process the saved command line arguments.  Avoid quoting/unquoting errors by
+     transcribing literally. */
+  stapargv[stapargc++] = "--client-options";
+  stapargv_freestart = stapargc;
+
+  for (i=1 ; ; i++)
+    {
+      char stapargfile[PATH_MAX];
+      FILE* argfile;
+      struct stat st;
+      char *arg;
+
+      if (stapargc >= MAXSTAPARGC)
+        {
+          errWarn("too many stap options; MAXSTAPARGC");
+          return;
+        }
+
+      snprintf (stapargfile, PATH_MAX, "%s/argv%d", requestDirName, i);
+
+      rc = stat(stapargfile, & st);
+      if (rc) break;
+
+      arg = malloc (st.st_size+1);
+      if (!arg)
+        {
+          errWarn("stap arg malloc");
+          return;
+        }
+
+      argfile = fopen(stapargfile, "r");
+      if (! argfile)
+        {
+          errWarn("stap arg fopen");
+          return;
+        }
+
+      rc = fread(arg, 1, st.st_size, argfile);
+      if (rc != st.st_size)
+        {
+          errWarn("stap arg fread");
+          return;
+        }
+
+      arg[st.st_size] = '\0';
+      stapargv[stapargc++] = arg; /* freed later */
+      fclose (argfile);
+    }
+
+  snprintf (stapstdout, PATH_MAX, "%s/stdout", responseDirName);
+  snprintf (stapstderr, PATH_MAX, "%s/stderr", responseDirName);
+
+  stapargv[stapargc] = NULL; /* spawn_and_wait expects NULL termination */
+
+  /* Check for the unprivileged flag; we need this so that we can decide to sign the module. */
+  for (i=0; i<stapargc; i++)
+    if (strcmp (stapargv[i], "--unprivileged") == 0)
+      unprivileged=1;
+  /* NB: but it's not that easy!  What if an attacker passes
+     --unprivileged as some sort of argument-parameter, so that the
+     translator does not interpret it as an --unprivileged mode flag,
+     but something else?  Then it could generate unrestrained modules,
+     but silly we might still sign it, and let the attacker get away
+     with murder.  And yet we don't want to fully getopt-parse the
+     args here for duplication of effort.
+
+     So let's do a hack: forcefully add --unprivileged to stapargv[]
+     near the front in this case, something which a later option
+     cannot undo. */
+  if (unprivileged)
+    {
+      if (stapargc+1 >= MAXSTAPARGC)
+        {
+          errWarn("too many stap options; MAXSTAPARGC");
+          return;
+        }
+
+      /* Shift all stapargv[] entries up one, including the NULL. */
+      for (i=stapargc; i>=1; i--)
+        stapargv[i+1]=stapargv[i];
+      stapargv_freestart ++; /* adjust for shift */
+
+      stapargv[1]="--unprivileged"; /* better not be resettable by later option */
+    }
+
+  /* All ready, let's run the translator! */
+  rc = spawn_and_wait (stapargv, "/dev/null", stapstdout, stapstderr, requestDirName);
+
+  /* Save the RC */
+  snprintf (staprc, PATH_MAX, "%s/rc", responseDirName);
+  f = fopen(staprc, "w");
+  if (f)
+    {
+      /* best effort basis */
+      fprintf(f, "%d", rc);
+      fclose(f);
+    }
+
+  /* Parse output to extract the -k-saved temprary directory.
+     XXX: bletch. */
+  f = fopen(stapstderr, "r");
+  if (!f)
+    {
+      errWarn("stap stderr open");
+      return;
+    }
+
+  while (1)
+    {
+      char line[PATH_MAX];
+      char *l = fgets(line, PATH_MAX, f); /* NB: normally includes \n at end */
+      if (!l) break;
+      char key[]="Keeping temporary directory \"";
+
+      /* Look for line from main.cxx: s.keep_tmpdir */
+      if (strncmp(l, key, strlen(key)) == 0 &&
+          l[strlen(l)-2] == '"')  /* "\n */
+        {
+          /* Move this directory under responseDirName.  We don't have to
+             preserve the exact stapXXXXXX suffix part, since stap-client
+             will accept anything ("stap......" regexp), and rewrite it
+             to a client-local string.
+
+             We don't just symlink because then we'd have to
+             remember to delete it later anyhow. */
+          char *mvargv[10];
+          char *orig_staptmpdir = & l[strlen(key)];
+          char new_staptmpdir[PATH_MAX];
+
+          orig_staptmpdir[strlen(orig_staptmpdir)-2] = '\0'; /* Kill the closing "\n */
+          snprintf(new_staptmpdir, PATH_MAX, "%s/stap000000", responseDirName);
+          mvargv[0]="mv";
+          mvargv[1]=orig_staptmpdir;
+          mvargv[2]=new_staptmpdir;
+          mvargv[3]=NULL;
+          rc = spawn_and_wait (mvargv, NULL, NULL, NULL, NULL);
+          if (rc != PR_SUCCESS)
+            errWarn("stap tmpdir move");
+
+          /* In unprivileged mode, if we have a module built, we need to
+             sign the sucker. */
+          if (unprivileged)
+            {
+              glob_t globber;
+              char pattern[PATH_MAX];
+              snprintf (pattern,PATH_MAX,"%s/*.ko", new_staptmpdir);
+              rc = glob (pattern, GLOB_ERR, NULL, &globber);
+              if (rc)
+                errWarn("stap tmpdir .ko glob");
+              else if (globber.gl_pathc != 1)
+                errWarn("stap tmpdir too many .ko globs");
+              else
+                {
+                  char *signargv [10];
+                  signargv[0] = STAP_PREFIX "/libexec/systemtap/stap-sign-module";
+                  signargv[1] = globber.gl_pathv[0];
+                  signargv[2] = dbdir;
+                  signargv[3] = NULL;
+                  rc = spawn_and_wait (signargv, NULL, NULL, NULL, NULL);
+                  if (rc != PR_SUCCESS)
+                    errWarn("stap-sign-module");
+                }
+            }
+        }
+
+      /* XXX: What about uprobes.ko? */
+    }
+
+  /* Free up all the arg string copies.  Note that the first few were alloc'd
+     by wordexp(), which wordfree() frees; others were hand-set to literal strings. */
+  for (i= stapargv_freestart; i<stapargc; i++)
+    free (stapargv[i]);
+  wordfree (& words);
+
+  /* Sorry about the inconvenience.  C string/file processing is such a pleasure. */
+}
+
+
+/* A frontend for posix_spawnp that waits for the child process and
+   returns overall success or failure. */
+static PRStatus spawn_and_wait (char ** argv,
+                                const char* fd0, const char* fd1, const char* fd2, const char *pwd)
+{
+  pid_t pid;
+  int rc;
+  int status;
+  extern char** environ;
+  posix_spawn_file_actions_t actions;
+  int dotfd = -1;
+
+#define CHECKRC(msg) do { if (rc) { errWarn(msg); return PR_FAILURE; } } while (0)
+
+  rc = posix_spawn_file_actions_init (& actions);
+  CHECKRC ("spawn file actions ctor");
+  if (fd0) {
+    rc = posix_spawn_file_actions_addopen(& actions, 0, fd0, O_RDONLY, 0600);
+    CHECKRC ("spawn file actions fd0");
+  }
+  if (fd1) {
+    rc = posix_spawn_file_actions_addopen(& actions, 1, fd1, O_WRONLY|O_CREAT, 0600);
+    CHECKRC ("spawn file actions fd1");
+  }
+  if (fd2) {
+    rc = posix_spawn_file_actions_addopen(& actions, 2, fd2, O_WRONLY|O_CREAT, 0600);
+    CHECKRC ("spawn file actions fd2");
+  }
+
+  /* change temporarily to a directory if requested */
+  if (pwd)
+    {
+      dotfd = open (".", O_RDONLY);
+      if (dotfd < 0)
+        {
+          errWarn ("spawn getcwd");
+          return PR_FAILURE;
+        }
+
+      rc = chdir (pwd);
+      CHECKRC ("spawn chdir");
+    }
+
+  rc = posix_spawnp (& pid, argv[0], & actions, NULL, argv, environ);
+  /* NB: don't react to rc!=0 right away; need to chdir back first. */
+
+  if (pwd && dotfd >= 0)
+    {
+      int subrc;
+      subrc = fchdir (dotfd);
+      subrc |= close (dotfd);
+      if (subrc)
+        errWarn("spawn unchdir");
+    }
+
+  CHECKRC ("spawn");
+
+  rc = waitpid (pid, &status, 0);
+  if ((rc!=pid) || !WIFEXITED(status))
+    {
+      errWarn ("waitpid");
+      return PR_FAILURE;
+    }
+
+  rc = posix_spawn_file_actions_destroy (&actions);
+  CHECKRC ("spawn file actions dtor");
+
+  return WEXITSTATUS(status) ? PR_FAILURE : PR_SUCCESS;
+#undef CHECKRC
+}
+
+
+
 /* Function:  int handle_connection()
  *
- * Purpose:  Handle a connection to a socket.
- *
+ * Purpose: Handle a connection to a socket.  Copy in request zip
+ * file, process it, copy out response.  Temporary directories are
+ * created & destroyed here.
  */
 static SECStatus
 handle_connection(PRFileDesc *tcpSocket)
@@ -376,11 +673,16 @@ handle_connection(PRFileDesc *tcpSocket)
   SECStatus          secStatus = SECFailure;
   PRStatus           prStatus;
   PRSocketOptionData socketOption;
-  PRFileInfo         info;
-  char              *cmdline;
-  char              *stap_server_prefix;
   int                rc;
   char              *rc1;
+  char               tmpdir[PATH_MAX];
+  char               requestFileName[PATH_MAX];
+  char               requestDirName[PATH_MAX];
+  char               responseDirName[PATH_MAX];
+  char               responseFileName[PATH_MAX];
+  char              *argv[10]; /* we use fewer than these in all the posix_spawn's below. */
+
+  tmpdir[0]='\0'; /* prevent cleanup-time /bin/rm of uninitialized directory */
 
   /* Make sure the socket is blocking. */
   socketOption.option             = PR_SockOpt_Nonblocking;
@@ -409,50 +711,49 @@ handle_connection(PRFileDesc *tcpSocket)
       goto cleanup;
     }
 
-  /* Create a temporary files and directories.  */
-  memcpy (requestFileName + sizeof (requestFileName) - 1 - 6, "XXXXXX", 6);
-  rc = mkstemp(requestFileName);
-  if (rc == -1)
+  snprintf(tmpdir, PATH_MAX, "%s/stap-server.XXXXXX", getenv("TMPDIR") ?: "/tmp");
+  rc1 = mkdtemp(tmpdir);
+  if (! rc1)
     {
-      fprintf (stderr, "Could not create temporary file %s\n", requestFileName);
+      fprintf (stderr, "Could not create temporary directory %s\n", tmpdir);
       perror ("");
       secStatus = SECFailure;
+      tmpdir[0]=0; /* prevent /bin/rm */
       goto cleanup;
     }
 
-  memcpy (responseDirName + sizeof (responseDirName) - 1 - 6, "XXXXXX", 6);
-  rc1 = mkdtemp(responseDirName);
-  if (! rc1)
+  /* Create a temporary files names and directories.  */
+  snprintf (requestFileName, PATH_MAX, "%s/request.zip", tmpdir);
+
+  snprintf (requestDirName, PATH_MAX, "%s/request", tmpdir);
+  rc = mkdir(requestDirName, 0700);
+  if (rc)
     {
-      fprintf (stderr, "Could not create temporary directory %s\n", responseDirName);
+      fprintf (stderr, "Could not create temporary directory %s\n", requestDirName);
       perror ("");
       secStatus = SECFailure;
       goto cleanup;
     }
 
-  memcpy (responseZipName, responseDirName, sizeof (responseDirName) - 1);
-  memcpy (responseZipName + sizeof (responseZipName) - 1 - 6, "XXXXXX", 6);
-  rc = mkstemp(responseZipName);
-  if (rc == -1)
+  snprintf (responseDirName, PATH_MAX, "%s/response", tmpdir);
+  rc = mkdir(responseDirName, 0700);
+  if (rc)
     {
-      fprintf (stderr, "Could not create temporary file %s\n", responseZipName);
+      fprintf (stderr, "Could not create temporary directory %s\n", responseDirName);
       perror ("");
       secStatus = SECFailure;
-
-      /* Remove this so that the other temp files will get removed in cleanup.  */
-      prStatus = PR_RmDir (responseDirName);
-      if (prStatus != PR_SUCCESS)
-	errWarn ("PR_RmDir");
       goto cleanup;
     }
 
+  snprintf (responseFileName, PATH_MAX, "%s/response.zip", tmpdir);
+
   /* Read data from the socket.
    * If the user is requesting/requiring authentication, authenticate
    * the socket.  */
 #if DEBUG
   fprintf(stdout, "\nReading data from socket...\n\n");
 #endif
-  secStatus = readDataFromSocket(sslSocket);
+  secStatus = readDataFromSocket(sslSocket, requestFileName);
   if (secStatus != SECSuccess)
     goto cleanup;
 
@@ -466,32 +767,42 @@ handle_connection(PRFileDesc *tcpSocket)
     }
 #endif
 
-  /* Call the stap-server script.  */
-  stap_server_prefix = getenv("SYSTEMTAP_SERVER_SCRIPTS") ?: BINDIR;
-  cmdline = PORT_Alloc(strlen (stap_server_prefix) + sizeof ("/stap-server") + 1 +
-		       sizeof (requestFileName) + 1 +
-		       sizeof (responseDirName) + 1 +
-		       sizeof (responseZipName) + 1 +
-		       strlen (dbdir) + 1 +
-		       1 + strlen (stapOptions) + 1 +
-		       1);
-  if (! cmdline) {
-    errWarn ("PORT_Alloc");
-    secStatus = SECFailure;
-    goto cleanup;
-  }
-
-  sprintf (cmdline, "%s/stap-server %s %s %s %s '%s'", stap_server_prefix,
-	   requestFileName, responseDirName, responseZipName, dbdir,
-	   stapOptions);
-  rc = system (cmdline);
+  /* Unzip the request. */
+  argv[0]="unzip";
+  argv[1]="-d";
+  argv[2]=requestDirName;
+  argv[3]=requestFileName;
+  rc = spawn_and_wait(argv, NULL, NULL, NULL, NULL);
+  if (rc != PR_SUCCESS)
+    {
+      errWarn ("request unzip");
+      secStatus = SECFailure;
+      goto cleanup;
+    }
 
-  PR_Free (cmdline);
+  /* Handle the request zip file.  An error therein should still result
+     in a response zip file (containing stderr etc.) so we don't have to
+     have a result code here.  */
+  handleRequest(requestDirName, responseDirName);
+
+  /* Zip the response. */
+  argv[0]="zip";
+  argv[1]="-r";
+  argv[2]=responseFileName;
+  argv[3]=".";
+  argv[4]=NULL;
+  rc = spawn_and_wait(argv, NULL, NULL, NULL, responseDirName);
+  if (rc != PR_SUCCESS)
+    {
+      errWarn ("response zip");
+      secStatus = SECFailure;
+      goto cleanup;
+    }
 
 #if DEBUG
   fprintf(stdout, "\nWriting data to socket...\n\n");
 #endif
-  secStatus = writeDataToSocket(sslSocket);
+  secStatus = writeDataToSocket(sslSocket, responseFileName);
 
 cleanup:
   /* Close down the socket. */
@@ -499,17 +810,16 @@ cleanup:
   if (prStatus != PR_SUCCESS)
     errWarn("PR_Close");
 
-  /* Attempt to remove temporary files, unless the temporary directory was
-     not deleted by the server script.  */
-  prStatus = PR_GetFileInfo(responseDirName, &info);
-  if (prStatus != PR_SUCCESS)
+  if (tmpdir[0])
     {
-      prStatus = PR_Delete (requestFileName);
-      if (prStatus != PR_SUCCESS)
-	errWarn ("PR_Delete");
-      prStatus = PR_Delete (responseZipName);
-      if (prStatus != PR_SUCCESS)
-	errWarn ("PR_Delete");
+      /* Remove the whole tmpdir and all that lies beneath. */
+      argv[0]="rm";
+      argv[1]="-r";
+      argv[2]=tmpdir;
+      argv[3]=NULL;
+      rc = spawn_and_wait(argv, NULL, NULL, NULL, NULL);
+      if (rc != PR_SUCCESS)
+        errWarn ("tmpdir cleanup");
     }
 
   return secStatus;
@@ -544,17 +854,11 @@ accept_connection(PRFileDesc *listenSock
 	  break;
 	}
 
+      /* XXX: alarm() or somesuch to set a timeout. */
+      /* XXX: fork() or somesuch to handle concurrent requests. */
+
       /* Accepted the connection, now handle it. */
       /*result =*/ handle_connection (tcpSocket);
-#if 0 /* Not necessary */
-      if (result != SECSuccess)
-	{
-	  prStatus = PR_Close(tcpSocket);
-	  if (prStatus != PR_SUCCESS)
-	    exitErr("PR_Close");
-	  break;
-	}
-#endif
     }
 
 #if DEBUG
--- a/stap-serverd
+++ b/stap-serverd
@@ -320,11 +320,19 @@ function advertise_presence {
 function listen {
     # The stap-server-connect program will listen forever
     # accepting requests.
-    ${stap_exec_prefix}stap-server-connect \
-	-p $port -n $nss_cert -d $ssl_db -w $nss_pw \
-	-s "$stap_options" \
-	2>&1 &
-    wait '%${stap_exec_prefix}stap-server-connect' >/dev/null 2>&1
+    # CVE-2009-4273 ... or at least, until resource limits fire
+    while true; do # NB: loop to avoid DoS by deliberate rlimit-induced halt
+        # NB: impose resource limits in case of mischevious data inducing
+        # too much / long computation
+        (ulimit -f 50000 -s 1000 -t 60 -u 20 -v 500000;
+         exec ${stap_pkglibexecdir}stap-server-connect \
+	   -p $port -n $nss_cert -d $ssl_db -w $nss_pw \
+	   -s "$stap_options") &
+        stap_server_connect_pid=$!
+        wait
+        # NB: avoid superfast spinning in case of a ulimit or other failure
+        sleep 1
+    done 2>&1
 }
 
 # function: check_db DBNAME
@@ -562,8 +570,8 @@ function terminate {
     wait '%avahi-publish-service' >/dev/null 2>&1
 
     # Kill any running 'stap-server-connect' job.
-    kill -s SIGTERM '%${stap_exec_prefix}stap-server-connect' 2> /dev/null
-    wait '%${stap_exec_prefix}stap-server-connect'  >/dev/null 2>&1
+    kill -s SIGTERM $stap_server_connect_pid >/dev/null 2>&1
+    wait $stap_server_connect_pid >/dev/null 2>&1
 
     exit
 }
--- /dev/null
+++ b/testsuite/systemtap.server/client_args.exp
@@ -0,0 +1,119 @@
+set test "Invalid Server Client Arguments"
+
+# Test that stap on the server side will correctly accept/reject certain
+# arguments in unprivileged mode.
+set test_file $srcdir/systemtap.server/test.stp
+
+# Test invalid combinations.
+set error_regexp ".*(ERROR)|(You can't specify .* when --unprivileged is specified).*"
+
+set invalid_options [list \
+  "--unprivileged --client-options -B X=Y" \
+  "--unprivileged --client-options -D X=Y" \
+  "--unprivileged --client-options -I /tmp" \
+  "--unprivileged --client-options -m test" \
+  "--unprivileged --client-options -R /tmp" \
+  "--unprivileged --client-options -B X=Y -D X=Y -I /tmp -m test -R /tmp -r [exec uname -r]" \
+  "--client-options --unprivileged -B X=Y" \
+  "--client-options --unprivileged -D X=Y" \
+  "--client-options --unprivileged -I /tmp" \
+  "--client-options --unprivileged -m test" \
+  "--client-options --unprivileged -R /tmp" \
+  "--client-options --unprivileged -B X=Y -D X=Y -I /tmp -m test -R /tmp -r [exec uname -r]" \
+  "--client-options -B X=Y --unprivileged" \
+  "--client-options -D X=Y --unprivileged" \
+  "--client-options -I /tmp --unprivileged" \
+  "--client-options -m test --unprivileged" \
+  "--client-options -R /tmp --unprivileged" \
+  "--client-options -B X=Y -D X=Y -I /tmp -m test -R /tmp -r [exec uname -r] --unprivileged" \
+  "--client-options -R /path" \
+  "-D \"foo;bar\"" \
+  "-D 2=4" \
+  "-D \"foo;bar\"" \
+  "--client-options -r /path" \
+  "-S /path" \
+  "--client-options -q" \
+]
+
+foreach options $invalid_options {
+    verbose -log "eval exec stap $options"
+    catch {eval exec stap $test_file -p1 $options} res_stap
+    verbose -log $res_stap
+
+    if {[regexp $error_regexp $res_stap]} {
+	pass "$test: $options"
+    } else {
+	fail "$test: $options"
+    }
+}
+
+# Test valid combinations
+# stap_run_exact (used below) only works for 'make installcheck'
+if {[info procs installtest_p] != "" && ![installtest_p]} { untested $test; return }
+
+set test "Valid Server Client Arguments"
+
+set no_error_result "# parse tree dump
+# file $test_file
+probe begin{
+exit()
+}
+
+"
+
+set valid_options [list \
+  "-a i386" \
+  "-B X=Y" \
+  "-D X=Y" \
+  "-I /tmp" \
+  "-m test" \
+  "-r [exec uname -r]" \
+  "-a i386 -B X=Y -D X=Y -I /tmp -m test -r [exec uname -r]" \
+  "--unprivileged" \
+  "--unprivileged -a i386" \
+  "--unprivileged -B X=Y" \
+  "--unprivileged -D X=Y" \
+  "--unprivileged -I /tmp" \
+  "--unprivileged -m test" \
+  "--unprivileged -R /tmp" \
+  "--unprivileged -r [exec uname -r]" \
+  "--unprivileged -a i386 -B X=Y -D X=Y -I /tmp -m test -R /tmp -r [exec uname -r]" \
+  "--client-options" \
+  "--client-options -a i386" \
+  "--client-options -D X=Y" \
+  "--client-options -I /tmp" \
+  "--client-options -m test" \
+  "--client-options -r [exec uname -r]" \
+  "--client-options -a i386 -D X=Y -I /tmp -m test -r [exec uname -r]" \
+  "--unprivileged --client-options" \
+  "--client-options --unprivileged" \
+  "--unprivileged -a i386 --client-options" \
+  "--unprivileged -B X=Y --client-options" \
+  "--unprivileged -D X=Y --client-options" \
+  "--unprivileged -I /tmp --client-options" \
+  "--unprivileged -m test --client-options" \
+  "--unprivileged -R /tmp --client-options" \
+  "--unprivileged -r [exec uname -r] --client-options" \
+  "--unprivileged -a i386 -B X=Y -D X=Y -I /tmp -m test -R /tmp -r [exec uname -r] --client-options" \
+  "-a i386 --unprivileged --client-options" \
+  "-B X=Y --unprivileged --client-options" \
+  "-D X=Y --unprivileged --client-options" \
+  "-I /tmp --unprivileged --client-options" \
+  "-m test --unprivileged --client-options" \
+  "-R /tmp --unprivileged --client-options" \
+  "-r [exec uname -r] --unprivileged --client-options" \
+  "-a i386 -B X=Y -D X=Y -I /tmp -m test -R /tmp -r [exec uname -r] --unprivileged --client-options" \
+  "-a i386 --client-options --unprivileged" \
+  "-B X=Y --client-options --unprivileged" \
+  "-D X=Y --client-options --unprivileged" \
+  "-I /tmp --client-options --unprivileged" \
+  "-m test --client-options --unprivileged" \
+  "-R /tmp --client-options --unprivileged" \
+  "-r [exec uname -r] --client-options --unprivileged" \
+  "-a i386 -B X=Y -D X=Y -I /tmp -m test -R /tmp -r [exec uname -r] --client-options --unprivileged" \
+]
+
+set ::result_string "$no_error_result"
+foreach options $valid_options {
+    eval stap_run_exact {"$test: $options"} $test_file -p1 $options
+}
--- a/testsuite/systemtap.server/hello.stp
+++ b/testsuite/systemtap.server/hello.stp
@@ -1,4 +1,4 @@
-#! stap 
+#! stap -p5
 
 probe begin
 {
--- a/testsuite/systemtap.server/server.exp
+++ b/testsuite/systemtap.server/server.exp
@@ -16,11 +16,7 @@ foreach file [lsort [glob -nocomplain $s
     # some tests are known to fail.
     switch $test {
         "buildok/perfmon01.stp with server" {setup_kfail 909 *-*-*}
-        "buildok/twentyseven.stp with server" {setup_kfail 4166 *-*-*}
-        "buildok/sched_test.stp with server" {setup_kfail 1155 *-*-*}
-        "buildok/process_test.stp with server" {setup_kfail 1155 *-*-*}
         "buildok/rpc-all-probes.stp with server" {setup_kfail 4413 *-*-*}
-        "buildok/nfs-all-probes.stp with server" {setup_kfail 4413 *-*-*}
     }
     if {$rc == 0} { pass $test } else { fail $test }
 }
--- /dev/null
+++ b/testsuite/systemtap.server/server_args.exp
@@ -0,0 +1,182 @@
+set test "Server Argument Test"
+
+# Each test will take 3 seconds or more due to the use of avahi to browse for
+# servers, so limit iterations to some small but reasonable number.
+set iterations 10
+
+# Allow seeding the random number generator using an environment variable in
+# order to facilitate reproducing a given series of tests. Otherwise seed it
+# using the current time.
+if [info exists env(STAP_RANDOM_SEED)] {
+    set random_seed $env(STAP_RANDOM_SEED)
+} else {
+    set random_seed [clock seconds]
+}
+verbose -log "Random seed is $random_seed"
+expr srand($random_seed)
+
+proc stap_direct_and_with_client {stap stap_client options} {
+    # tcl's 'eval' creates a string containing the arguments and
+    # recursively passes it to the tcl interpreter. Special
+    # characters need to be quoted.
+    regsub -all "\[\"\\\\;\]" $options {\\\0} options
+    regsub -all "\[\n\]" $options {\\n} options
+
+    verbose -log "eval exec $stap $options"
+    catch {eval exec $stap $options} res_stap
+    verbose -log $res_stap
+
+    # Now run it against stap-client
+    verbose -log "eval exec $stap_client $options"
+    catch {eval exec $stap_client $options} res_stap_client
+    verbose -log $res_stap_client
+
+    # Now check the output
+    set n 0
+    set expected [split $res_stap "\n"]
+    set received [split $res_stap_client "\n"]
+    foreach line $received {
+	set expected_line [lindex $expected $n]
+	# Some messages contain the names of files or directories
+	# and will be prefixed for the client.
+	if {[regexp "^              (.*)" $expected_line match data]} {
+	    # Special characters in the regexp need to be quoted.
+	    regsub -all "\[\"\\\\;\]" $data {\\\0} data
+	    if {[regexp "^              tapsets.*/$data" $line]} {
+		incr n
+		continue
+	    }
+	    if {[regexp "^              runtime.*/$data" $line]} {
+		incr n
+		continue
+	    }
+	    # Some messages contain the name of the module based on the
+	    # process id.
+	    if {[regexp "^              stap_\[0-9\]*" $expected_line] &&
+		[regexp "^              stap_\[0-9\]*" $line]} {
+		incr n
+		continue
+	    }
+	} else {
+	    if {[regexp "^Input file '(.*)' is empty or missing." $expected_line match data]} {
+		# Special characters in the regexp need to be quoted.
+		regsub -all "\[\"\\\\;\]" $data {\\\0} data
+		if {[regexp "^Input file 'script.*/$data' is empty or missing." $line]} {
+		    incr n
+		    continue
+		}
+	    }
+	}
+
+	# Otherwise the output must be identical.
+	if {! [string equal $line $expected_line]} {
+	    break
+	}
+	incr n
+    }
+    if {$n == [llength $expected] && [llength $expected] == [llength $received]} {
+	# Test passes
+	return 1
+    }
+    # Test fails
+    send_log "line [expr $n + 1]:\n"
+    send_log "Expected: \"[lindex $expected $n]\"\n"
+    send_log "Got     : \"$line\"\n"
+    return 0
+}
+
+# ************ Start of mainline *************
+
+# Don't attempt these tests if the client/server are not available
+# Start a systemtap server, if one is not already started.
+if {! [use_server_p]} then {
+    if {! [setup_server]} then {
+	untested "$test"
+	return
+    }
+}
+
+# Make sure we call the correct instances of stap and stap-client.
+# There is a copy of 'stap-client' on the PATH as 'stap'.
+if {[installtest_p]} then {
+    # For 'make installcheck', use both from the location of the actual
+    # stap-client on the PATH.
+    set stap_client [exec which stap-client]
+    set stap [exec dirname $stap_client]/stap
+} else {
+    # For 'make check' use stap-client from the source tree and use stap from
+    # the build tree.
+    set stap_client $srcdir/../stap-client
+    set stap [exec pwd]/../stap
+}
+
+# Test some argument strings which have failed in the past. This is useful
+# for debugging a currently failing case and helps to ensure that previously
+# fixed cases do not regress.
+set previously_fixed [list \
+  "-p1 -I8o\\2ie -e'1\\ -D\n\" -c" \
+  "-p1 -Ira\\3;c g -e0fle'qq -Dr/316k\\o8 -cjyoc\n3" \
+  "-p1 -I6p3 -elc -Dqgsgv' -c" \
+  "-p1 -I\"vyv;z -ej\"/3 -D/ 01qck\n -c3u55zut" \
+  "-p1 -I1 -eo9e\nx047q -D9xyefk0a -cvl98/x1'i" \
+  "-p1 -c; test.stp" \
+  "-p1 -I4hgy96 -e5oo39p -Ddx8v -c4;" \
+  "-p1 -I -esq3wors -Dz -c*eibz8h2e" \
+  "-p1 -I a -em339db5 -Du2;c0ps -ch9o\\" \
+  "-p1 -Ipfjps4 -ebug4dc -Du8vd fvkl -c" \
+  "-p1 -I0\"nspzjyf -e5r3up8h -Dmi;ojp9m -cx;a2fat" \
+  "-p1 -Iu -ek7;r -Dcu\"; -c\"hc" \
+  "-p1 -Icd4fidq  m40mv -edn -D;8ha\\cjr -c1*vnq" \
+  "-p1 -I;3 -er8e -D -cb6k29z" \
+  "-p1 -Ircj -e -D -c\\vmww" \
+  "-p1 -Illc5 -e65wof9 qr*=x7x5 -D -cgx;" \
+  "-p1 -Iyaj420=3 -e\" -D -cd'5mi" \
+  "-p1 -Ir -e -D29\\ -cj2szt;4" \
+  "-p1 -Ibno3=b4sk -e' -Dg2-j;e -c2ijx'" \
+  "-p1 -I285v7pl -eo5\\0 -D86s -c-c*v" \
+]
+
+set i 0
+foreach options $previously_fixed {
+    if {[stap_direct_and_with_client $stap $stap_client $options]} {
+	pass "$test $i"
+    } else {
+	fail "$test $i"
+    }
+    incr i
+}
+
+# Generate semi-random arguments containing with potential problem characters.
+# Check that running systemtap with the client/server generates output
+# comparable to running stap directly.
+set dangerous_options [list "-I" "-e" "-D" "-c" "-S"]
+# NB: Other options could be candidates here, like -r and -B, but
+# there stap-server imposes more restrictions than local stap, so
+# this simple error-matching test cannot use them.
+set argchars "0123456789/;*'=-\\\"\n abcdefghijklmnopqrstuvwxyz"
+
+for {set i 0} {$i < $iterations} {incr i} {
+    verbose -log "Iteration $i"
+
+    # First generate an argument string
+    set options "-p1"
+    foreach option $dangerous_options {
+	set options "$options $option"
+	set limit [expr int(rand() * 10)]
+	for {set c 0} {$c < $limit} {incr c} {
+	    set options "$options[string index $argchars [expr int(rand() * [string length $argchars])]]"
+	}
+    }
+
+    # Now test it against stap and stap-client
+    if {[stap_direct_and_with_client $stap $stap_client $options]} {
+	pass "Fuzzing $test $i"
+    } else {
+	fail "Fuzzing $test $i"
+    }
+}
+
+# Shudown the server, if we started it.
+if {! [use_server_p]} then {
+    shutdown_server
+}
--- /dev/null
+++ b/testsuite/systemtap.server/test.stp
@@ -0,0 +1 @@
+probe begin { exit (); }
--- a/util.cxx
+++ b/util.cxx
@@ -1,5 +1,5 @@
 // Copyright (C) Andrew Tridgell 2002 (original file)
-// Copyright (C) 2006, 2009 Red Hat Inc. (systemtap changes)
+// Copyright (C) 2006-2010 Red Hat Inc. (systemtap changes)
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License as
@@ -19,6 +19,8 @@
 #include "sys/sdt.h"
 #include <stdexcept>
 #include <cerrno>
+#include <map>
+#include <string>
 
 extern "C" {
 #include <fcntl.h>
@@ -30,6 +32,7 @@ extern "C" {
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include <regex.h>
 }
 
 using namespace std;
@@ -374,4 +377,35 @@ kill_stap_spawn(int sig)
   return spawned_pid ? kill(spawned_pid, sig) : 0;
 }
 
+
+void assert_regexp_match (const string& name, const string& value, const string& re)
+{
+  typedef map<string,regex_t*> cache;
+  static cache compiled;
+  cache::iterator it = compiled.find (re);
+  regex_t* r = 0;
+  if (it == compiled.end())
+    {
+      r = new regex_t;
+      int rc = regcomp (r, re.c_str(), REG_ICASE|REG_NOSUB|REG_EXTENDED);
+      if (rc) {
+        cerr << "regcomp " << re << " (" << name << ") error rc=" << rc << endl;
+        exit(1);
+      }
+      compiled[re] = r;
+    }
+  else
+    r = it->second;
+
+  // run regexec
+  int rc = regexec (r, value.c_str(), 0, 0, 0);
+  if (rc)
+    {
+      cerr << "ERROR: Safety pattern mismatch for " << name
+           << " ('" << value << "' vs. '" << re << "') rc=" << rc << endl;
+      exit(1);
+    }
+}
+
+
 /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */
--- a/util.h
+++ b/util.h
@@ -18,7 +18,7 @@ const std::string cmdstr_quoted(const st
 std::string git_revision(const std::string& path);
 int stap_system(int verbose, const std::string& command);
 int kill_stap_spawn(int sig);
-
+void assert_regexp_match (const std::string& name, const std::string& value, const std::string& re);
 
 // stringification generics
 
openSUSE Build Service is sponsored by