Collection of miscellaneous ill-documented tools. 1.0
authormdw <mdw>
Wed, 1 Oct 2003 00:08:57 +0000 (00:08 +0000)
committermdw <mdw>
Wed, 1 Oct 2003 00:08:57 +0000 (00:08 +0000)
.cvsignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
cdb-assign [new file with mode: 0755]
cdb-check-domain.c [new file with mode: 0644]
cdb-list [new file with mode: 0755]
cdb-probe.c [new file with mode: 0644]
not.c [new file with mode: 0644]
qmail-checkspam.c [new file with mode: 0644]
splitconf [new file with mode: 0755]
unfwd [new file with mode: 0755]
xtitle.c [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..3910527
--- /dev/null
@@ -0,0 +1 @@
+cdb-check-domain cdb-probe not qmail-checkspam
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..841e7fd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+## Makefile for miscellaneous stuff
+##
+## No proper build system here.  Just kludgy hacks.
+
+PROGS = \
+       qmail-checkspam not \
+       cdb-probe cdb-check-domain \
+       xtitle.so
+
+CC = gcc
+LD = gcc
+CFLAGS = -O2 -g -pedantic -Wall
+LINK = $(LD) $(LDFLAGS) -o $@ $^
+
+all: $(PROGS)
+
+qmail-checkspam: qmail-checkspam.o
+       $(LINK) -lspamc
+
+cdb-probe: cdb-probe.o
+       $(LINK) -lfreecdb
+
+cdb-check-domain: cdb-check-domain.o
+       $(LINK) -lfreecdb
+
+not: not.o
+       $(LINK)
+
+xtitle.o: xtitle.c
+       $(CC) $(CFLAGS) -c -fpic -I/usr/include/bash -DBASH_BUILTIN -o $@ $^
+xtitle.so: xtitle.o
+       $(LINK) -shared
+
+clean:; rm -f *.o $(PROGS)
+
+.PHONY: all clean
diff --git a/cdb-assign b/cdb-assign
new file mode 100755 (executable)
index 0000000..5f2f44e
--- /dev/null
@@ -0,0 +1,15 @@
+#! /usr/bin/perl
+
+use CDB_File;
+
+@ARGV >= 1 or die "usage: $0 CDB [INPUT ...]\n";
+$f = shift;
+$c = CDB_File->new($f, "$f.new") or die "CDB_File->new: $!\n";
+while (<>) {
+  chomp;
+  next if m'^\s*(\#|$)';
+  m'^\s*([-\w]+)\s*=\s*(.*\S|)\s*$' or die "bad assignment `$_'\n";
+  $c->insert($1, $2);
+}
+$c->finish();
+exit 0;
diff --git a/cdb-check-domain.c b/cdb-check-domain.c
new file mode 100644 (file)
index 0000000..bac6481
--- /dev/null
@@ -0,0 +1,38 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "freecdb.h"
+
+const char *prog;
+
+static void check(const char *p)
+{
+  int rc;
+  uint32_t l;
+
+  rc = cdb_seek(0, p, strlen(p), &l);
+  if (rc < 0) {
+    fprintf(stderr, "%s: cdb_seek: %s\n", prog, strerror(errno));
+    exit(111);
+  }
+  if (rc) exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+  const char *p;
+
+  prog = argv[0];
+  if (argc != 2) {
+    fprintf(stderr, "usage: %s KEY <CDB\n", prog);
+    exit(111);
+  }
+  p = argv[1];
+  while (p && *p) {
+    check(p);
+    p = strchr(p + 1, '.');
+  }
+  return (1);
+}
diff --git a/cdb-list b/cdb-list
new file mode 100755 (executable)
index 0000000..b0403ea
--- /dev/null
+++ b/cdb-list
@@ -0,0 +1,15 @@
+#! /usr/bin/perl
+
+use CDB_File;
+
+@ARGV >= 1 or die "usage: $0 CDB [INPUT ...]\n";
+$f = shift;
+$c = CDB_File->new($f, "$f.new") or die "CDB_File->new: $!\n";
+while (<>) {
+  chomp;
+  next if m'^\s*(\#|$)';
+  m'^\s*(.*\S|)\s*$';
+  $c->insert($1, "");
+}
+$c->finish();
+exit 0;
diff --git a/cdb-probe.c b/cdb-probe.c
new file mode 100644 (file)
index 0000000..cb19a29
--- /dev/null
@@ -0,0 +1,32 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "freecdb.h"
+
+const char *prog;
+
+static void check(const char *p)
+{
+  int rc;
+  uint32_t l;
+
+  rc = cdb_seek(0, p, strlen(p), &l);
+  if (rc < 0) {
+    fprintf(stderr, "%s: cdb_seek: %s\n", prog, strerror(errno));
+    exit(111);
+  }
+  if (rc) exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+  prog = argv[0];
+  if (argc != 2) {
+    fprintf(stderr, "usage: %s KEY <CDB\n", prog);
+    exit(111);
+  }
+  check(argv[1]);
+  return (1);
+}
diff --git a/not.c b/not.c
new file mode 100644 (file)
index 0000000..75d8835
--- /dev/null
+++ b/not.c
@@ -0,0 +1,35 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/wait.h>
+
+int main(int argc, char *argv[])
+{
+  pid_t kid;
+  int rc;
+
+  if (argc < 2) {
+    fprintf(stderr, "usage: %s PROG [ARGS]\n", argv[0]);
+    exit(111);
+  }
+  kid = fork();
+  if (kid < 0) {
+    fprintf(stderr, "%s: fork: %s\n", argv[0], strerror(errno));
+    exit(111);
+  }
+  if (!kid) {
+    execvp(argv[1], argv + 1);
+    fprintf(stderr, "%s: exec %s: %s\n", argv[0], argv[1], strerror(errno));
+    exit(111);
+  }
+  if (waitpid(kid, &rc, 0) <= 0) {
+    fprintf(stderr, "%s: wait: %s\n", argv[0], strerror(errno));
+    exit(111);
+  }
+  if (!WIFEXITED(rc) || WEXITSTATUS(rc) == 111)
+    exit(111);
+  exit(WEXITSTATUS(rc) == 0 ? 100 : 0);
+}
diff --git a/qmail-checkspam.c b/qmail-checkspam.c
new file mode 100644 (file)
index 0000000..de9c425
--- /dev/null
@@ -0,0 +1,164 @@
+/* -*-c-*-
+ *
+ * $Id: qmail-checkspam.c,v 1.1 2003/10/01 00:08:57 mdw Exp $
+ *
+ * Filter messages for spam
+ *
+ * (c) 2003 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: qmail-checkspam.c,v $
+ * Revision 1.1  2003/10/01 00:08:57  mdw
+ * Collection of miscellaneous ill-documented tools.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/unistd.h>
+
+#include <libspamc.h>
+
+/*----- Main code ---------------------------------------------------------*/
+
+static const char *strenv(const char *e, const char *d)
+{
+  const char *p = getenv(e);
+  if (!p) return (d);
+  return (p);
+}
+
+static double dblenv(const char *e, double d)
+{
+  const char *p = getenv(e);
+  char *q;
+  int err = errno;
+  double f;
+  if (!p) return (d);
+  errno = 0;
+  f = strtod(p, &q);
+  if (errno) return (d);
+  errno = err;
+  return (f);
+}
+
+static int intenv(const char *e, int d)
+{
+  const char *p = getenv(e);
+  char *q;
+  int err = errno;
+  long l;
+  if (!p) return (d);
+  errno = 0;
+  l = strtol(p, &q, 0);
+  if (errno) return (d);
+  errno = err;
+  if (l < 0 || l > INT_MAX) return (d);
+  return ((int)l);
+}
+
+int shovel(int from, int to)
+{
+  char buf[4096];
+  ssize_t n;
+  char *p;
+  size_t r;
+
+  for (;;) {
+    n = read(from, buf, sizeof(buf));
+    if (n < 0 && errno != EINTR && errno != EAGAIN)
+      return (-1);
+    else if (!n)
+      return (0);
+    p = buf;
+    r = n;
+    while (r) {
+      n = write(to, p, n);
+      if (n <= 0 && errno != EINTR && errno != EAGAIN)
+       return (-1);
+      r -= n;
+      p += n;
+    }
+  }
+}
+
+int main(int argc, char *argv[])
+{
+  struct sockaddr sa;
+  struct message m;
+  int fd_m[2], fd_e[2];
+  pid_t kid;
+  int rc;
+
+  m.max_len = intenv("QMAIL_CHECKSPAM_MAXLEN", 2 * 1024 * 1024);
+  m.timeout = intenv("QMAIL_CHECKSPAM_TIMEOUT", 300);
+  rc = message_read(0, 0, &m);
+  if (rc != 0 && rc != EX_TOOBIG)
+    return (54);
+  if (!rc) {
+    if (lookup_host(strenv("QMAIL_CHECKSPAM_SPAMDHOST", "localhost"),
+                   intenv("QMAIL_CHECKSPAM_SPAMDPORT", 783),
+                   &sa))
+      return (56);
+    if (message_filter(&sa, "spamd", 0, &m))
+      return (74);
+    if (m.score >= dblenv("QMAIL_CHECKSPAM_THRESH", m.threshold))
+      return (31);
+  }
+  if (pipe(fd_m) || pipe(fd_e))
+    return (56);
+  if ((kid = fork()) < 0)
+    return (56);
+  if (!kid) {
+    close(fd_m[0]);
+    close(fd_e[0]);
+    if (message_write(fd_m[1], &m) < 0)
+      _exit(127);
+    if (rc == EX_TOOBIG && shovel(0, fd_m[1]))
+      _exit(127);
+    close(fd_m[1]);
+    if (shovel(1, fd_e[1]))
+      _exit(127);
+    close(fd_e[1]);
+    _exit(0);
+  }
+
+  dup2(fd_m[0], 0);
+  dup2(fd_e[0], 1);
+  close(fd_m[0]);
+  close(fd_e[0]);
+  close(fd_m[1]);
+  close(fd_e[1]);
+  execlp(strenv("QMAIL_CHECKSPAM_QUEUE", "/var/qmail/bin/qmail-queue"),
+        (char *)0);
+  fprintf(stderr, "failed to exec: %s\n", strerror(errno));
+  return (56);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/splitconf b/splitconf
new file mode 100755 (executable)
index 0000000..4a09f0a
--- /dev/null
+++ b/splitconf
@@ -0,0 +1,217 @@
+#! /usr/bin/tclsh
+
+proc die {msg} {
+  global argv0
+  puts stderr "$argv0: $msg"
+  exit 1
+}
+
+proc usage {file} {
+  global argv0
+  puts $file "Usage: \n\
+       $argv0 [-s] FILE\n
+       $argv0 -u OUTPUT FILE FILE ..."
+}
+
+set job "split"
+while {[llength $argv]} {
+  switch -glob -- [lindex $argv 0] {
+    "-u" - "--unsplit" {
+      set job "unsplit"
+      if {[llength $argv] < 2} { die "option `-u' needs an argument" }
+      set output [lindex $argv 1]
+      set argv [lrange $argv 1 end]
+    }
+    "-d" - "--delete" { set job "delete" }
+    "-s" - "--split" { set job "split" }
+    "-h" - "--help" { usage stdout; exit 0 }
+    "-" { break }
+    "--" { set argv [lrange $argv 1 end]; break }
+    "-*" { die "unknown option `[lindex $argv 0]'"; exit 1 }
+    default { break }
+  }
+  set argv [lrange $argv 1 end]
+}
+
+proc clear-arrays {args} {
+  foreach i $args {
+    upvar 1 $i v
+    unset i
+    array set v {}
+  }
+}
+
+proc write-safe {stuff {tidy {}}} {
+  global _ws_close _ws_del _ws_new
+  clear-arrays _ws_del _ws_new
+  set _ws_close {}
+
+  if {[set rc [catch {
+    uplevel 1 $stuff
+  } err]]} {
+    foreach f $_ws_close { catch { close $f } }
+    foreach f [array names _ws_new] { catch { file delete -- $f.new } }
+    catch { uplevel 1 $tidy }
+    return -code $rc $err
+  }
+  foreach f $_ws_close { catch { close $f } }
+  clear-arrays all
+  foreach f [concat [array names _ws_old] [array names _ws_del]] {
+    set all($f) 0
+  }
+  if {[set rc [catch {
+    foreach f [array names all] {
+      if {[file exists $f]} {
+       file delete -- $f.old
+       file copy -force -- $f $f.old
+      }
+      set old($f) 0
+    }
+    foreach f [array names _ws_new] { file rename -force -- $f.new $f }
+    foreach f [array names _ws_del] { file delete -- $f }
+  } err]]} {
+    foreach f [array names _ws_new] { catch { file delete -- $f.new } }
+    foreach f [array names old] { file rename -force -- $f.old $f }
+    catch { uplevel 1 $tidy }
+    return -code $rc $err
+  }
+  foreach i [array names all] { catch { file delete -- $i.old } }
+  catch { uplevel 1 $tidy }
+  return {}
+}
+
+proc write-safe-open {name {trans auto}} {
+  global _ws_close _ws_new
+  if {[file isdirectory $name]} { error "`$name' is a directory" }
+  set f [open $name.new w]
+  fconfigure $f -translation $trans
+  lappend _ws_close $f
+  set _ws_new($name) 0
+  return $f
+}
+
+proc write-safe-delete {name} {
+  global _ws_del
+  set _ws_del($name) 0
+}
+
+proc write-safe-file {name contents {trans auto}} {
+  set f [write-safe-open $name $trans]
+  puts -nonewline $f $contents
+  close $f
+}
+
+proc read-file {name {trans auto}} {
+  set f [open $name]
+  fconfigure $f -translation $trans
+  set c [read $f]
+  close $f
+  return $c
+}
+
+proc write-safe-manifest {f l} {
+  set f [write-safe-open $f.files]
+  foreach i $l { puts $f $i }
+  close $f
+}
+
+proc old-files {conf} {
+  set old {}
+  if {[file exists $conf.files]} {
+    set f [open $conf.files]
+    while {[gets $f line] >= 0} { lappend old $line }
+    close $f
+  }
+  return $old
+}
+
+set rc 0
+clear-arrays opt
+array set opt {
+  prefix ""
+  before ""
+  after ""
+}
+switch $job {
+  "unsplit" {
+    set f "\#\# automatically generated by splitconf\n\n"
+    set ff {}
+    foreach i $argv {
+      if {[catch {
+       set c [read-file $i]
+       append f "\[$i\]\n$c\n"
+       lappend ff $i
+      } msg]} {
+       set rc 1
+      }
+    }
+    write-safe {
+      write-safe-file $output $f
+      write-safe-manifest $output $ff
+    }
+  }
+  "delete" {
+    if {[llength $argv] != 1} { die "need exactly one filename" }
+    set conf [lindex $argv 0]
+    set old [old-files $conf]
+    write-safe {
+      foreach i $old { write-safe-delete $i }
+      write-safe-delete $conf.files
+    }
+  }
+  "split" {
+    if {[llength $argv] != 1} { die "need exactly one filename" }
+    set conf [lindex $argv 0]
+    set old [old-files $conf]
+    set c [open $conf r]
+    catch { unset o }
+    set file ""
+    set spill ""
+    array set new {}
+    write-safe {
+      while {[gets $c line] >= 0} {
+       if {[regexp -- {^\[(.*)\]\s*$} $line . name]} {
+         if {[info exists o]} {
+           puts -nonewline $o $file
+           close $o
+         } else {
+           exec "sh" "-c" $opt(before)
+         }
+         set name "$opt(prefix)$name"
+         set o [write-safe-open $name]
+         set new($name) 1
+         set file ""
+         set spill ""
+       } elseif {[info exists o]} {
+         switch -regexp -- $line {
+           {^\s*$} { append spill "$line\n" }
+           {^\#\#} { }
+           {^\!} { append file "$spill[string range $line 1 end]\n" }
+           default { append file "$spill$line\n" }
+         }
+       } elseif {[regexp -- {^\s*(\#|$)} $line]} {
+         continue
+       } elseif {[regexp -- {^\s*([-\w]+)\s*=\s*(.*\S|)\s*$} $line . k v]} {
+         if {![info exists opt($k)]} {
+           error "unknown configuration option `$k'"
+         } else {
+           set opt($k) $v
+         }
+       } else {
+         error "unknown preamble directive"
+       }
+      }
+      if {[info exists o]} {
+       puts -nonewline $o $file
+       close $o
+      }
+      close $c
+      foreach i $old {
+       if {![info exists new($i)]} { write-safe-delete $i }
+      }
+      write-safe-manifest $conf [array names new]
+    } {
+      exec "sh" "-c" $opt(after)
+    }
+  }
+}
diff --git a/unfwd b/unfwd
new file mode 100755 (executable)
index 0000000..3f89ef6
--- /dev/null
+++ b/unfwd
@@ -0,0 +1,73 @@
+#! /usr/bin/perl
+
+use MIME::Parser;
+use MIME::Entity;
+use MIME::Head;
+use MIME::Body;
+
+sub bounce {
+  print STDERR "$0: ", @_, "\n";
+  exit 100;
+}
+
+sub retry {
+  print STDERR "$0: ", @_, "\n";
+  exit 111;
+}
+
+$DONE = 0;
+$FAIL = 0;
+
+sub fail {
+  print STDERR "$0: ", @_, "\n";
+  $FAIL = 100;
+}
+
+sub msg {
+  my ($body) = @_;
+  $DONE = 1;
+  pipe(IN, OUT);
+  my $kid = fork();
+  defined($kid) or retry("couldn't fork: $!");
+  if (!$kid) {
+    open(STDIN, "<&IN");
+    close(OUT);
+    close(IN);
+    exec @ARGV;
+    print STDERR "$0: exec `", join(" ", @ARGV), "' failed: $!\n";
+    exit 100;
+  }
+  close(IN);
+  $body->print(\*OUT) or fail "print failed: $!", last;
+  close(OUT) or fail "close failed: $!";
+  waitpid($kid, 0) or fail "waitpid failed: $!";
+  $? and fail "program `", join(" ", @ARGV), "' exited with status $?";
+}
+
+sub digest {
+  my ($e) = @_;
+  foreach my $i ($e->parts()) {
+    msg($i->bodyhandle());
+  }
+}
+
+$SIG{__DIE__} = sub { retry "DEAD: ", @_, "!" };
+$SIG{PIPE} = IGN;
+@ARGV or retry "$0: no command given";
+my $pp = MIME::Parser->new();
+$pp->output_to_core(ALL);
+$pp->extract_nested_messages(0);
+my $top = $pp->parse(\*STDIN);
+if ($top->effective_type =~ m'multipart/mixed'i) {
+  foreach my $i ($top->parts()) {
+    if ($i->effective_type =~ m'message/rfc822'i) {
+      msg($i->bodyhandle());
+    } elsif ($i->effective_type =~ m'multipart/digest'i) {
+      digest($i);
+    }
+  }
+} elsif ($top->effective_type =~ m'multipart/digest'i) {
+  digest($top);
+}
+if (!$DONE) { bounce "no forwarded message or digest"; }
+exit $FAIL;
diff --git a/xtitle.c b/xtitle.c
new file mode 100644 (file)
index 0000000..94e9af6
--- /dev/null
+++ b/xtitle.c
@@ -0,0 +1,169 @@
+#include <errno.h>
+#include <unistd.h>
+#include <termios.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#ifdef BASH_BUILTIN
+#include "bash/config.h"
+#include "bash/shell.h"
+#include "bash/builtins.h"
+#include "bash/builtins/common.h"
+#include "bash/builtins/bashgetopt.h"
+#endif
+
+#ifdef BASH_BUILTIN
+int xtitle_builtin(WORD_LIST *list)
+#else
+int main(int argc, char *argv[])
+#endif
+{
+  int query = 0;
+  int fd;
+  int openned = 0;
+
+#ifdef BASH_BUILTIN
+  reset_internal_getopt();
+#endif
+  for (;;) {
+#ifdef BASH_BUILTIN
+    int i;
+    i = internal_getopt(list, "q");
+#else
+    int i = getopt(argc, argv, "q");
+#endif
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'q':
+       query = 1;
+       break;
+      default:
+#ifdef BASH_BUILTIN
+       builtin_usage();
+#else
+       fprintf(stderr, "usage: xtitle [-q] [string]\n");
+#endif
+       return (1);
+    }
+  }
+
+#ifdef BASH_BUILTIN
+  if (!query && loptend == 0) {
+#else
+  if (!query && optind == argc) {
+#endif
+    fprintf(stderr, "xtitle: no string to set\n");
+    return (1);
+  }
+
+  {
+    char *t = getenv("TERM");
+    if (!t || strncmp(t, "xterm", 5))
+      return (0);
+  }
+
+  if (isatty(0))
+    fd = 0;
+  else {
+    fd = open("/dev/tty", O_RDWR);
+    if (fd < 0) {
+      fprintf(stderr, "xtitle: couldn't open terminal: %s", strerror(errno));
+      return (1);
+    }
+    openned = 1;
+  }
+
+  if (!query) {
+#ifdef BASH_BUILTIN
+    WORD_LIST *l = loptend;
+    char sp = ' ';
+    write(fd, "\33]0;", 4);
+    while (l) {
+      write(fd, l->word->word, strlen(l->word->word));
+      if (l->next)
+       write(fd, &sp, 1);
+      l = l->next;
+    }
+    write(fd, "\7", 2);
+#else
+    int i;
+    char sp = ' ';
+    write(fd, "\33]0;", 4);
+    for (i = optind; i < argc; i++) {
+      write(fd, argv[i], strlen(argv[i]));
+      if (i < argc - 1)
+       write(fd, &sp, 1);
+    }
+    write(fd, "\7", 2);
+#endif
+  } else {
+    struct termios o, n;
+    char hack;
+    int state = 0;
+
+    tcgetattr(fd, &o);
+    n = o;
+    n.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
+                  |INLCR|IGNCR|ICRNL|IXON);
+    n.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+    n.c_cflag &= ~(CSIZE|PARENB);
+    n.c_cflag |= CS8;
+    tcsetattr(fd, TCSAFLUSH, &n);
+    write(fd, "\33[21t", 5);
+
+    while (state != -1) {
+      if (read(fd, &hack, 1) < 1)
+       break;
+      switch (state) {
+       case 0:
+         if (hack == '\33') state = 1;
+         break;
+       case 1:
+         if (hack == ']') state = 2; else state = 0;
+         break;
+       case 2:
+         if (hack == 'l') state = 3; else state = 0;
+         break;
+       case 3:
+         if (hack == '\33') state = 4; else putchar(hack);
+         break;
+       case 4:
+         if (hack == '\\') { state = -1; putchar('\n'); }
+         else { putchar('\33'); putchar(hack); state = 3; }
+         break;
+      }
+    }
+
+    tcsetattr(fd, TCSAFLUSH, &o);
+  }
+
+  if (openned)
+    close(fd);
+
+  return (0);
+}
+
+
+#ifdef BASH_BUILTIN
+
+static char *xtitle_doc[] = {
+  "Either set or read the title of the current xterm window.  With the",
+  "-q option, writes the current xterm title to standard output.  Without",
+  "the -q option, sets the xterm title to be the arguments given,",
+  "separated by space characters.  [By Mark Wooding, mdw@nsict.org]",
+  0
+};
+
+struct builtin xtitle_struct = {
+  "xtitle",
+  xtitle_builtin,
+  BUILTIN_ENABLED,
+  xtitle_doc,
+  "xtitle [-q] [arguments]",
+  0
+};
+
+#endif