diff --git a/src.mdwn b/src.mdwn index 613d402..aac43d5 100644 --- a/src.mdwn +++ b/src.mdwn @@ -4,28 +4,6 @@ I write software for a living. I like doing it so much that sometimes I even write software just for fun. Here are some of the better hacks I've created over the years. -[Firebot](firebot) is an IRC automaton (bot) that works as an InfoBot, -LinkBot (links multiple channels, even across servers), command bot -(displays the output of programs), web bot (fetches and parses web -pages, returning the results), and more. Everything runs in an -asynchronous select() loop with a very easy-to-use high-level interface. +[[!map pages="src/*" show="title"]] -[Photobob](photobob) is a web-based photo album. There's nothing to -differentiate this from the myriad other web-based photo albums out -there, except that this one doesn't need a database and doesn't write -any meta-information. You just point it at a directory full of -images/movies and it serves 'em up. - -[eguile](eguile) is a text preprocessor using guile scheme. -[escm](escm) is the same thing for gauche. You could use these to build -a web site with a common theme. I used to use them, but now I use a -very simple m4 template. - -The [Universal Boardgame Kit](ubk/) is a kit you can build for under $5 -to use with a number of boardgames. A few boards are included. I -designed the pieces and most of the boards, and hand-coded them all in -PostScript. That makes me feel manly. - -I also have some little [Python](python/), [PostScript](ps-hacks), and -[other](misc) hacks, which do clever things. diff --git a/src/firebot.mdwn b/src/firebot.mdwn index 2a54df2..aa61766 100644 --- a/src/firebot.mdwn +++ b/src/firebot.mdwn @@ -1,4 +1,4 @@ -[[!meta title="Firebot"]] +[[!meta title="Firebot: IRC Automaton"]] FireBot is a winner! diff --git a/src/ipqueue.mdwn b/src/ipqueue.mdwn index 27a563e..5dd7d26 100644 --- a/src/ipqueue.mdwn +++ b/src/ipqueue.mdwn @@ -10,6 +10,9 @@ of a firewall. You can use it to snoop on traffic, modify or discard certain packets, make routing decisions, masquerade stuff, whatever--and you get it all with garbage collection :) +Apparently this program appears in a book called "Security Power Tools". +That means I'm *Internet Famous*! + Download -------- @@ -22,6 +25,12 @@ License GPL, of course. +Support +------- + +You can email me at , or try the IRC channel +[#py-ipqueue on irc.oftc.net](irc://irc.oftc.net/py-ipqueue). + Screen Shots ------------ diff --git a/src/ipqueue/ipqueue.tar.gz b/src/ipqueue/ipqueue.tar.gz deleted file mode 120000 index be60321..0000000 --- a/src/ipqueue/ipqueue.tar.gz +++ /dev/null @@ -1 +0,0 @@ -ipqueue-1.4.1.tar.gz \ No newline at end of file diff --git a/src/ipqueue/ipqueue.tar.gz b/src/ipqueue/ipqueue.tar.gz new file mode 100644 index 0000000..3755e21 Binary files /dev/null and b/src/ipqueue/ipqueue.tar.gz differ diff --git a/src/misc/9p.scm b/src/misc/9p.scm new file mode 100755 index 0000000..fbdd175 --- /dev/null +++ b/src/misc/9p.scm @@ -0,0 +1,265 @@ +#! /usr/bin/gosh + +;; +;; Neale Pickett wrote this. +;; + +;; This uses gauche's networking stuff, but no other gauche stuff. It +;; should be simple to substitute your implementation's networking +;; procedures. +(use gauche.net) +(require-extension (srfi 1 4 8 9)) + +(define message-specs + ;; name num format + '((TVersion 100 (2 4 s)) ;x64 + (RVersion 101 (2 4 s)) + (TAuth 102 (2 4 s s)) + (RAuth 103 (2 13)) + (TAttach 104 (2 4 4 s s)) ;x68 + (RAttach 105 (2 13)) + (TError 106 ()) ;illegal + (RError 107 (2 s)) + (TFlush 108 (2 2)) ;x6c + (RFlush 109 (2)) + (TWalk 110 (2 4 4 (2 . s))) + (RWalk 111 (2 (2 . 13))) + (TOpen 112 (2 4 1)) ;x70 + (ROpen 113 (2 13 4)) + (TCreate 114 (2 4 s 4 1)) + (RCreate 115 (2 13 4)) + (TRead 116 (2 4 8 4)) ;x74 + (RRead 117 (2 (4 . d))) + (TWrite 118 (2 4 8 (4 . s))) + (RRwrite 119 (2 4)) + (TClunk 120 (2 4)) ;x78 + (RClunk 121 (2)) + (TRemove 122 (2 4)) + (RRemove 123 (2)) + (TStat 124 (2 4)) ;x7c + (RStat 125 (2 n)) + (TWStat 126 (2 4 n)) + (RWStat 127 (2)))) ;x7f + +(define (spec-by-num num) + (let loop ((specs message-specs)) + (cond + ((null? specs) + #f) + ((equal? (cadar specs) num) + (car specs)) + (else + (loop (cdr specs)))))) + + +;; +;; Helper procedures +;; + +(define (u8-list->uint l) + (let loop ((l (reverse l)) + (acc 0)) + (if (null? l) + acc + (loop (cdr l) + (+ (* 256 acc) + (car l)))))) + +(define (uint->u8-list width i) + (if (zero? width) + '() + (let ((b (modulo i 256)) + (r (floor (/ i 256)))) + (cons b (uint->u8-list (- width 1) r))))) + +;; XXX: s had better be printable 7-bit ASCII +(define (string->u8-list s) + (map char->integer (string->list s))) + +(define (u8-list->string l) + (list->string (map integer->char l))) + + +;; +;; Packing and unpacking, both deal with u8-lists +;; + +(define (pack fmt args) + (let loop ((fmt fmt) + (args args) + (acc '())) + ;;(write (list fmt args acc)) (newline) + (cond + ((null? fmt) + acc) + ((number? (car fmt)) + (loop (cdr fmt) + (cdr args) + (append acc (uint->u8-list (car fmt) (car args))))) + ((equal? (car fmt) 's) + ;;XXX Should handle UTF-8 + (loop (cdr fmt) + (cdr args) + (append acc + (uint->u8-list 2 (string-length (car args))) + (string->u8-list (car args))))) + ((pair? (car fmt)) + ;; fmt item is (c . type), which gets packed to a c-octet n, + ;; followed by n types. + (let ((count (length (car args)))) + (loop (cdr fmt) + (cdr args) + (append acc + (uint->u8-list (caar fmt) count) + (pack (make-list count (cdar fmt)) + (car args)))))) + ((equal? (car fmt) 'n) + ;; XXX: total guess here + (loop (cdr fmt) + (cdr args) + (append acc (car args)))) + (else + (error (format "Unknown format element: ~a" (car fmt))))))) + +(define (unpack fmt l) + (reverse + (let loop ((fmt fmt) + (l l) + (acc '())) + ;;(write (list fmt l acc)) (newline) + (cond + ((null? fmt) + acc) + ((number? (car fmt)) + (loop (cdr fmt) + (drop l (car fmt)) + (cons (u8-list->uint (take l (car fmt))) + acc))) + ((equal? (car fmt) 's) + (let ((len (u8-list->uint (take l 2))) + (m (drop l 2))) + (loop (cdr fmt) + (drop m len) + (cons (u8-list->string (take m len)) + acc)))) + ((pair? (car fmt)) + (let* ((count (u8-list->uint (take l (caar fmt)))) + (m (drop l (caar fmt)))) + (receive (p octets) + (case (cdar fmt) + ((s) + (let ((p (reverse (unpack (make-list count (cdar fmt)) + l)))) + (values p + (reduce + 0 (map string-length p))))) + ((d) + (values (take m count) + count)) + (else + (values (reverse (unpack (make-list count (cdar fmt)) + l)) + (* count (cdar fmt))))) + (loop (cdr fmt) + (drop m octets) + (cons p acc))))) + + (else + (error (format "Unknown format element: ~a" (car fmt)))))))) + + +;; +;; Packet assembly and disassembly +;; + +(define (make-packet type . args) + (let* ((spec (cdr (assoc type message-specs))) + (msgnum (car spec)) + (fmt (cadr spec)) + (p (pack fmt args))) + (append (uint->u8-list 4 (+ 5 (length p))) + (list msgnum) + p))) + +(define (write-packet ixp type . args) + ((ixp-write ixp) (apply make-packet (cons type args)))) + +(define (read-uint width ixp) + (u8-list->uint ((ixp-read ixp) width))) + +(define (read-packet ixp) + (let* ((len (read-uint 4 ixp)) + (msgnum (read-uint 1 ixp)) + (spec (spec-by-num msgnum)) + (fmt (caddr spec)) + (datum ((ixp-read ixp) (- len 5)))) + (cons (car spec) + (unpack fmt datum)))) + + +;; +;; 9p record +;; +;; This is how I deal with the fact that no two scheme implementations +;; have the same socket API. There are no SRFIs for sockets so that's +;; not likely to change in the near future. +;; +;; You create one of these with (make-ixp read-u8-list write-u8-list). +;; read-u8-list should one argument, count, and return a list of length +;; count of octets (u8s) read from the socket. write-u8-list takes one +;; argument, l (a u8-list), and writes that to the socket. +;; +(define-record-type ixp + (make-ixp read-u8-list write-u8-list) + ixp? + (read-u8-list ixp-read) + (write-u8-list ixp-write)) + + +(define (ixp-transaction ixp type . args) + (apply write-packet (append (list ixp type 1) args)) + (let ((ret (read-packet ixp))) + (if (equal? (car ret) 'RError) + (error (format "IXP Recieve: ~a" ret)) + ret))) + +;; Rewriting this procedure should be all you need to do in order to +;; port this code. +(define (gauche-with-ixp socket proc) + (call-with-client-socket socket + (lambda (in out) + (let ((ixp (make-ixp + (lambda (count) + (let ((vec (make-u8vector count))) + (if (not (equal? (read-block! vec in) count)) + (error "read-octets: short read") + (u8vector->list vec)))) + (lambda (u8-list) + (write-block (list->u8vector u8-list) out) + (flush out))))) + (proc ixp))))) + + +(define (main args) + (gauche-with-ixp (make-client-socket 'unix "/tmp/ns.neale.:0/wmii") + (lambda (ixp) + (let ((root-fid #xf00fb1ba) + (fid 1) + (username "the dink") + (filename "event") + (data "hello\n")) + (ixp-transaction ixp 'TVersion 4096 "9P2000") + (ixp-transaction ixp 'TAttach root-fid #xffffffff username "") + + (ixp-transaction ixp 'TWalk root-fid fid (list filename)) + (ixp-transaction ixp 'TOpen fid 1) + (ixp-transaction ixp 'TWrite fid 0 (list data)) + (ixp-transaction ixp 'TClunk fid) + + (ixp-transaction ixp 'TWalk root-fid fid (list filename)) + (ixp-transaction ixp 'TOpen fid 0) + (write + (let ((cl (caddr (ixp-transaction ixp 'TRead fid 0 4096)))) + (ixp-transaction ixp 'TClunk fid) + (u8-list->string cl))) + (newline)))) + 0) \ No newline at end of file diff --git a/src/misc/Makefile b/src/misc/Makefile new file mode 100644 index 0000000..5f98566 --- /dev/null +++ b/src/misc/Makefile @@ -0,0 +1,18 @@ +all: dwm-config.h spamfairy.py mail-expire.sh gourmet.sh deliver.sh + +COPY = cp $< $@ + +dwm-config.h: $(HOME)/opt/src/dwm/config.h + $(COPY) + +spamfairy.py: /usr/local/bin/spamfairy + $(COPY) + +mail-expire.sh: /usr/local/bin/mail-expire + $(COPY) + +gourmet.sh: /usr/local/bin/gourmet + $(COPY) + +deliver.sh: /usr/local/bin/deliver + $(COPY) \ No newline at end of file diff --git a/src/misc/deliver.sh b/src/misc/deliver.sh new file mode 100755 index 0000000..2d20203 --- /dev/null +++ b/src/misc/deliver.sh @@ -0,0 +1,38 @@ +#! /bin/sh -x + +JUNK=$HOME/Maildir/.Junk + +deliver () { + fn=`date +%s`.$$.`hostname` + mv $1 $2/tmp/$fn + mv $2/tmp/$fn $2/new/ + chmod 600 $2/new/$fn +} + +if [ -d $JUNK ]; then + tmp=`tempfile` + trap "rm -f $tmp" 0 + + bogofilter -p -u > $tmp + case $? in + 0) # spam + deliver $tmp $JUNK + ;; + 1) # ham + /usr/lib/dovecot/deliver < $tmp + ;; + 2) # unsure + for d in .maybe -maybe; do + if [ -d $JUNK$d ]; then + deliver $tmp $JUNK$d + exit 0 + fi + done + + # Fall-through + /usr/lib/dovecot/deliver < $tmp + ;; + esac +else + exec /usr/lib/dovecot/deliver +fi \ No newline at end of file diff --git a/src/misc/dwm-config.h b/src/misc/dwm-config.h new file mode 100644 index 0000000..4fc2485 --- /dev/null +++ b/src/misc/dwm-config.h @@ -0,0 +1,102 @@ +/* + * Description: Neale's DWM config + * Author: Neale Pickett + * Time-stamp: <2008-03-31 11:52:28 neale> + */ + +#define TERM "rxvt" + +/* appearance */ +#define BORDERPX 2 +#define FONT "-adobe-helvetica-medium-r-*-*-10-*-*-*-*-*-*-*" +#define NORMBORDERCOLOR "#cfdde6" +#define SELBORDERCOLOR "#80d0ff" +#define NORMBGCOLOR "#69668b" +#define NORMFGCOLOR "#d3d1e6" +#define SELBGCOLOR "#ccb48f" +#define SELFGCOLOR "#000000" + +/* tagging */ +const char tags[][MAXTAGLEN] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +Rule rules[] = { + /* class:instance:title substr tags ref isfloating */ + { "Gimp", tags[5], True }, + { "KNetworkManager", tags[8], False }, +}; + +/* layout(s) */ +#define RESIZEHINTS True /* False - respect size hints in tiled resizals */ +#define SNAP 32 /* snap pixel */ + +Layout layouts[] = { + /* symbol function isfloating */ + { "[]=", tilev, False }, /* first entry is default */ + { "><>", floating, True }, + { "[M]", monocle, False }, +}; + +const char *layoutcycle[] = {"[]=", "[M]"}; + +void +nextlayout(const char *arg) +{ + static int which = 0; + + which = (which + 1) % LENGTH(layoutcycle); + setlayout(layoutcycle[which]); +} + +void +restart(const char *arg) +{ + if (arg) { + execlp(arg, arg, NULL); + } else { + execlp("dwm", "dwm", NULL); + } +} + +/* key definitions */ +#define MODKEY Mod4Mask +Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_a, spawn, + "exec dmenu_run -fn '"FONT"' -nb '"NORMBGCOLOR"' -nf '"NORMFGCOLOR"' -sb '"SELBGCOLOR"' -sf '"SELFGCOLOR"'" }, + { MODKEY|ShiftMask, XK_Return, spawn, "exec " TERM }, + { MODKEY, XK_l, spawn, "exec screenlock" }, + { MODKEY, XK_m, spawn, "exec " TERM " -e ncmpc" }, + { MODKEY, XK_w, spawn, "exec mpc prev" }, + { MODKEY, XK_v, spawn, "exec mpc toggle" }, + { MODKEY, XK_z, spawn, "exec mpc next" }, + + { MODKEY, XK_n, focusnext, NULL }, + { MODKEY, XK_t, focusprev, NULL }, + { MODKEY, XK_Return, zoom, NULL }, + { MODKEY, XK_equal, nextlayout, NULL }, + { MODKEY|ShiftMask, XK_equal, setlayout, "><>" }, + { MODKEY|ShiftMask, XK_space, togglefloating, NULL }, + { MODKEY, XK_grave, killclient, NULL }, + { MODKEY, XK_0, view, NULL }, + { MODKEY, XK_1, view, tags[0] }, + { MODKEY, XK_2, view, tags[1] }, + { MODKEY, XK_3, view, tags[2] }, + { MODKEY, XK_4, view, tags[3] }, + { MODKEY, XK_5, view, tags[4] }, + { MODKEY, XK_6, view, tags[5] }, + { MODKEY, XK_7, view, tags[6] }, + { MODKEY, XK_8, view, tags[7] }, + { MODKEY, XK_9, view, tags[8] }, + { MODKEY|ShiftMask, XK_0, tag, NULL }, + { MODKEY|ShiftMask, XK_1, tag, tags[0] }, + { MODKEY|ShiftMask, XK_2, tag, tags[1] }, + { MODKEY|ShiftMask, XK_3, tag, tags[2] }, + { MODKEY|ShiftMask, XK_4, tag, tags[3] }, + { MODKEY|ShiftMask, XK_5, tag, tags[4] }, + { MODKEY|ShiftMask, XK_6, tag, tags[5] }, + { MODKEY|ShiftMask, XK_7, tag, tags[6] }, + { MODKEY|ShiftMask, XK_8, tag, tags[7] }, + { MODKEY|ShiftMask, XK_9, tag, tags[8] }, + { MODKEY, XK_q, restart, NULL }, + { MODKEY|ShiftMask, XK_q, quit, NULL }, +}; diff --git a/src/misc/frobnitz.ml b/src/misc/frobnitz.ml new file mode 100644 index 0000000..cd73b16 --- /dev/null +++ b/src/misc/frobnitz.ml @@ -0,0 +1,389 @@ +(** A stupid adventure game + + Nick wanted an adventure game framework. Here ya go, Nick. + + Compile this with + ocamlc -o foo foo.ml + + @author Neale Pickett + +*) + +(** Directions *) +type direction = North | South | East | West | Up | Down | Emad + +(** An item *) +type item = { + names: string list; + i_full_name: string; + i_description: string; + actions: (string * (item -> unit)) list; +} + +(** A room. Contents are mutable so that the player can pick stuff + up. Exits are mutable so that I can link them back and forth *) +type room = { + r_full_name: string; + r_description: string; + mutable contents: item list; + mutable exits: (direction * room) list; +} + +(** The player *) +type player = { + mutable carrying: item list; + mutable location: room; +} + +let player = { + carrying = []; + location = { + r_full_name = "Nowhere"; + r_description = "Non-descript"; + contents = []; + exits = [] + } +} + +(* + * + * General functions + * + *) + +(** Relocate something + + Note that this does no checks to make sure the item is actually in + src! + + @param i item to relocate + @param src where it comes from + @param dst where it goes + @return (new src, new dst) +*) +let move i src dst = + (List.filter ((!=) i) src, + i :: dst) + + +(** Is the player carrying an item? *) +let carrying p i = + List.exists ((==) i) p.carrying + +(** Drop something *) +let drop i = + if carrying player i then + let carrying, contents = move i player.carrying player.location.contents in + player.carrying <- carrying; + player.location.contents <- contents; + else + raise (Failure ("You're not carrying " ^ i.i_full_name ^ "!")) + + +(** Pick something up *) +let get i = + if carrying player i then + raise (Failure ("You already have " ^ i.i_full_name ^ "!")) + else if List.exists ((==) i) player.location.contents then + let contents, carrying = move i player.location.contents player.carrying in + player.carrying <- carrying; + player.location.contents <- contents; + else + raise (Failure ("You don't see " ^ i.i_full_name ^ " here!")) + + +(** Describe an item *) +let describe i = + print_endline i.i_description + +(* + * + * Rooms + * + * There's really no need to clutter up the global namespace here, + * since all I really need is "start_location" to set the starting + * location. + * + *) +let start_location = + let bigroom = { (* Big room *) + r_full_name = "The big room"; + r_description = "This is that big room just outside the computer lab " ^ + "with the blue ceiling. It smells fresh, yet dirty at the same time. " ^ + "Off to the north is a grassy knoll."; + contents = [ + { (* lantern *) + names = ["brass lantern"; "brass"; "lantern"; "lamp"; "light"]; + i_full_name = "a brass lantern"; + i_description = "This rusty lantern looks like it's been used in " ^ + "one too many adventure games."; + actions = [ + ("rub", + fun self -> + print_endline "Nothing happens."; + ); + ("kick", + fun self -> + print_endline "There'll be a hot time in the old town tonight!"; + ) + ] + }; + { (* stone *) + names = ["smooth"; "stone"; "rock"]; + i_full_name = "a smooth stone"; + i_description = "This stone is smooth. Yes it is."; + actions = [ + ("rub", + fun self -> + print_endline "Your worries seem to vanish."; + ); + ("throw", + fun self -> + print_endline "You throw the stone."; + let carrying, contents = (move self player.carrying + player.location.contents) in + drop self; + print_endline "It lands a stone's throw away."; + ) + ] + } + ]; + exits = [] + } + in + let knoll = { (* Grassy knoll *) + r_full_name = "Grassy knoll"; + r_description = "You find yourself standing on the top of a " ^ + "small mound. The green carpeting of grass beneath your feet " ^ + "beckons kite-flying or frisbee-throwing. Near the bottom of the " ^ + "mound is a funny-looking man intensely examining a teacup. He " ^ + "is wearing a nametag reading \"Emad\"."; + contents = []; + exits = [] + } + in + let emad = { + r_full_name = "Emad"; (* Emad *) + r_description = "You are in Emad. How unpleasant."; + contents = [ + { (* ham sandwich *) + names = ["ham and bacon sandwich"; "bacon sandwich"; "ham sandwich"; + "sandwich"; "ham"; "bacon"]; + i_full_name = "a delicious ham and bacon sandwich"; + i_description = "Named after John Montagu, fourth Earl of Sandwich, " ^ + "this savory instance is comprised of ham and bacon, \"sandwiched\" " ^ + "as it were between two slices of whole-wheat bread."; + actions = [ + ("eat", + fun self -> + print_endline "A little acidic, but not bad." + ) + ] + } + ]; + exits = [] + } + in + begin + bigroom.exits <- [(North, knoll)]; + knoll.exits <- [(South, bigroom); + (Emad, emad)]; + emad.exits <- [(Up, knoll)]; + bigroom + end + +let describe_room r = + let rec display_items = function + | [] -> + " nothing" + | [i] -> (* just one *) + (" " ^ i.i_full_name) + | [i; j] -> (* two *) + (" " ^ i.i_full_name ^ + ", and " ^ j.i_full_name) + | i :: j :: tl -> (* multiple *) + (" " ^ i.i_full_name ^ "," ^ + display_items (j :: tl)) + in + print_endline ""; + print_endline r.r_full_name; + print_endline ""; + print_endline r.r_description; + if (r.contents = []) then + print_endline "" + else + print_endline ("You see" ^ (display_items r.contents) ^ " here.") + + +(* + * + * Player functions + * + *) + + +let rec find_item name l = + match (name, l) with + | _, [] -> + raise Not_found + | name, i :: tl -> + if List.exists ((=) name) i.names then + i + else + find_item name tl + +let rec find_action action l = + match (action, l) with + | _, [] -> + raise Not_found + | action, (name, func) :: tl -> + if (action = name) then + func + else + find_action action tl + +(** Apply action to name + + @param action What to do + @param name What to do it to +*) +let apply_action action name = + match action with + | "describe" | "look" | "l" -> + let i = try + find_item name player.carrying + with Not_found -> + try + find_item name player.location.contents + with Not_found -> + raise (Failure "You're not carrying that.") + in + describe i + | "take" | "get" -> + let i = try + find_item name player.location.contents + with Not_found -> + raise (Failure "You don't see that here.") + in + get i; + print_endline "Taken." + | "drop" -> + let i = try + find_item name player.carrying + with Not_found -> + raise (Failure "You're not carrying that.") + in + drop i; + print_endline "Dropped." + | _ -> + let i = try + find_item name player.carrying + with Not_found -> + raise (Failure "You don't have that.") + in + let a = try + find_action action i.actions + with Not_found -> + raise (Failure ("You can't do that to " ^ i.i_full_name ^ ".")) + in + a i + +(** Move the player + + @param dir direction +*) +let go dir = + let moveout (d, room) = + if d = dir then + begin + player.location <- room; + describe_room player.location; + end + in + let here = player.location in + List.iter moveout player.location.exits; + if here == player.location then + raise (Failure "You can't get there from here.") + +(** Display inventory +*) +let inventory () = + let print_item i = + print_endline ("* " ^ i.i_full_name) + in + if (player.carrying = []) then + print_endline "You are empty-handed." + else + begin + print_endline "You are carrying: "; + List.iter print_item player.carrying + end + + +(** Do something + + @param action What to do +*) +let do_action action = + match action with + | "inventory" | "inv" | "i" -> + inventory () + | "look" | "l" -> + describe_room player.location + | "north" | "n" -> + go North + | "south" | "s" -> + go South + | "east" | "e" -> + go East + | "west" | "w" -> + go West + | "up" | "u" -> + go Up + | "down" | "d" -> + go Down + | "emad" -> + go Emad + | _ -> + print_endline "I'm sorry, Dave, I'm afraid I can't do that." + +(** Parse a line + + @param str the line to parse +*) +let parse str = + try + let action, name = + let pos = String.index str ' ' in + (String.sub str 0 pos, + String.sub str (pos + 1) ((String.length str) - pos - 1)) + in + apply_action action name + with Not_found -> + do_action str + +(** Read-Eval-Print loop +*) +let rec repl () = + let line = read_line () in + print_endline ""; + begin + try + parse (String.lowercase line) + with Failure str -> + print_endline str + end; + repl () + +let main () = + player.location <- start_location; + describe_room player.location; + repl () + +let _ = + try + main () + with End_of_file -> + () + + diff --git a/src/misc/gourmet.sh b/src/misc/gourmet.sh new file mode 100755 index 0000000..9c506ac --- /dev/null +++ b/src/misc/gourmet.sh @@ -0,0 +1,38 @@ +#! /bin/sh + +frm="$1" +ext="$2" +base="${HOME}/.gourmet" + +num="`echo $ext | sed -n 's/^\([0-9]*\)-.*/\1/p'`" + +if [ -z "$num" ]; then + # No number; not a gourmet address + exec cat +fi + +if ! [ -d "$base" ]; then + mkdir "$base" +fi + +if [ -f "$base/$ext" ]; then + num="`cat $base/$ext`" +fi + +left=`expr "$num" - 1` +echo $left > "$base/$ext" + +if [ "$num" -gt 0 ]; then + exec awk " +{ + if (! p && /^Subject: /) { + sub(\"^Subject: \", \"Subject: <$ext: $left left> \"); + p = 1; + } + print; +}" +fi + +# Must have sent too many, return permanent failure code +echo "Address has self-destructed." 1>&2 +exit 69 diff --git a/src/misc/lcdutils-0.2-neale.tar.gz b/src/misc/lcdutils-0.2-neale.tar.gz new file mode 100644 index 0000000..4bd19fb Binary files /dev/null and b/src/misc/lcdutils-0.2-neale.tar.gz differ diff --git a/src/misc/mail-expire.sh b/src/misc/mail-expire.sh new file mode 100755 index 0000000..daf8918 --- /dev/null +++ b/src/misc/mail-expire.sh @@ -0,0 +1,86 @@ +#!/usr/bin/python + +description = '''All Maildir++ folders are checked for old mail. Any messages older than +DAYS days than the newest message in a folder are removed. If there is +a file named"expire" in a maildir, its contents will be used instead of +DAYS for that folder. You can put "12345678" in the "expire" file to +set an expiration of 1413 years, which is probably longer than you will +care about the mail folder. + +Specify a path in MAILDIR to check a specific Maildir. Subfolders will +not be checked in this case. + +''' + +import glob +import os +import sys +import optparse + +progname = sys.argv[0] +debug = 0 + +def expire(dirname, days): + expire = "%s/expire" % dirname + if os.path.exists(expire): + days = int(open(expire).read()) + secs = long(days) * 24 * 60 * 60 + messages = glob.glob("%s/cur/*" % dirname) + messages += glob.glob("%s/new/*" % dirname) + if not messages: + return + messages = [(os.path.getmtime(m), m) for m in messages] + messages.sort() + latest = messages[-1][0] + for m in messages: + if (m[0] + secs) < latest: + try: + _, flags = m[1].split(',') + except ValueError: + continue + if 'S' in flags: + # Only if the mail's been seen + if debug: + print m[1] + else: + os.remove(m[1]) + +def usage(err=None): + if err: + print "Error: %s" % err + print + print __doc__ % globals() + if err: + sys.exit(1) + else: + sys.exit() + +def main(): + import optparse + global debug + + parser = optparse.OptionParser(usage='usage: %prog [options] [MAILDIR ...]', + description=description) + parser.add_option('-d', '--debug', dest='debug', action='store_true', + help="run in debug mode (don't do anything)") + parser.add_option('-t', '--time', dest='days', metavar='DAYS', type='int', + default=90, + help="Mail older than DAYS days will be removed (default 90)") + options, args = parser.parse_args() + + debug = options.debug + + if (len(args) == 0): + base = os.path.expanduser("~/Maildir") + files = [os.path.join(base, x) for x in os.listdir(base) if x[0] == '.'] + dirs = [f for f in files if os.path.isdir(f)] + dirs.append(base) + else: + dirs = args + + for d in dirs: + expire(d, options.days) + + +if __name__ == "__main__": + main() diff --git a/src/misc/spamfairy.py b/src/misc/spamfairy.py new file mode 100755 index 0000000..7ab012e --- /dev/null +++ b/src/misc/spamfairy.py @@ -0,0 +1,190 @@ +#! /usr/bin/python + +import os +import glob +import time +from sets import Set + +bogosity = {'U': "Unsure", + 'S': "Spam", + 'H': "Ham"} + +def classification(filename): + f = file(filename) + for l in f: + if l.startswith('X-Bogosity: '): + return l[12] + elif not l.strip(): + return None + +def reclassify(oldtype, newtype, filenames): + cmd = 'bogofilter -b -v' + t = oldtype + newtype + if t == 'SH': + cmd += ' -Sn' + elif t == 'HS': + cmd += ' -Ns' + elif t == 'UH': + cmd += ' -n' + elif t == 'US': + cmd += ' -s' + else: + raise ValueError('Unable to reclassify %s' % t) + + cmd += ' 2>/dev/null' + + # Reclassify them + f = os.popen(cmd, 'w') + for fn in filenames: + f.write('%s\n' % fn) + ret = f.close() + if ret: + raise IOError("bogofilter puked on %s (%r)" % (fn, ret)) + + # Add new bogosity header + for fn in filenames: + base, filename = os.path.split(fn) + folder, _ = os.path.split(base) + tmpfn = os.path.join(folder, 'tmp', filename) + + inf = file(fn) + outf = file(tmpfn, 'w') + + headered = False + for l in inf: + if l.startswith('X-Bogosity: '): + l = (('X-Bogosity: %s (Reclassified), was ' % bogosity[newtype]) + + l[12:]) + headered = True + elif not l.strip() and not headered: + l = (('X-Bogosity: %s (Reclassified)\n' % bogosity[newtype])) + headered = True + outf.write(l) + os.rename(tmpfn, fn) + +def visit(path, folder_type): + # Read in list of already-processed files + visited = os.path.join(path, 'spamfairy-visited') + oldfiles = Set() + try: + f = file(visited) + for l in f: + oldfiles.add(l.strip()) + f.close() + except IOError: + pass + + if folder_type == 'S': + subdirs = ('cur', 'new') + else: + subdirs = ('cur',) + + for sd in subdirs: + # Read in list of current files in the directory + root = os.path.join(path, sd) + files = Set(os.listdir(root)) + + # We only consider the difference + todo = files - oldfiles + + # Check new messages for reclassification + reclass = {} # {oldtype: [filenames]} + for fn in todo: + fn = os.path.join(root, fn) + c = classification(fn) + if not c: + continue + if c != folder_type: + l = reclass.setdefault(c, []) + l.append(fn) + + # Reclassify anything that needs it + for c, filenames in reclass.iteritems(): + try: + reclassify(c, folder_type, filenames) + except ValueError: + pass + + # Write out new list of processed files + new_visited = "%s.%d" % (visited, os.getpid()) + f = file(new_visited, 'w') + for fn in files: + f.write('%s\n' % fn) + f.close() + os.rename(new_visited, visited) + + +def get_folder_type(path): + """Auto-detect type of a maildir. + + You can have the following files in your maildir: + spamfairy-ignore : skip this folder entirely + spamfairy-spam : treat this folder as spam + spamfairy-ham : treat this folder as ham + + Otherwise, check the name of the folder: + Trash : skip + Drafts : skip + Sent : skip + Unsure : skip + Junk.maybe : skip + Junk : spam + + Otherwise, treat it as a ham folder. + + Return values are None for skip, 'S' for spam, 'H' for ham + + """ + + path = os.path.realpath(path) + _, folder = os.path.split(path) + folder = folder.lower() + if os.path.exists(os.path.join(path, 'spamfairy-ignore')): + return None + elif os.path.exists(os.path.join(path, 'spamfairy-ham')): + return 'H' + elif os.path.exists(os.path.join(path, 'spamfairy-spam')): + return 'S' + elif folder in ('.sent', '.drafts', '.trash', '.unsure', '.junk.maybe'): + return None + elif folder in ('.junk', '.spam'): + return 'S' + else: + return 'H' + + +def sprinkle(folders): + """Sprinkle magic spamfairy dust on the given folders""" + + if not folders: + maildir = os.path.expanduser(os.path.join('~', 'Maildir')) + folders = [maildir] + folders += glob.glob(os.path.join(maildir, '.??*')) + + # Only do maildirs + folders = filter(lambda d: os.path.isdir(os.path.join(d, 'cur')), + folders) + + for path in folders: + folder_type = get_folder_type(path) + if not folder_type: + continue + visit(path, folder_type) + +def compact(wordlist): + """Compact bogofilter database""" + otime = os.path.getmtime(wordlist) + now = time.time() + if now - otime > 60*60*24*11: + new = '%s.new' % wordlist + ret = os.system('bogoutil -d %s | bogoutil -l %s' % (wordlist, new)) + if not ret: + os.rename(new, wordlist) + +if __name__ == '__main__': + import sys + + wordlist = os.path.expanduser(os.path.join('~', '.bogofilter', 'wordlist.db')) + if os.path.exists(wordlist): + sprinkle(sys.argv[1:]) + compact(wordlist) diff --git a/src/misc/wbackup.tar.gz b/src/misc/wbackup.tar.gz new file mode 100644 index 0000000..39d7779 Binary files /dev/null and b/src/misc/wbackup.tar.gz differ diff --git a/src/misc/zsdump.tar.gz b/src/misc/zsdump.tar.gz new file mode 100644 index 0000000..d25db80 Binary files /dev/null and b/src/misc/zsdump.tar.gz differ diff --git a/src/photobob.mdwn b/src/photobob.mdwn index 9cb31d1..2d2c95e 100644 --- a/src/photobob.mdwn +++ b/src/photobob.mdwn @@ -1,4 +1,4 @@ -[[!meta title="Photobob"]] +[[!meta title="Photobob: Web photo albums"]] I don't have a lot to say about photobob. It's the 7th or so photo album package I've written, and probably the best. You just put diff --git a/src/postscript/.sconsign b/src/postscript/.sconsign new file mode 100644 index 0000000..d60f426 --- /dev/null +++ b/src/postscript/.sconsign @@ -0,0 +1,4 @@ +index.html: - 12dab84c3ad5b0dba380c051471c14d6 - - +index.ptml: 1042003175 - 39a0ed00cb554392574b5955dbdc0af9 - +links.html: - 962d7fd4c69ea67cfeb1d0cc88e892a6 - - +links.ptml: 1041913579 - bd8a21beca5f285bf5e8ba50e86f340f - diff --git a/src/postscript/Makefile b/src/postscript/Makefile new file mode 100644 index 0000000..e5078f4 --- /dev/null +++ b/src/postscript/Makefile @@ -0,0 +1,13 @@ +FILES = alice.ps banner.ps emergency-card.ps graph-paper.ps logo.ps +FILES += page_dimensions.ps resume.ps ruled-paper.ps skel.ps + +TARGETS += index.html + +include ../../Makefile + +.INTERMEDIATE: index.txt +index.txt: index.dirlist $(FILES) + $(DIRLIST) $(FILES) > $@ + +%.png: %.ps + pstopnm -portrait -xmax 600 -stdout $< | pnmscale -x 300 | pnmtopng > $@ diff --git a/src/postscript/alice.ps b/src/postscript/alice.ps new file mode 100644 index 0000000..e74706a --- /dev/null +++ b/src/postscript/alice.ps @@ -0,0 +1,562 @@ +%!PS-Adobe-2.0 +%%Title: Alice in DIGITALand -- an example application of skel.ps +%%Creator: Neale Pickett +%%CreationDate: Thu Nov 22 15:27:53 MST 1998 +%% Time-stamp: <2002-10-22 09:34:24 neale> +%%EndComments + +/FontSize 12 def + +/RegFont /Times-Roman def +/BoldFont /Times-Bold def +/ItalFont /Times-Italic def + +/RegFSet RegFont findfont FontSize scalefont def +/BoldFSet BoldFont findfont FontSize scalefont def +/HeadFSet BoldFont findfont FontSize 1.1 mul scalefont def +/ItalFSet ItalFont findfont FontSize scalefont def + +/SetRegFont { RegFSet setfont } def +/SetBoldFont { BoldFSet setfont } def +/SetHeadFont { HeadFSet setfont } def +/SetItalFont { ItalFSet setfont } def + +/LM 50 def +/BM 50 def +/RM 580 LM sub def +/TM 760 BM sub def + +/indentLevel 30 def + +% Re-set the margins +/reset_margins { + /lm LM def + /rm RM def +} def + +reset_margins + +% Move down just a little +/down { + reset_margins + lm indentation add currentpoint exch pop + FontSize 3 div sub + moveto +} def + +% Move to the next line +/next { + reset_margins + lm indentation add currentpoint exch pop % LM Y + FontSize 1.1 mul sub % LM Y' + moveto + currentpoint exch pop + BM lt { + showpage + LM TM moveto + } {} ifelse +} def + +% Move to the previous line +/prev { + lm indentation add currentpoint exch pop % LM Y + FontSize 1.1 mul add % LM Y' + moveto +} def + +% Re-align the indentation +/align { + lm indentation add currentpoint exch pop moveto +} def + +% Indent once +/indentation 0 def +/indent { + /indentation indentation indentLevel add def + align +} def + +% Deindent +/deindent { + /indentation indentation indentLevel sub def + align +} def + +% Show left justified +/lshow { + gsave + % Set the left margin + dup + stringwidth pop + LM add + /lm exch def + + currentpoint exch pop % (str) Y + LM exch % (str) x Y + moveto show + grestore +} def + +% Show centered +/cshow { + gsave + dup % (str) (str) + stringwidth pop % (str) x + 2 div % (str) x/2 + RM LM sub 2 div % (str) x/2 RM/2 + exch sub LM add % (str) x' + currentpoint exch pop % (str) x' Y + moveto show + grestore +} def + +% Show right justified +/rshow { + gsave + dup % (str) (str) + stringwidth pop % (str) x + + % set the right margin + dup + RM exch sub padwidth sub + /rm exch def + + RM exch sub % (str) x' + currentpoint exch pop % (str) x' Y + moveto show + grestore +} def + +% Show in a bold font +/bshow { + SetBoldFont + show + SetRegFont +} def + +% Show in a italics font +/ishow { + SetItalFont + show + SetRegFont +} def + +% I totally stole this out of the blue book. +/wordbreak ( ) def +/BreakIntoLines { + /proc exch def + /linelength exch def + /textstring exch def + /curwidth exch def + + /breakwidth wordbreak stringwidth pop def + /lastwordbreak 0 def + + /startchar 0 def + /restoftext textstring def + + { + restoftext wordbreak search + + { + /done false def + } { + () exch + wordbreak exch + /done true def + } ifelse + + /nextword exch def pop + /restoftext exch def + /wordwidth nextword stringwidth pop def + + curwidth wordwidth add linelength gt + { + textstring startchar + lastwordbreak startchar sub + getinterval proc + /startchar lastwordbreak def + /curwidth wordwidth breakwidth add def + } { + /curwidth curwidth wordwidth add + breakwidth add def + } ifelse + + /lastwordbreak lastwordbreak + nextword length add 1 add def + + done { + exit + } { + } ifelse + } loop + /lastchar textstring length def + textstring startchar lastchar startchar sub + getinterval proc +} def + +% Show some text, and word-wrap it if necessary, then move to the next line +/wshow { + 0 exch + /x currentpoint pop def + rm x sub % Line length + { + show next + x currentpoint exch pop moveto + } + BreakIntoLines +} def + +% Show indented, wrapped text +/iwshow { + indentLevel exch + /x currentpoint pop def + rm x sub + indentLevel 0 rmoveto + { + show next + x currentpoint exch pop moveto + } + BreakIntoLines +} def + +% Show inverse-indented, wrapped text +/iiwshow { + indentLevel neg exch + /x currentpoint pop indentLevel add def + rm x sub + { + show next + x currentpoint exch pop moveto + } + BreakIntoLines + indentLevel neg 0 rmoveto +} def + + +LM TM moveto +SetRegFont + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Content goes here ;-) + +(Alice in DIGITALand) cshow next + +(Author: ) show (rals@pineapple.bbs.com) ishow next next align + +("Where am I?" asked Alice, as she peered at the large 7-lettered sign +with the standard blue letters.) iwshow + +("You're in Digitaland," replied the security guard, "May I see your +badge?") iwshow + +("I don't have a badge.") iwshow + +("Did you lose it?") iwshow + +("No," answered Alice in a puzzled tone. "How could I lose something I +never had?") iwshow + +("If it's not lost then you must show it to me.") iwshow + +("I can't. I don't have one.") iwshow + +("Then you'll have to have a temporary.") iwshow + +("A temporary what?" asked Alice, more confused then ever.) iwshow + +("A temporary Badge. What's your badge number?" requested the guard.) iwshow + +("I don't have one") iwshow + +("Of course not, Ken Olsen has 1. Give me your badge number, and your +cost center") iwshow + +("I'm so confused. I can't do this. I've already said 3 times why. Do +I have to tell you 4?") iwshow + +("Ahhh. 3XY, badge number 4. You must be very important to have such a +low badge number. I should have immediately recognized how low by your +state of extreme confusion. Here's your temporary. Go right on in.") iwshow + +(Alice pasted the sticky paper to her dress and headed down the hall. +Not 10 feet ahead she saw a rather distressed looking rabbit coming +toward her. He was dressed in a pair of torn, faded jeans, and a dirty +tee shirt.) iwshow + +("What's wrong?" Alice asked.) iwshow + +("I'm late! I'm late!" exclaimed the rabbit as he peered at the PERT +chart dangling from his pocket protector.) iwshow + +("Late for what?" asked Alice.) iwshow + +("My date. I'm going to miss my date. I've got a deadline to meet and +I'm not going to make it.") iwshow + +("Well, if it's already dead, it probably won't mind. In fact it isn't +likely to be going too far in such a state. I'm sure that however long +you take will be just fine.") iwshow + +("You obviously don't understand. Everything takes longer than it +really does. It doesn't matter what you are doing, only that you meet +your date, and that's always impossible.") iwshow + +("Well if it's impossible, why would anyone expect you to meet it?" +Almost at once regretting that she had asked. Was this was going to be +as confusing as badges?) iwshow + +("It's really very simple. In order to move forward, you need a goal. +Any goal will do. It just has to be impossible to do. To motivate the +troops, you have to make goals very challenging. It's really only +there to get a stake in the ground, you know. After that we march in +step until we reach our objective. The date really doesn't mean +anything. You simple have to understand that we are going to do the +right thing.") iwshow + +("But the if the goal is impossible, and really doesn't mean anything +why are you trying to go there. Wouldn't it be simpler to first figure +out what you are really going to do, then figure out how to get there?") +iwshow + +("You obviously don't understand the process. And as I said before I'm +late so there is obviously only one thing to do.") iwshow + +("Hurry up and rush off?" Alice asked, hoping it would sound more like +a suggestion than a question.) iwshow + +("No. No. No. A meeting. Let me find the Mad Manager and a number of +involved, interested, or warm bodies.") iwshow + +("That will obviously take a lot of time. I don't think you have any to +waste.) iwshow + +("No it won't. All we have to do is find a conference room. There are +lots of them right over here.") iwshow + +("But," started Alice, "those rooms are all full of people. Don't we +need an empty conference room?") iwshow + +("Silly thought. If we want to find the Mad Manager and some meeting +attendees, why would we look in an empty conference room? Anyway, it's +impossible to ever find an empty conference room.") iwshow + +(The rabbit took Alice by the hand, and promptly lead her into the +largest, fullest conference room. Alice immediately noticed that the +wastebasket was quite full of foam cups, and overhead projector bulbs. +These people had obviously been here for a long time.) iwshow + +(At the head of the table sat a man with a rather funny suit wearing a +large hat.) iwshow + +("Why" whispered Alice to the rabbit, "is that man wearing that funny +hat? Who is he?") iwshow + +("I'm the Mad Manager," answered the man at the end of the table, +obviously overhearing the question, " And I'll be happy to tell you +why I'm wearing this Hat, but that topic is not on the agenda.") iwshow + +("Why don't we change the agenda?" asked a person in the corner.) iwshow + +("Is that a topic for another meeting?" replied the manager.) iwshow + +("Is what a topic for another meeting?" voiced a third. "The reason +for the hat, or why we don't change the agenda?") iwshow + +("Why don't we take this off line?" queried another.) iwshow + +("Does everyone agree that these are all topics we should address?" +asked the mad manager.) iwshow + +("Possibly so. " injected the person in the corner. "Could it be that +we have a hidden agenda?") iwshow + +("Oh no!" the Mad Manager began, the dismay obvious on his face, +"someone has hidden the agenda again! Let me put on my process hat and +we'll see if we can work this issue.") iwshow + +(With that, he removed his rather amusing top hat, and place a big green +fedora on his head.) iwshow + +("Now, with my process hat on, I'd like to address the issue of the +hidden agenda. Since we can't have a productive meeting without an +agenda, it is up to all of us to find it.") iwshow + +("But, " a voice from the corner piped in, "who is going to drive this +issue?") iwshow + +("Do we have an action item here?" asked another attendee.) iwshow + +("Does anyone here want to work this?" asked the mad manager.) iwshow + +("Who originally brought this up?" asked another.) iwshow + +("I believe that the woman who came in with the rabbit proposed this. +Shouldn't she own it?") iwshow + +("Well" the Manager stated, pointing to Alice. "I'd say that this is +your issue.") iwshow + +("What issue. I don't have any issues. " retorted Alice, nervously +fingering her temporary badge. "I only posed a simple question.") iwshow + +("I'm not sure we can accept that," the manager declared. "We need a +date.") iwshow + +("But, " Alice began, remembering what the rabbit told her about dates, +"a date is impossible.") iwshow + +(From the back of the room another voice asked, "How about a date for a +date?") iwshow + +("The least we can ask it that you give us a date when you will be able +to give us the date for the date." stated the person in the corner.) +iwshow + +("I'm not sure I can do that," Alice opened, "since I don't know what +I'm supposed to give you a date for. I'm having a problem trying to +figure out what you want me to do.") iwshow + +("We don't have any problems here, only opportunities!" Piped a chorus +of voices.) iwshow + +("It's really quite obvious," the mad manager declared as he reached +behind him for a striped blue and gray beret, "let me put on my Digital +hat for a moment," he continued doffing the fedora and flipping on his +latest selection, "You must do the right thing.") iwshow + +("Yes. yes. " chimed the chorus of attendees, "Do the right thing.) +iwshow + +("Now, who is keeping the minutes?" the manager asked as he pitched the +beret and placed the fedora back on his head. "We need to record this +action item so we can come back to it later.") iwshow + +("We obviously can't deal with this issue until we can determine whose +meeting this is?") iwshow + +("Should we schedule some time to cover that topic?" asked one of the +attendees.) iwshow + +("Whose going to drive this?" asked another.) iwshow + +(Just at the Mad Manager was pulling out a rather worn pith helmet, a +voice in the back suggested "Let's take a break and work some of this +1x1 off line") iwshow + +(Being closest to the door Alice was the first to leave. She quickly +dashed down the hall, and ran up the first flight of stairs she +encountered, relieved to be free of the madness.) iwshow + +(When she opened the door the scene that confronted her made her wonder +if returning to the meeting wasn't a bad idea. Seated around a large +oval table were what appeared to be playing cards, each dressed in a +gray or navy blue three piece suit. Around each neck was a rather +oddly shaped handle \(or were they nooses?\) made of silk, or polyester.) +iwshow + +("Off with her head!" screamed the queen of hearts who was sitting at +the head of the table. Alice noticed that her tie was silk, and each +card seated near her was dressed in a suit and noose combination +similar to the queen's.) iwshow + +("Why would you want to remove my head?" Alice asked. By now she was +feeling beyond confused.) iwshow + +("It's not a modern, iconic, user friendly, menu driven, color, PC +compatible user interface," replied the queen, in a tone that would +need to come up two notches to be vaguely considered condescending.) +iwshow + +("It happens to suit me just fine," retorted Alice.) iwshow + +("What are you an engineer or something?" asked the 7 of spades.) iwshow + +("No, I'm Alice. Who are you?") iwshow + +("Marketing." they replied in perfect fifty-two part harmony.) iwshow + +("And what is that?" asked Alice.) iwshow + +(There was a brief interlude of silence as each of the cards fidgeted +with their ties, checked their watches and scribbled notes on the pads +of paper contained in a handsome genuine imitation leather folder +embossed with the company logo. Then one by one, as dominoes would do, +they turned to the person on the left until they all stared at the +queen of hearts.) iwshow + +(The queen cleared her throat, adjusted her tie a second time and stared +directly at Alice. "We provide the strategic thinking necessary to +grow the business.") iwshow + +("Oh," said Alice, "you figure out what products to build!") iwshow + +("Heavens, no!" exclaimed the Queen, "That's too tactical. We feel +it's our job to develop the vision for the long term.") iwshow + +("You develop things," began Alice, "so you build the products?") iwshow + +(In unison each member of the table made a face reminiscent of the look +a small child gets upon tasting spoiled dead roaches for the first +time.) iwshow + +("Uggggh, that's even more tactical," jeered the chorus.) iwshow + +("No! No!" shouted the Queen. "You still do not understand. We take +the pulse of the key market leaders demand curve.") iwshow + +("I see now." said Alice, "You sell the products.") iwshow + +(By now the chorus of cards chanting "Tac-ti-cal! Tac-ti-cal!" was +becoming too much.) iwshow + +(The queen was furious and repeated her original greeting. "Off with +her head! Off With her head") iwshow + +("WAIT!" demanded Alice. "I believe I understand. You are all +responsible for driving the solution opportunities for the key client +supply perceptions through strategic vision management!") iwshow + +(Alice wondered if she should add something about the claws catching, +and frumious bandersnatches and thought that she'd best leave it at +that before she became ill.) iwshow + +("Yes," screamed the cards, "That's exactly right!") iwshow + +("And how, might I ask, do you accomplish these lofty and important +goals?") iwshow + +("By calling a BOD," the queen responded.) iwshow + +("And what, pray tell, might that be?" inquired Alice as she looked for +the quickest escape route, hoping that this jabber would keep her head +attached long enough to get out.) iwshow + +("A Board of Directors," began the queen, just as Alice noticed the door +to the left of the table. "It's a type of high level meeting.") iwshow + +("A meeting????!!!!" exclaimed Alice. "Not another meeting!" With +that she bolted for the door, no longer fearing for her head. Her only +hope was that she make it through before the agenda hit the overhead. +In a dead run, she passed through the door just as the projector lamp +flicked on. The sound of the fan was the last sound to fade as the +door closed.) iwshow + +(Breathlessly she looked up to see a large open area. Directly in front +of her was an enclosed area lined on one side with triple chrome table. +A stack of plastic trays was at the foyer.) iwshow + +(As she wandered through an assortment of sandwiches, prepared foods, +soft drinks and salad began their daily spiel. "Eat Me! Drink Me! Eat +Me!") iwshow + +("Oh no," answered Alice, "I may know nothing about dates, and problems +and meetings and agendas, and marketing and badges, but I do know food. +I'm not gonna touch any of you. After the morning I've had I deserve a +nice cheese steak \(no lettuce\)!") iwshow + +(With that, Alice opened the nearest exit door and left. A resounding +high pitched whine sang its midday good-byes as Alice returned to the +real world.) iwshow + +showpage diff --git a/src/postscript/banner.ps b/src/postscript/banner.ps new file mode 100644 index 0000000..fb987fd --- /dev/null +++ b/src/postscript/banner.ps @@ -0,0 +1,111 @@ +%!PS-Adobe-2.0 +%%Description: Boilerplate for multi-page banners + +% --- Leave this alone, go to the bottom to start writing stuff --- + +% Define margins +/LM 50 def +/BM 50 def +/RM 580 LM sub def +/TM 760 BM sub def + +% font types +/times { + /font /Times-Roman def +} def + +/courier { + /font /Courier def +} def + +% font sizes +/monstrous { + /FontSize 312 def + /font findfont FontSize scalefont setfont +} def + +/huge { + /FontSize 288 def + font findfont FontSize scalefont setfont +} def + +/jumbo { + /FontSize 188 def + font findfont FontSize scalefont setfont +} def + +/large { + /FontSize 122 def + font findfont FontSize scalefont setfont +} def + + +% Line kerning +/smooshed { + /Spacing .5 def +} def + +/even { + /Spacing .75 def +} def + +/wide { + /Spacing 1 def +} def + +% Show text in landscape +/lshow { + gsave + 90 rotate + show + grestore +} def + +% start on line 1 +/offset 0 def + +% Form feed +/ff { + showpage + /offset 0 def +} def + +% Line feed (in current font) +/lf { + /offset offset Spacing FontSize mul add def +} def + +% Show top text +/disp { + lf + LM offset add RM gt { + ff + lf + } { + } ifelse + LM offset add BM moveto + lshow +} def + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Start writing your data here + +% How do you want it to appear? +courier jumbo wide + +% Even bigger font: times, letters closely spaced +%times huge smooshed + +% Smaller times, normal spacing +%times large even + +% Monstrous, the biggest of all +%times monstrous smooshed + +(This) disp +(is some) disp +(really) disp +(large) disp +(text.) disp +ff diff --git a/src/postscript/emergency-card.png b/src/postscript/emergency-card.png new file mode 100644 index 0000000..070247b Binary files /dev/null and b/src/postscript/emergency-card.png differ diff --git a/src/postscript/emergency-card.ps b/src/postscript/emergency-card.ps new file mode 100644 index 0000000..3e45e18 --- /dev/null +++ b/src/postscript/emergency-card.ps @@ -0,0 +1,357 @@ +%!PS-Adobe-2.0 +%%Creator: Neale Pickett +%%Title: Emergency card +%%Pages: 2 +%%PageOrder: Ascend +%%BoundingBox: 50 50 358 294 +%%EndComments + +% A credit card is 0 0 154 244, this page is twice that so you can fold +% it in half. Store phone numbers inside or whatever. + +/RegFont /Times-Roman findfont 8 scalefont def +/BoldFont /Times-Bold findfont 8 scalefont def + +RegFont setfont + +/bshow { + BoldFont setfont + show + RegFont setfont +} def + +/h1 { + /Times-Bold findfont 11 scalefont setfont + show + RegFont setfont +} def + +/nl { + currentpoint exch pop 10 exch 10 sub moveto +} def + +/tab { + currentpoint exch pop moveto +} def + +/hl { + gsave + currentpoint exch pop 10 exch moveto + 0.3 setlinewidth + 134 0 rlineto + closepath stroke + grestore + nl +} def + +/star-of-life { + gsave + translate + 0.13 -0.13 scale + [] 0 setdash +1 setlinewidth +0 setlinejoin +0 setlinecap +currentrgbcolor % push color +gsave [1 0 0 1 0 0] concat +gsave +1 1 1 setrgbcolor +newpath +71.9375 6.84375 moveto +71.9375 51.09375 lineto +33.625 28.96875 lineto +8 73.375 lineto +46.3125 95.46875 lineto +8 117.59375 lineto +33.625 162.03125 lineto +71.9375 139.90625 lineto +71.9375 184.15625 lineto +123.25 184.15625 lineto +123.25 139.9375 lineto +161.53125 162.03125 lineto +187.1875 117.59375 lineto +148.875 95.5 lineto +187.1875 73.375 lineto +161.53125 28.96875 lineto +123.25 51.0625 lineto +123.25 6.84375 lineto +71.9375 6.84375 lineto +closepath +fill +grestore +setrgbcolor currentrgbcolor +[] 0 setdash +1.7 setlinewidth +0 setlinejoin +1 setlinecap +newpath +71.9375 6.84375 moveto +71.9375 51.09375 lineto +33.625 28.96875 lineto +8 73.375 lineto +46.3125 95.46875 lineto +8 117.59375 lineto +33.625 162.03125 lineto +71.9375 139.90625 lineto +71.9375 184.15625 lineto +123.25 184.15625 lineto +123.25 139.9375 lineto +161.53125 162.03125 lineto +187.1875 117.59375 lineto +148.875 95.5 lineto +187.1875 73.375 lineto +161.53125 28.96875 lineto +123.25 51.0625 lineto +123.25 6.84375 lineto +71.9375 6.84375 lineto +closepath +stroke +gsave +setrgbcolor currentrgbcolor +newpath +75.09375 10 moveto +75.09375 56.53125 lineto +34.78125 33.25 lineto +12.28125 72.21875 lineto +52.59375 95.5 lineto +12.28125 118.75 lineto +34.78125 157.71875 lineto +75.09375 134.4375 lineto +75.09375 181 lineto +120.09375 181 lineto +120.09375 134.46875 lineto +160.375 157.71875 lineto +182.875 118.75 lineto +142.5625 95.5 lineto +182.875 72.21875 lineto +160.375 33.25 lineto +120.09375 56.5 lineto +120.09375 10 lineto +75.09375 10 lineto +closepath +fill +grestore +gsave +1 1 1 setrgbcolor +newpath +92.517151 28.749341 moveto +92.408986 25.487473 95.364102 22.444786 98.522226 22.444786 curveto +100.78169 22.444786 103.56391 24.163507 103.49852 27.028713 curveto +102.70837 61.652668 100.60467 131.89502 99.809866 166.84929 curveto +99.785836 167.9063 98.454636 167.43783 98.406332 166.35356 curveto +96.861909 131.68636 93.680464 63.830863 92.517151 28.749341 curveto +closepath +eofill +grestore +setrgbcolor currentrgbcolor +[] 0 setdash +0.60000002 setlinewidth +0 setlinejoin +0 setlinecap +newpath +92.517151 28.749341 moveto +92.408986 25.487473 95.364102 22.444786 98.522226 22.444786 curveto +100.78169 22.444786 103.56391 24.163507 103.49852 27.028713 curveto +102.70837 61.652668 100.60467 131.89502 99.809866 166.84929 curveto +99.785836 167.9063 98.454636 167.43783 98.406332 166.35356 curveto +96.861909 131.68636 93.680464 63.830863 92.517151 28.749341 curveto +closepath +stroke +gsave +1 1 1 setrgbcolor +newpath +94.036939 33.435356 moveto +96.542562 33.435356 97.667652 34.958914 99.217717 36.299367 curveto +100.71504 37.594206 101.88918 38.77889 101.88918 40.05277 curveto +101.88918 41.391534 100.90645 40.990382 99.865157 41.482074 curveto +99.316637 41.74108 99.365585 42.300792 98.469657 42.300792 curveto +96.316099 42.300792 95.45867 40.274406 92.51715 40.274406 curveto +90.093039 40.274406 86.944591 43.179145 86.944591 48.506596 curveto +86.944591 56.552685 90.047493 60.158311 93.720316 62.944591 curveto +94.100263 72.506596 lineto +90.174142 70.860159 80.042216 61.931473 80.042216 49.519789 curveto +80.042216 38.718116 87.055227 33.435356 94.036939 33.435356 curveto +closepath +eofill +grestore +setrgbcolor currentrgbcolor +[] 0 setdash +0.60000002 setlinewidth +0 setlinejoin +0 setlinecap +newpath +94.036939 33.435356 moveto +96.542562 33.435356 97.667652 34.958914 99.217717 36.299367 curveto +100.71504 37.594206 101.88918 38.77889 101.88918 40.05277 curveto +101.88918 41.391534 100.90645 40.990382 99.865157 41.482074 curveto +99.316637 41.74108 99.365585 42.300792 98.469657 42.300792 curveto +96.316099 42.300792 95.45867 40.274406 92.51715 40.274406 curveto +90.093039 40.274406 86.944591 43.179145 86.944591 48.506596 curveto +86.944591 56.552685 90.047493 60.158311 93.720316 62.944591 curveto +94.100263 72.506596 lineto +90.174142 70.860159 80.042216 61.931473 80.042216 49.519789 curveto +80.042216 38.718116 87.055227 33.435356 94.036939 33.435356 curveto +closepath +stroke +gsave +setrgbcolor currentrgbcolor +newpath +94.986808 36.126649 moveto +96.778096 35.842684 96.597917 36.918831 97.39314 37.614775 curveto +95.554706 37.669411 95.147378 36.898305 94.986808 36.126649 curveto +closepath +eofill +grestore +gsave +1 1 1 setrgbcolor +newpath +102.52243 66.934037 moveto +102.26913 76.05277 lineto +102.26913 81.62722 87.514512 91.407424 87.514512 101.38259 curveto +87.514512 109.06464 93.688655 113.98417 95.968338 115.12401 curveto +95.683377 107.08179 lineto +95.683377 107.08179 92.897098 105.20276 92.897098 102.01583 curveto +92.897098 97.506158 109.86807 85.891676 109.86807 77.002639 curveto +109.86807 70.699933 105.56201 68.348285 102.52243 66.934037 curveto +closepath +eofill +grestore +setrgbcolor currentrgbcolor +[] 0 setdash +0.60000002 setlinewidth +0 setlinejoin +0 setlinecap +newpath +102.52243 66.934037 moveto +102.26913 76.05277 lineto +102.26913 81.62722 87.514512 91.407424 87.514512 101.38259 curveto +87.514512 109.06464 93.688655 113.98417 95.968338 115.12401 curveto +95.683377 107.08179 lineto +95.683377 107.08179 92.897098 105.20276 92.897098 102.01583 curveto +92.897098 97.506158 109.86807 85.891676 109.86807 77.002639 curveto +109.86807 70.699933 105.56201 68.348285 102.52243 66.934037 curveto +closepath +stroke +gsave +1 1 1 setrgbcolor +newpath +101.35093 111.54617 moveto +101.35093 111.54617 107.14512 115.37689 107.14512 120.18997 curveto +107.14512 124.30746 103.98764 128.07412 100.25762 131.96931 curveto +95.900204 136.51968 94.980033 142.21601 97.551452 148.17942 curveto +97.773087 153.0554 lineto +95.905013 151.53561 92.960422 146.78869 92.960422 138.87071 curveto +92.960422 129.33988 101.12929 126.47258 101.12929 117.68866 curveto +101.35093 111.54617 lineto +closepath +eofill +grestore +setrgbcolor currentrgbcolor +[] 0 setdash +0.60000002 setlinewidth +0 setlinejoin +0 setlinecap +newpath +101.35093 111.54617 moveto +101.35093 111.54617 107.14512 115.37689 107.14512 120.18997 curveto +107.14512 124.30746 103.98764 128.07412 100.25762 131.96931 curveto +95.900204 136.51968 94.980033 142.21601 97.551452 148.17942 curveto +97.773087 153.0554 lineto +95.905013 151.53561 92.960422 146.78869 92.960422 138.87071 curveto +92.960422 129.33988 101.12929 126.47258 101.12929 117.68866 curveto +101.35093 111.54617 lineto +closepath +stroke +gsave +1 1 1 setrgbcolor +newpath +100.21108 151.25066 moveto +100.21108 151.25066 102.90238 152.69861 102.90238 156.47493 curveto +102.90238 160.02958 98.451601 163.27556 97.773087 163.75726 curveto +97.11579 164.2239 96.863869 163.61619 97.39314 163.12401 curveto +97.939619 162.61583 100.17942 159.61423 100.17942 156.15831 curveto +100.21108 151.25066 lineto +closepath +eofill +grestore +setrgbcolor +[] 0 setdash +0.60000002 setlinewidth +0 setlinejoin +0 setlinecap +newpath +100.21108 151.25066 moveto +100.21108 151.25066 102.90238 152.69861 102.90238 156.47493 curveto +102.90238 160.02958 98.451601 163.27556 97.773087 163.75726 curveto +97.11579 164.2239 96.863869 163.61619 97.39314 163.12401 curveto +97.939619 162.61583 100.17942 159.61423 100.17942 156.15831 curveto +100.21108 151.25066 lineto +closepath +stroke +grestore + + + grestore +} def + +%%Page: 1 + +% Get away from the edge of the paper +50 50 translate + +% Draw some lines for cutting +gsave + 0.9 setgray + + 154 0 moveto + 154 244 lineto + closepath stroke + + 0 0 moveto + 308 0 lineto + 308 244 lineto + 0 244 lineto + closepath stroke +grestore + +125 240 star-of-life + +10 230 moveto +(Name) bshow 50 tab (John Smith) show nl +(Address) bshow 50 tab (21 Jump Street) show nl + 50 tab (New York, NY USA) show nl + +nl +30 tab (Notify In Emergency) h1 hl + +(Name) bshow 50 tab (Sally Smith) show nl + 50 tab (+1 800-555-1212) show nl +(Name) bshow 50 tab (Joseph Smith) show nl + 50 tab (+1 800-555-1212) show nl +(Doctor) bshow 50 tab (Mahatma Ghandi) show nl + 50 tab (+1 800-555-1212) show nl + +hl + +(Conditions) bshow 50 tab (None) show nl +(Allergies) bshow 50 tab (None) show nl +(Meds) bshow 50 tab (None) show nl +(Blood) bshow 50 tab (A+++) show + 70 tab (I'm an Organ Donor!) bshow + +% Next page +154 0 translate + +5 240 star-of-life + +40 225 moveto +(info on other side) h1 nl +nl +nl +(You can put whatever you want here.) show nl + +showpage + +% Local Variables: +% mode: ps +% End: diff --git a/src/postscript/graph-paper.ps b/src/postscript/graph-paper.ps new file mode 100644 index 0000000..9ca374c --- /dev/null +++ b/src/postscript/graph-paper.ps @@ -0,0 +1,32 @@ +%!PS-Adobe-3.0 +%%Title: A sheet of graph paper +%%Creator: Neale Pickett +%%CreationDate: Sun Oct 20 17:10:42 2002 +%%EndComments + +64 dict begin + +% How wide do you want the cells to be? (in mm) +/l 5 def + +% How light should the lines be? 0.0 is black, 1.0 is white (no lines). +0.9 setgray + +%%%%%%%%%%%%%%%%%%%%%%%%% +% You don't need to change anything below here + +% One millimeter in points +/mm 2.8452756 def + +0 l mm mul 900 { + dup 0 moveto + 0 900 rlineto + + 0 exch moveto + 900 0 rlineto +} for +stroke + +end +showpage +%%EOF diff --git a/src/postscript/index.dirlist b/src/postscript/index.dirlist new file mode 100644 index 0000000..25e9a93 --- /dev/null +++ b/src/postscript/index.dirlist @@ -0,0 +1,4 @@ +== PostScript Hacks == + +Here are some little programs that are either too small or too +self-explanatory to deserve their own web pages. diff --git a/src/postscript/index.html b/src/postscript/index.html new file mode 100644 index 0000000..e7c4b89 --- /dev/null +++ b/src/postscript/index.html @@ -0,0 +1,82 @@ + + + + + PostScript Hacks + + + + +

+ + Neale Pickett +

+ +
+

PostScript Hacks +

+ +

Here are some little programs that are either too small or too +self-explanatory to deserve their own web pages.

+ +
+
alice.ps (19k)
+
Alice in DIGITALand -- an example application of skel.ps
+
banner.ps (2.1k)
+
Boilerplate for multi-page banners
+
emergency-card.ps (11k)
+
Emergency card
+
graph-paper.ps (1.1k)
+
A sheet of graph paper
+
logo.ps (1.1k)
+
Some functions to make PostScript feel like LOGO
+
page_dimensions.ps (2.1k)
+
Displays dimensions of the page, and where your printer clips.
+
resume.ps (13k)
+
Résumé
+
ruled-paper.ps (2.1k)
+
A sheet of ruled paper
+
skel.ps (5.2k)
+
Skeleton for typesetting in PostScript.
+
+ +
+ + + + \ No newline at end of file diff --git a/src/postscript/index.stp b/src/postscript/index.stp new file mode 100644 index 0000000..9e33e5b --- /dev/null +++ b/src/postscript/index.stp @@ -0,0 +1,8 @@ + + +

Here is some neat stuff that's too small or self-explanatory + to deserve its own web page.

+
+ +
+ diff --git a/src/postscript/logo.ps b/src/postscript/logo.ps new file mode 100644 index 0000000..30275ca --- /dev/null +++ b/src/postscript/logo.ps @@ -0,0 +1,34 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%BoundingBox: 0 0 612 792 +%%Title: Some functions to make PostScript feel like LOGO +%%Creator: Neale Pickett +%%CreationDate: Tue Oct 22 09:23:22 2002 +%%EndComments + +64 dict begin +/pen true def +/pu { /pen false def } def +/pd { /pen true def } def +/fd { /l exch def pen { 0 l rlineto } { 0 l rmoveto } ifelse } def +/rt { rotate } def +/lt { -1 mul rotate } def +/home { 306 496 moveto } def +home + +%% Your LOGO-like program starts here + +% a square +40 fd +90 rt +40 fd +90 rt +40 fd +90 rt +40 fd +stroke + +%% Your LOGO-like program ends here + +end +showpage +%%EOF diff --git a/src/postscript/page_dimensions.ps b/src/postscript/page_dimensions.ps new file mode 100644 index 0000000..394c9c3 --- /dev/null +++ b/src/postscript/page_dimensions.ps @@ -0,0 +1,117 @@ +%!PS-Adobe-2.0 +%Description: Displays dimensions of the page, and where your printer clips. + +/TM 792 def +/RM 612 def + +/margin 18 def +/linelen 18 def + +/step 18 def + + +.1 setlinewidth +/ruler { + newpath + + 0 0 moveto + 0 1 18 { + 0 6 rlineto + 1 -6 rmoveto + } for + + 0 0 moveto + 0 3 18 { + 0 12 rlineto + 3 -12 rmoveto + } for + + 9 0 moveto + 0 15 rlineto + + 0 0 moveto + 0 18 rlineto + 18 0 moveto + 0 18 rlineto + + stroke +} def + + +/Times-Roman findfont 10 scalefont setfont + + +/str 20 string def +0 step TM { + dup dup + margin exch + gsave + translate + + 0 0 moveto + step 2 mul le { + pop + } { + 18 0 rmoveto + str cvs str show + } ifelse + 0 18 translate + -90 rotate + ruler + + grestore +} for + +/str 20 string def +newpath +0 step RM { + dup dup + margin + gsave + translate + 0 0 moveto + + step 2 mul le { + pop + } { + 0 18 rmoveto + str cvs str show + } ifelse + ruler + grestore +} for + +1 setlinewidth + +% Score 1" in on all sides +newpath + +72 0 moveto +0 36 rlineto + +72 TM moveto +0 -36 rlineto + +RM 72 sub 0 moveto +0 36 rlineto + +RM 72 sub TM moveto +0 -36 rlineto + + +0 72 moveto +36 0 rlineto + +RM 72 moveto +-36 0 rlineto + +0 TM 72 sub moveto +36 0 rlineto + +RM TM 72 sub moveto +-37 0 rlineto + +stroke + + +showpage diff --git a/src/postscript/resume.ps b/src/postscript/resume.ps new file mode 100644 index 0000000..13a43e8 --- /dev/null +++ b/src/postscript/resume.ps @@ -0,0 +1,559 @@ +%!PS-Adobe-2.0 +%%Title: Résumé +%%Creator: Neale Pickett +%%CreationDate: Thu Nov 22 15:27:53 MST 1998 +%% Time-stamp: <2007-10-15 22:16:40 neale> +%%EndComments + +%% +%% +%% +%% If you are reading this, I really want to work for you :-) +%% +%% Seriously. +%% +%% +%% + +% You know, this was kinda fun. I'd never really used a stack-based +% language before, except for programming my HP calculator. +% +% Feel free to do with this as you please, but it comes with ABSOLUTELY +% NO WARRANTY, express or implied, including merchantability or fitness +% for a particular purpose. To quote man chat(1): "If it breaks, you +% get to keep both pieces." + +[/linux /database /documentation /sysadmin /windows /math /humor] { + false def +} forall + +/position (System Administrator or Database Engineer in a fast-paced\ + and challenging environment) def +%/linux true def +/database true def +%/documentation true def +/sysadmin true def +%/windows true def +%/math true def +/humor true def + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Some definitions. Season to taste. + +/FontSize 12 def + +/RegFont /Times-Roman def +/BoldFont /Times-Bold def +/ItalFont /Times-Italic def + +/RegFSet RegFont findfont FontSize scalefont def +/BoldFSet BoldFont findfont FontSize scalefont def +/HeadFSet BoldFont findfont FontSize 1.1 mul scalefont def +/ItalFSet ItalFont findfont FontSize scalefont def + +/SetRegFont { RegFSet setfont } def +/SetBoldFont { BoldFSet setfont } def +/SetHeadFont { HeadFSet setfont } def +/SetItalFont { ItalFSet setfont } def + +/LM 50 def +/BM 50 def +/RM 580 LM sub def +/TM 760 BM sub def + +/indentLevel 30 def + +% A little buffer around left and right-justified text +SetRegFont +(M) stringwidth pop +/padwidth exch def + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Some handy operations. + +% concatenate two strings +/strcat { + /s2 exch def + /s1 exch def + + s1 length s2 length add + string /s exch def + s 0 s1 putinterval + s s1 length s2 putinterval + s +} def + +/lm LM def +/rm RM def + +% set the tab stop +/settab { + stringwidth pop + currentpoint pop add + /tabstop exch def +} def + +% tab out there +/tab { + tabstop currentpoint exch pop moveto +} def + +% The TeX logo thingy +/TeX { + % Times Roman has smaller serifs and shallower angles than Computer + % Modern Roman, so I had to be creative about how things line up. I + % think I did mostly okay, and I hope you do too. + /Times-Roman findfont FontSize scalefont setfont + (T) show + FontSize 8 div neg FontSize 6.666666666666 div neg rmoveto + (E) show + FontSize 15 div neg FontSize 6.666666666666 div rmoveto + (X) show +} def + +% The LaTeX logo thingy +/LaTeX { + /Times-Roman findfont dup FontSize scalefont setfont + (L) show + gsave + FontSize 1.25 div scalefont setfont + FontSize 3.428571428571 div neg FontSize 6 div rmoveto + (A) show + grestore + FontSize 4.8 div 0 rmoveto + TeX +} def + +% Bullet-list an item +/bullet { + gsave + indentLevel 3 div neg 0 rmoveto + SetRegFont + (\267) show + grestore +} def + + +%% +%% In theory, all this lm, LM, rm, RM stuff should make it so that if +%% you right- or left-justify something, the word wrapper will be smart +%% enough to wrap around it on that line. In practice, this doesn't +%% work. But I've left the code in that was supposed to do it, in case +%% I get bored some day. +%% + +% Re-set the margins +/reset_margins { + /lm LM def + /rm RM def +} def + +% Move down just a little +/down { + reset_margins + lm indentation add currentpoint exch pop + FontSize 3 div sub + moveto +} def + +% Move to the next line +/next { + reset_margins + lm indentation add currentpoint exch pop % LM Y + FontSize 1.1 mul sub % LM Y' + moveto + currentpoint exch pop + BM lt { + showpage + TM LM moveto + } {} ifelse +} def + +% Move to the previous line +/prev { + lm indentation add currentpoint exch pop % LM Y + FontSize 1.1 mul add % LM Y' + moveto +} def + +% Re-align the indentation +/align { + lm indentation add currentpoint exch pop moveto +} def + +% Indent once +/indentation 0 def +/indent { + /indentation indentation indentLevel add def + align +} def + +% Deindent +/deindent { + /indentation indentation indentLevel sub def + align +} def + +% Show left justified +/lshow { + gsave + % Set the left margin + dup + stringwidth pop + LM add + /lm exch def + + currentpoint exch pop % (str) Y + LM exch % (str) x Y + moveto show + grestore +} def + +% Show centered +/cshow { + gsave + dup % (str) (str) + stringwidth pop % (str) x + 2 div % (str) x/2 + RM LM sub 2 div % (str) x/2 RM/2 + exch sub LM add % (str) x' + currentpoint exch pop % (str) x' Y + moveto show + grestore +} def + +% Show right justified +/rshow { + gsave + dup % (str) (str) + stringwidth pop % (str) x + + % set the right margin + dup + RM exch sub padwidth sub + /rm exch def + + RM exch sub % (str) x' + currentpoint exch pop % (str) x' Y + moveto show + grestore +} def + +% Show in a bold font +/bshow { + SetBoldFont + show + SetRegFont +} def + +% Show in a italics font +/ishow { + SetItalFont + show + SetRegFont +} def + +% I totally stole this out of the blue book. +/wordbreak ( ) def +/BreakIntoLines { + /proc exch def + /linelength exch def + /textstring exch def + + /breakwidth wordbreak stringwidth pop def + /curwidth 0 def + /lastwordbreak 0 def + + /startchar 0 def + /restoftext textstring def + + { + restoftext wordbreak search + + { + /done false def + } { + () exch + wordbreak exch + /done true def + } ifelse + + /nextword exch def pop + /restoftext exch def + /wordwidth nextword stringwidth pop def + + curwidth wordwidth add linelength gt + { + textstring startchar + lastwordbreak startchar sub + getinterval proc + /startchar lastwordbreak def + /curwidth wordwidth breakwidth add def + } { + /curwidth curwidth wordwidth add + breakwidth add def + } ifelse + + /lastwordbreak lastwordbreak + nextword length add 1 add def + + done { + exit + } { + } ifelse + } loop + /lastchar textstring length def + textstring startchar lastchar startchar sub + getinterval proc +} def + +% Show some text, and word-wrap it if necessary, then move to the next line +/wshow { + /x currentpoint pop def + rm x sub % Line length + { + show next + x currentpoint exch pop moveto + } + BreakIntoLines +} def + +% Show as a heading (larger font, and move to next line) +/hshow { + FontSize exch + /FontSize FontSize 1.1 mul def + gsave + BoldFont findfont FontSize scalefont setfont + show + grestore + next + /FontSize exch def +} def + +% Draw a horizontal line from margin to margin +/hline { + gsave + LM currentpoint exch pop 1 sub moveto + 1 setlinewidth + RM LM sub 0 rlineto + stroke + grestore +} def + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% The actual text. + + +newpath + + +0 TM moveto +SetHeadFont +(Neale Pickett) cshow next +SetRegFont +(neale@pobox.com) cshow next +(134 Long View Dr. #11) cshow next +(White Rock, NM 87544) cshow next +(\(505\) 665-3740) cshow next +(http://people.lanl.gov/neale) cshow next + +next +next + +(Desired Position: ) hshow +indent position wshow deindent + +next + + +(Work History:) hshow + + +indent +(1997-Present ) bshow +(Technical Staff Member: Network Engineering Group, LANL) wshow +indent +bullet (Designed and implemented a high-availability, encrypted\ + database proxy server capable of handling over 45,000 transactions\ + per day at rates exceeding 4 per second) wshow +bullet (Specified, created, and administered 18,000-record LDAP\ + directory server, web phonebook, and mission-critical locator\ + client) wshow +bullet (Designed and created web \(CGI\) programming tools, now in use\ + by three major lab-wide applications, each of which service over\ + 20,000 users) wshow +bullet (Created Linux device driver for 1.28+1.28 Gbit/sec networking\ + project) wshow +deindent + +down + +(1996-1997 ) bshow +(Lab Assistant / Grader, New Mexico Tech) wshow + +down + +(1994-1996 ) bshow +(User Consultant and Systems Programmer, New Mexico Tech) wshow +indent +bullet (Designed, created, and supported 20-machine campus Macintosh\ + lab and associated configuration integrity package) wshow +bullet (Maintained and assisted installation of 160+ machine SunOS and\ + Linux network, including all campus-wide servers) wshow +deindent +deindent + +windows { + down + + (Summer, 1992 ) bshow + (Student Aid/GS, NMERI, Albuquerque, NM) wshow + indent + bullet (Created custom application to link output from a DOS program\ + to any Windows program, using DDE) wshow + deindent +} {} ifelse + +next + +linux { + (Linux Experience:) hshow + + indent + bullet (4 years Linux administration and programming experience) wshow + bullet (6 months experience coding Linux device drivers and kernel\ + hacking) wshow + bullet (Debian package maintainer \(pending 2.1 release\)) wshow + bullet (Installed and maintain 2.0.x Linux on Intel, Alpha, \ + M68k, and PPC machines) wshow + humor { + bullet (Can create an XF86Config by hand) show + /Courier findfont FontSize scalefont setfont + 0 FontSize 14 div rmoveto + ( ;) show + 0 FontSize 14 div neg rmoveto + FontSize 6 div neg 0 rmoveto + (-) show + FontSize 6 div neg 0 rmoveto + (\)) show + SetRegFont + next + } {} ifelse + deindent + + next +} {} ifelse + +(Authored GPL Software:) hshow + +indent +SetBoldFont (phonebook ) settab + +(whiz) bshow +(\(Python\)) rshow +tab (Sequential CGI forms tool \(Presented at 7th annual International Python Conference\)) wshow +align + +(SQLd) bshow +(\(C\)) rshow +tab (Lightweight, encrypted, database proxy) wshow +align + +(fmsh) bshow +(\(C\)) rshow +tab (Restricted execution shell \(used in LANL's lab-wide firewall\ + proxy\)) wshow +align + +(phonebook) bshow +(\(Python\)) rshow +tab (Web-based phonebook view of LDAP data) wshow +align +deindent + +next + +(Brief Knowledge List:) hshow + +indent + +/other () def + +linux { + SetBoldFont (Server Software ) settab + /other other (Unix, Windows, MacOS, ) strcat def +} { + SetBoldFont (Operating Systems ) settab + (Operating Systems) bshow + tab + (Linux \(Red Hat and Debian\) expert, Solaris, Windows, MacOS) wshow align +} ifelse + +(Languages) bshow +tab +(Python, C/C++, Perl, sh, Tcl, PostScript, ) show TeX (, etc.) show next + +documentation { + (Documentation) bshow + tab + (HTML, DocBook, LinuxDoc, ) show LaTeX next +} { + /other other (documentation tools \(HTML, SGML\), ) strcat def +} ifelse + +database { + (Databases) bshow + tab + (SQL, Sybase, Postgres, OpenDB library) wshow align +} { + /other other (databases, ) strcat def +} ifelse + +sysadmin { + (Server Software) bshow + tab + (Apache, UMich LDAP, ssh, Sendmail, inn) wshow align + + (Unix Utilities) bshow + tab + (GNU Shellutils, Emacs, vi, awk, sed, etc.) wshow align + + (Networking) bshow + tab + (Firewalls, proxies, routing, troubleshooting) wshow align +} { + /other other (servers \(email, LDAP, web, etc.\), Unix tools,\ + networking, ) strcat def +} ifelse + +other () eq {} { + (Other) bshow + tab + other (etc.) strcat wshow +} ifelse + +deindent + +next + +(Education:) hshow + +indent +(BS Comp. Sci. ) bshow +(New Mexico Tech \(1997, ) show (cum laude) ishow (\)) show +math { + indent + (Emphasis in mathematics/music) wshow + deindent + + (Math/Music studies ) bshow + (Texas Tech \(1992-1993\)) wshow +} {} ifelse +deindent + +showpage + diff --git a/src/postscript/ruled-paper.ps b/src/postscript/ruled-paper.ps new file mode 100644 index 0000000..c86effa --- /dev/null +++ b/src/postscript/ruled-paper.ps @@ -0,0 +1,95 @@ +%!PS-Adobe-3.0 +%%Title: A sheet of ruled paper +%%Creator: Neale Pickett +%%CreationDate: Sun Oct 20 17:10:42 2002 +%%Pages: 1 +%%BoundingBox: 0 0 612 792 +%%EndComments + + +% Body height (in mm) +% 9 - big chief +% 5 - wide +% 4 - average +% 3 - teensy +/bh 9 def + + +%%%%%%%%%%%%%%%%%%%%%%%%% +% You don't need to change anything below here + + +% How much additional space between lines (mm)? +/ls 0 def + +% Show waist line? +/wl true def + +% Show ascenders and descenders? +/ad false def + +9 bh le { + /ls 4 def + /wl true def + /ad true def +} if + +5 bh eq { + /ls 2 def +} if + + +%%%%%%%%%%%%%%%%%%%%%%%%% +% You really don't need to change anything below here + +% One millimeter in points +/mm 2.8452756 def + +% Distance from baseline to baseline, in points +/lp bh 2 mul ls add mm mul def + +% Body height, in points +/bp bh mm mul def + +792 lp sub lp neg 0 { + 20 exch moveto + + ad { + % ascender and descender + gsave + 0.9 setgray + 0 bp 2 div rmoveto + 900 0 rlineto + -900 bp rmoveto + 900 0 rlineto + stroke + grestore + } if + + % waist line + wl { + gsave + 0.7 setgray + 0 bp rmoveto + 900 0 rlineto + stroke + grestore + } if + + % left margin + gsave + 0.3 setgray + 0 bp rlineto + stroke + grestore + + % base line + gsave + 0.3 setgray + 900 0 rlineto + stroke + grestore +} for + +showpage +%%EOF diff --git a/src/postscript/skel.ps b/src/postscript/skel.ps new file mode 100644 index 0000000..6eb25df --- /dev/null +++ b/src/postscript/skel.ps @@ -0,0 +1,273 @@ +%!PS-Adobe-2.0 +%%Title: Skeleton for typesetting in PostScript. +%%Creator: Neale Pickett +%%CreationDate: Thu Nov 22 15:27:53 MST 1998 +%% Time-stamp: <2002-10-22 09:34:13 neale> +%%EndComments + +/FontSize 12 def + +/RegFont /Times-Roman def +/BoldFont /Times-Bold def +/ItalFont /Times-Italic def + +/RegFSet RegFont findfont FontSize scalefont def +/BoldFSet BoldFont findfont FontSize scalefont def +/HeadFSet BoldFont findfont FontSize 1.1 mul scalefont def +/ItalFSet ItalFont findfont FontSize scalefont def + +/SetRegFont { RegFSet setfont } def +/SetBoldFont { BoldFSet setfont } def +/SetHeadFont { HeadFSet setfont } def +/SetItalFont { ItalFSet setfont } def + +/LM 50 def +/BM 50 def +/RM 580 LM sub def +/TM 760 BM sub def + +/indentLevel 30 def + +% Re-set the margins +/reset_margins { + /lm LM def + /rm RM def +} def + +reset_margins + +% Move down just a little +/down { + reset_margins + lm indentation add currentpoint exch pop + FontSize 3 div sub + moveto +} def + +% Move to the next line +/next { + reset_margins + lm indentation add currentpoint exch pop % LM Y + FontSize 1.1 mul sub % LM Y' + moveto + currentpoint exch pop + BM lt { + showpage + LM TM moveto + } {} ifelse +} def + +% Move to the previous line +/prev { + lm indentation add currentpoint exch pop % LM Y + FontSize 1.1 mul add % LM Y' + moveto +} def + +% Re-align the indentation +/align { + lm indentation add currentpoint exch pop moveto +} def + +% Indent once +/indentation 0 def +/indent { + /indentation indentation indentLevel add def + align +} def + +% Deindent +/deindent { + /indentation indentation indentLevel sub def + align +} def + +% Show left justified +/lshow { + gsave + % Set the left margin + dup + stringwidth pop + LM add + /lm exch def + + currentpoint exch pop % (str) Y + LM exch % (str) x Y + moveto show + grestore +} def + +% Show centered +/cshow { + gsave + dup % (str) (str) + stringwidth pop % (str) x + 2 div % (str) x/2 + RM LM sub 2 div % (str) x/2 RM/2 + exch sub LM add % (str) x' + currentpoint exch pop % (str) x' Y + moveto show + grestore +} def + +% Show right justified +/rshow { + gsave + dup % (str) (str) + stringwidth pop % (str) x + + % set the right margin + dup + RM exch sub padwidth sub + /rm exch def + + RM exch sub % (str) x' + currentpoint exch pop % (str) x' Y + moveto show + grestore +} def + +% Show in a bold font +/bshow { + SetBoldFont + show + SetRegFont +} def + +% Show in a italics font +/ishow { + SetItalFont + show + SetRegFont +} def + +% Special parsing stuff; returns true if it's a printing character +/parse_special { + /word exch def + () word eq { + SetBoldFont + false + } { + () word eq { + SetItalFont + false + } { + () word eq { + SetRegFont + false + } { + true + } ifelse + } ifelse + } ifelse +} def + +% I totally stole this out of the blue book. But I changed it a lot, so +% you might not recognize it if not for the name. +/wordbreak ( ) def +/BreakIntoLines { + /proc exch def + /linelength exch def + /textstring exch def + /curwidth exch def + + /breakwidth wordbreak stringwidth pop def + /lastwordbreak 0 def + + /startchar 0 def + /restoftext textstring def + + SetRegFont + { + restoftext wordbreak search + + { + /done false def + } { + () exch + wordbreak exch + /done true def + } ifelse + + /nextword exch def pop + /restoftext exch def + + nextword parse_special { + /wordwidth nextword stringwidth pop def + curwidth wordwidth add linelength gt + { + % This word should go on the next line + proc + /curwidth exch def + } if + + /curwidth curwidth wordwidth add + breakwidth add def + nextword show + wordbreak show + + /lastwordbreak lastwordbreak + nextword length add 1 add def + + done { + exit + } { + } ifelse + } if + } loop + /lastchar textstring length def + textstring startchar lastchar startchar sub + getinterval proc +} def + +% Show some text, and word-wrap it if necessary, then move to the next line +/wshow { + 0 exch + /x currentpoint pop def + rm x sub % Line length + { + next + x currentpoint exch pop moveto + 0 + } + BreakIntoLines +} def + +% Show indented, wrapped text +/iwshow { + indentLevel exch + /x currentpoint pop def + rm x sub + indentLevel 0 rmoveto + { + next + x currentpoint exch pop moveto + 0 + } + BreakIntoLines +} def + +% Show inverse-indented, wrapped text +/iiwshow { + indentLevel neg exch + /x currentpoint pop indentLevel add def + rm x sub + { + next + x currentpoint exch pop moveto + 0 + } + BreakIntoLines + indentLevel neg 0 rmoveto +} def + + +LM TM moveto +SetRegFont + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Content goes here ;-) + + + +showpage \ No newline at end of file diff --git a/src/python.mdwn b/src/python.mdwn new file mode 100644 index 0000000..7009921 --- /dev/null +++ b/src/python.mdwn @@ -0,0 +1,3 @@ +[[!meta title="Python hacks"]] + +[[!map pages="src/python/*" show="description"]] diff --git a/src/python/htmlpp.py b/src/python/htmlpp.py new file mode 100755 index 0000000..96af002 --- /dev/null +++ b/src/python/htmlpp.py @@ -0,0 +1,100 @@ +#! /usr/bin/env python + +## htmlpp -- Python HTML/CSS pretty-printer +## +## Thanks to ActiveState's ASPN (which rules, thanks guys) for leading +## me to this idea. AFAIK, they got the idea from MoinMoin. +## +## This is a part of epy: http://woozle.org/~neale/src/epy/ + +from __future__ import generators +import cgi +import sys +import keyword +import token +import tokenize + +def prettyprint(fd): + """Pretty print code into HTML/CSS. + + This returns a generator, which generates tokens to be printed to + some HTML document. You'll need to define a style sheet to get the + colors you like. + + """ + + end = (0, 0) + last = (None,) * 5 + for tok in tokenize.generate_tokens(fd.readline): + start = tok[2] + if start[1] != end[1]: + if start[0] != end[0]: + # What to do here? Punt. + yield '\nprettyprint punting: %s %s\n' % (tok, end) + else: + yield tok[4][end[1]:start[1]] + end = tok[3] + if end[1] == len(tok[4]): + # Prevent punting on newlines + end = (end[0] + 1, 0) + if tok[0] == token.NAME: + if keyword.iskeyword(tok[1]): + style = 'KEYWORD' + elif last[1] in ('class', 'def'): + style = 'FUNCTION' + else: + style = 'NAME' + else: + style = token.tok_name.get(tok[0]) + s = tok[1].expandtabs() + txt = cgi.escape(s) + if style: + last = tok + yield ('%s' + % (style, txt)) + else: + yield s + +def pp_fd(f, out=sys.stdout): + """Pretty print a file object.""" + + for item in prettyprint(f): + out.write(item) + +def pp_file(filename, out=sys.stdout): + """Pretty print a filename. + + Open and pretty-print python source in filename. Output goes to out + (default, sys.stdout). + + """ + + pp_fd(open(filename), out) + +def pp_string(string, out=sys.stdout): + """Pretty print a string.""" + + import cStringIO as StringIO + + f = StringIO.StringIO(string) + pp_fd(f, out) + +if __name__ == "__main__": + import sys + + print ''' + + + Bee-yoo-ti-ful source code + + + ''' + print '
'
+    # write colorized version to stdout
+    for item in prettyprint(sys.stdin):
+        sys.stdout.write(item)
+    print '
' + + diff --git a/src/python/kmp.py b/src/python/kmp.py new file mode 100755 index 0000000..221c662 --- /dev/null +++ b/src/python/kmp.py @@ -0,0 +1,42 @@ +#! /usr/bin/env python + +# Description: Knuth-Morris-Pratt algorithm + +"""Knuth-Morris-Pratt algorithm + +This is a direct transaltion of the KMP algorithm in the book +"Introduction to Algorithms" by Cormen, Lieserson, and Rivest. See that +book for an explanation of why this algorithm works. It's pretty cool. + +The only things I changed were some offsets, to cope with the fact that +Python arrays are 0-offset. + +""" + +__author__ = 'Neale Pickett ' + +def compute_prefix_function(p): + m = len(p) + pi = [0] * m + k = 0 + for q in range(1, m): + while k > 0 and p[k] != p[q]: + k = pi[k - 1] + if p[k] == p[q]: + k = k + 1 + pi[q] = k + return pi + +def kmp_matcher(t, p): + n = len(t) + m = len(p) + pi = compute_prefix_function(p) + q = 0 + for i in range(n): + while q > 0 and p[q] != t[i]: + q = pi[q - 1] + if p[q] == t[i]: + q = q + 1 + if q == m: + return i - m + 1 + return -1 diff --git a/src/python/lousycron.py b/src/python/lousycron.py new file mode 100755 index 0000000..359d579 --- /dev/null +++ b/src/python/lousycron.py @@ -0,0 +1,68 @@ +#! /usr/bin/python + +## cronit -- A lightweight (aka "lousy") anacron replacement +## Copyright (C) 2007 Neale Pickett +## +## 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 3 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, see . + +import time +import os +import popen2 +import socket + +basedir = os.path.expanduser('~/lib/cron') + +os.chdir(os.path.expanduser('~')) + +now = time.time() +periods = (('test', -1), + ('hourly', 3600), + ('daily', 86400), + ('weekly', 604800), + ('monthly', 18144000), # more or less + ('yearly', 2204892000)) + +def run_parts(dir): + out = [] + for script in os.listdir(dir): + if script.endswith('~') or script.startswith('.'): + continue + fn = os.path.join(dir, script) + proc = popen2.Popen4(fn) + proc.tochild.close() + outstr = proc.fromchild.read() + ret = proc.wait() + if outstr or ret: + out.append((fn, outstr, ret)) + return out + +def cronit(basedir): + for name, interval in periods: + dir = os.path.join(basedir, name) + if not os.path.exists(dir): + continue + tsfile = os.path.join(dir, '.timestamp.%s' % socket.gethostname()) + try: + ts = int(file(tsfile).read().strip()) + except: + ts = 0 + if ts + interval < now: + problems = run_parts(dir) + file(tsfile, 'w').write('%d\n' % now) + for script, output, ret in problems: + print '====== %s exited with code %d' % (script, ret/256) + print output + +if __name__ == '__main__': + cronit(basedir) diff --git a/src/python/maildir.py b/src/python/maildir.py new file mode 100644 index 0000000..cfe7ddf --- /dev/null +++ b/src/python/maildir.py @@ -0,0 +1,46 @@ +# maildir.py -- Maildir utilities +# +# Released into the public domain + +"""Maildir utilities""" + +__author__ = 'Neale Pickett ' + +import os +import socket +import time + +# Counter gets incremented by one for every message delivered +count = 0 + +# Local hostname +HOST = socket.gethostname() + +def create(mdir): + os.umask(0022) + for i in ('tmp', 'cur', 'new'): + os.makedirs('%s/%s' % (mdir, i)) + +def write(mdir, message, info=None): + """Write a message out to a maildir. + + """ + global count + + mdir = time.strftime('%Y-%m') + if not os.path.exists(mdir): + maildir_create(mdir) + + filename = '%d.%d_%04d.%s' % (time.time(), os.getpid(), + count, HOST) + f = open('%s/tmp/%s' % (mdir, filename), + 'w') + f.write(message) + f.close() + if info: + os.rename('%s/tmp/%s' % (mdir, filename), + '%s/cur/%s:2,%s' % (mdir, filename, info)) + else: + os.rename('%s/tmp/%s' % (mdir, filename), + '%s/new/%s' % (mdir, filename)) + count += 1 diff --git a/src/python/ndstrunc.py b/src/python/ndstrunc.py new file mode 100755 index 0000000..3d9c350 --- /dev/null +++ b/src/python/ndstrunc.py @@ -0,0 +1,54 @@ +#! /usr/bin/python + +"""ndstrunc -- Trims .nds ROM files.""" + + +import struct +import optparse +import os + +parser = optparse.OptionParser(description='Trims .nds ROM files.') +parser.add_option('-d', '--dry-run', + action='store_false', dest='writeout', default=True, + help="Don't actually modify any files.") +parser.add_option('-f', '--force', + action='store_true', dest='force', default=False, + help="Force truncation even if it seems like too much.") +(options, args) = parser.parse_args() + +for fn in args: + f = file(fn, 'rb+') + f.seek(0x80) + (size,) = struct.unpack('= ondisk: + print ' Already truncated.' + continue + + print ' On disk:', ondisk + print ' Header says:', size + print ' Would save: %dB (%2.0f%%)' % (ondisk-size, reduction) + if size < (ondisk >> 2): + if options.force: + print ' Truncating anyway, as requested.' + else: + print ' Would truncate too much!!' + continue + if options.writeout: + f.truncate(size) + print ' Truncated.' + + + diff --git a/src/python/robotfindskitten.py b/src/python/robotfindskitten.py new file mode 100644 index 0000000..aecf026 --- /dev/null +++ b/src/python/robotfindskitten.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +"""robotfindskitten -- A zen simulation. + +This is a web version of the classic text-based game. The text-based +version is much better. Go check it out at http://robotfindskitten.org/ + +""" + +import cgitb; cgitb.enable() +import cgi +import whrandom +import sys + +symbols = list('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&\'()*+,-./:;<=>?@[\]^_`{|}~') +width = 50 +height = 30 + +def color(): + def c(): + return whrandom.choice(('44', '88', 'cc', 'ff')) + + return c() + c() + c() + +print 'Content-type: text/html' +print + +def main(): + nki = [] + f = open('messages.h') + for line in f.xreadlines(): + if line.startswith(' "'): + line = line.strip() + if line[-1] == ',': + line = line[1:-2] + else: + line = line[1:-1] + line = line.replace(r'\"', '"') + nki.append(line) + + print ''' + robotfindskitten: a zen simulation + + + ''' % color() + + print '' + + print '

robotfindskitten

' + + print ''' +

+ You are robot. Your mission: find kitten. + + + Hold the mouse cursor over an object you suspect to be kitten. + After a few seconds, your browser will tell you what you have + found. Older browsers may not be able to relay what an object + actually is, so the experience may be diminished somewhat. +

+ + ''' + + + print '
'
+
+    screen = []
+    for i in range(height):
+        screen.append([' '] * width)
+
+    for i in range(30):
+        x = whrandom.randrange(width)
+        y = whrandom.randrange(height)
+
+        n = whrandom.randrange(len(symbols))
+        s = symbols[n]
+        del symbols[n]
+        s = cgi.escape(s)
+
+        n = whrandom.randrange(len(nki))
+        t = nki[n]
+        del nki[n]
+        t = cgi.escape(t)
+        t = t.replace('"', '"')
+
+        screen[y][x] = ('%s'
+                        % (color(), t, s))
+    # place kitten!
+    screen[y][x] = '%s' % s
+
+    for row in screen:
+        print ''.join(row)
+
+    print '
' + + print '' + +if __name__ == '__main__': + main() + diff --git a/src/python/snpplib.py b/src/python/snpplib.py new file mode 100644 index 0000000..20f3b79 --- /dev/null +++ b/src/python/snpplib.py @@ -0,0 +1,538 @@ +#! /usr/bin/env python + +# Description: SNPP client class for Python + +'''SNPP client class. + +Author: Neale Pickett + +This was modified from the Python 1.5 library SMTP lib. +Which was modified from the Python 1.5 library HTTP lib. + +Basically, I took smtplib, did an s/SMTP/SNPP/, and changed about ten +other things. If only all projects were this easy :-) + +Example: + + >>> import snpplib + >>> s = snpplib.SNPP("localhost") + >>> print s.help()[1] + + Level 1 commands accepted: + + PAGEr + MESSage + RESEt + SEND + QUIT + HELP + + Level 2 commands accepted: + + DATA + LOGIn [password] + LEVEl + COVErage + HOLDuntil [+/-GMTdifference] + CALLerid + + Level 3 commands accepted: + + none + + OK + >>> s.putcmd("rese") + >>> s.getreply() + (250, 'Reset ok') + >>> s.quit() +''' + +import socket +import re +import rfc822 +import types + +SNPP_PORT = 444 +CRLF="\r\n" + +# Exception classes used by this module. +class SNPPException(Exception): + """Base class for all exceptions raised by this module.""" + +class SNPPServerDisconnected(SNPPException): + """Not connected to any SNPP server. + + This exception is raised when the server unexpectedly disconnects, + or when an attempt is made to use the SNPP instance before + connecting it to a server. + """ + +class SNPPResponseException(SNPPException): + """Base class for all exceptions that include an SNPP error code. + + These exceptions are generated in some instances when the SNPP + server returns an error code. The error code is stored in the + `snpp_code' attribute of the error, and the `snpp_error' attribute + is set to the error message. + """ + + def __init__(self, code, msg): + self.snpp_code = code + self.snpp_error = msg + self.args = (code, msg) + +class SNPPSenderRefused(SNPPResponseException): + """Caller ID refused. + + In addition to the attributes set by on all SNPPResponseException + exceptions, this sets `sender' to the string that the SNPP refused + """ + + def __init__(self, code, msg, sender): + self.snpp_code = code + self.snpp_error = msg + self.sender = sender + self.args = (code, msg, sender) + +class SMTPRecipientsRefused(SNPPResponseException): + """All recipient addresses refused. + + The errors for each recipient are accessable thru the attribute + 'recipients', which is a dictionary of exactly the same sort as + SNPP.sendpage() returns. + """ + + def __init__(self, recipients): + self.recipients = recipients + self.args = ( recipients,) + + +class SNPPDataError(SNPPResponseException): + """The SNPP server didn't accept the data.""" + +class SNPPConnectError(SNPPResponseException): + """Error during connection establishment""" + +def quotedata(data): + """Quote data for email. + + Double leading '.', and change Unix newline '\n', or Mac '\r' into + Internet CRLF end-of-line. + """ + return re.sub(r'(?m)^\.', '..', + re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) + +class SNPP: + """This class manages a connection to an SNPP server. + SNPP Objects: + + For method docs, see each method's docstrings. In general, there is + a method of the same name to perform each SNPP command, and there + is a method called 'sendpage' that will do an entire page + transaction. + """ + debuglevel = 0 + file = None + + def __init__(self, host = '', port = 0): + """Initialize a new instance. + + If specified, `host' is the name of the remote host to which to + connect. If specified, `port' specifies the port to which to connect. + By default, snpplib.SNPP_PORT is used. An SNPPConnectError is + raised if the specified `host' doesn't respond correctly. + + """ + self.esnpp_features = {} + if host: + (code, msg) = self.connect(host, port) + if code != 220: + raise SNPPConnectError(code, msg) + + def set_debuglevel(self, debuglevel): + """Set the debug output level. + + A non-false value results in debug messages for connection and for all + messages sent to and received from the server. + + """ + self.debuglevel = debuglevel + + def connect(self, host='localhost', port = 0): + """Connect to a host on a given port. + + If the hostname ends with a colon (`:') followed by a number, and + there is no port specified, that suffix will be stripped off and the + number interpreted as the port number to use. + + Note: This method is automatically invoked by __init__, if a host is + specified during instantiation. + + """ + if not port: + i = host.find(':') + if i >= 0: + host, port = host[:i], host[i+1:] + try: + port = int(port) + except ValueError: + raise socket.error, "nonnumeric port" + if not port: port = SNPP_PORT + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if self.debuglevel > 0: print 'connect:', (host, port) + self.sock.connect((host, port)) + (code,msg)=self.getreply() + if self.debuglevel >0 : print "connect:", msg + return (code,msg) + + def sendstr(self, str): + """Send `str' to the server.""" + if self.debuglevel > 0: print 'send:', `str` + if self.sock: + try: + self.sock.send(str) + except socket.error: + raise SNPPServerDisconnected('Server not connected') + else: + raise SNPPServerDisconnected('please run connect() first') + + def putcmd(self, cmd, args=""): + """Send a command to the server.""" + str = '%s %s%s' % (cmd, args, CRLF) + self.sendstr(str) + + def getreply(self): + """Get a reply from the server. + + Returns a tuple consisting of: + + - server response code (e.g. '250', or such, if all goes well) + Note: returns -1 if it can't read response code. + + - server response string corresponding to response code (multiline + responses are converted to a single, multiline string). + + Raises SNPPServerDisconnected if end-of-file is reached. + """ + resp=[] + if self.file is None: + self.file = self.sock.makefile('rb') + while 1: + line = self.file.readline() + if line == '': + self.close() + raise SNPPServerDisconnected("Connection unexpectedly closed") + if self.debuglevel > 0: print 'reply:', `line` + resp.append(line[4:].strip()) + code=line[:3] + # Check that the error code is syntactically correct. + # Don't attempt to read a continuation line if it is broken. + try: + errcode = int(code) + except ValueError: + errcode = -1 + break + # Check if multiline response. + if errcode != 214: + break + + errmsg = '\n'.join(resp) + if self.debuglevel > 0: + print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg) + return errcode, errmsg + + def docmd(self, cmd, args=""): + """Send a command, and return its response code.""" + self.putcmd(cmd,args) + return self.getreply() + + def help(self, args=''): + """SNPP 'help' command. + Returns help text from server.""" + return self.docmd("help", args) + + def rese(self): + """SNPP 'rese' command -- resets session.""" + return self.docmd("rese") + + def page(self, pagerid, password=""): + """SNPP 'page' command -- specifies a pager ID. + + + + """ + + if password: + args = "%s %s" % (pagerid, password) + else: + args = pagerid + return self.docmd("page", args) + + def mess(self, msg): + """SNPP 'mess' command -- sends a single-line message.""" + return self.docmd("mess", msg) + + def send(self): + """SNPP 'send' command -- sends a message.""" + return self.docmd("send") + + def data(self, msg): + """SNPP 'DATA' command -- sends message data to server. + + Automatically quotes lines beginning with a period per rfc821. + Raises SNPPDataError if there is an unexpected reply to the + DATA command; the return value from this method is the final + response code received when the all data is sent. + """ + self.putcmd("data") + (code,repl)=self.getreply() + if self.debuglevel >0 : print "data:", (code,repl) + if code <> 354: + raise SNPPDataError(code,repl) + else: + self.sendstr(quotedata(msg)) + self.sendstr("%s.%s" % (CRLF, CRLF)) + (code,msg)=self.getreply() + if self.debuglevel >0 : print "data:", (code,msg) + return (code,msg) + + # Level 2 - Optional Extensions + + def logi(self, loginid, password=""): + """SNPP 'logi' command -- logs in to the server. + + This command allows for a session login ID to be specified. It + is used to validate the person attempting to access the paging + terminal. If no LOGIn command is issued, "anonymous" user + status is assumed. + + """ + + if password: + args = "%s %s" % (loginid, password) + else: + args = loginid + return self.docmd("logi", args) + + def leve(self, servicelevel): + """SNPP 'leve' command -- sets server level for next PAGE. + + The LEVEl function is used to specify an optional alternate + level of service for the next PAGEr command. Ideally, + "ServiceLevel" should be an integer between 0 and 11 inclusive. + The TME protocol specifies ServiceLevel as follows: + + 0 - Priority + 1 - Normal (default) + 2 - Five minutes + 3 - Fifteen minutes + 4 - One hour + 5 - Four hours + 6 - Twelve hours + 7 - Twenty Four hours + 8 - Carrier specific '1' + 9 - Carrier specific '2' + 10 - Carrier specific '3' + 11 - Carrier specific '4' + + """ + + return self.docmd("leve", servicelevel) + + def aler(self, alertoverride): + """SNPP 'aler' command -- override alert setting. + + The optional ALERt command may be used to override the default + setting and specify whether or not to alert the subscriber upon + receipt of a message. This option, like the previous command, + alters the parameters submitted to the paging terminal using + the PAGEr command. The TME protocol specifies AlertOverride as + either 0-DoNotAlert, or 1-Alert. + + """ + + return self.docmd("aler", alertoverride) + + def cove(self, alternatearea): + """SNPP 'cove' command -- override coverage area. + + The optional COVErage command is used to override the + subscriber's default coverage area, and allow for the selection + of an alternate region. This option, like the previous + command, alters the parameters submitted to the paging terminal + using the PAGEr command. AlternateArea is a designator for one + of the following: + + - A subscriber-specific alternate coverage area + - A carrier-defined region available to subscribers + + """ + + return self.docmd("cove", alternatearea) + + def hold(self, timespec, gmtdiff=""): + """SNPP 'hold' command -- hold until specified time. + + The HOLDuntil command allows for the delayed delivery of a + message, to a particular subscriber, until after the time + specified. The time may be specified in local time (e.g. local + to the paging terminal), or with an added parameter specifying + offset from GMT (in other words, "-0600" specifies Eastern + Standard Time). This option, like the previous command, alters + the parameters submitted to the paging terminal using the PAGEr + command. + + """ + + if gmtdiff: + args = "%s %s" % (timespec, gmtdiff) + else: + args = timespec + + return self.docmd("hold", args) + + def call(self, callerid): + """SNPP 'call' command -- specify caller ID. + + The CALLerid function is a message-oriented function (as opposed + to the subscriber-oriented functions just described). This + allows for the specification of the CallerIdentifier function as + described in TME. This parameter is optional, and is at the + discretion of the carrier as to how it should be implemented or + used. + + """ + + return self.docmd("call", callerid) + + def subj(self, messagesubject): + """SNPP 'subj' command -- specify a message subject. + + The SUBJect function allows is a message-oriented function that + allows the sender to specify a subject for the next message to + be sent. This parameter is optional and is at the discretion of + the carrier as to how it should be implemented or used. + + """ + + return self.docmd("subj", messagesubject) + + + # Level 3 -- Two-Way Extensions + + # I didn't implement these because I got tired of typing this stuff + # in, and I doubt anyone's going to use this anyhow, seeing as how + # qpage doesn't do any of these extensions. Although this may + # change. + + # some useful methods + def sendpage(self, from_id, to_ids, msg): + """This command performs an entire mail transaction. + + The arguments are: + - from_id : The CallerID sending this mail, or None to + not send any CallerID. + - to_ids : A list of pager IDs to send this page to. A + bare string will be treated as a list with 1 + address. + - msg : The message to send. + + This method will return normally if the page is accepted for at + least one recipient. It returns a dictionary, with one entry for + each recipient that was refused. Each entry contains a tuple of + the SNPP error code and the accompanying error message sent by + the server. + + This method may raise the following exceptions: + + SNPPRecipientsRefused The server rejected for ALL recipients + (no mail was sent). + SNPPSenderRefused The server didn't accept the from_id. + SNPPDataError The server replied with an unexpected + error code + + Note: the connection will be open even after an exception is raised. + + Example: + + >>> import snpplib + >>> s=snpplib.SNPP("localhost") + >>> tolist=["parsingh","rhiannon","saffola","bob"] + >>> msg = 'Hey guys, what say we have pizza for dinner?' + >>> s.sendpage("neale",tolist,msg) + { "bob" : ( 550 ,"User unknown" ) } + >>> s.quit() + + In the above example, the message was accepted for delivery to three + of the four addresses, and one was rejected, with the error code + 550. If all addresses are accepted, then the method will return an + empty dictionary. + + """ + (code,resp) = self.call(from_id) + if code <> 250: + # CallerID command not implemented, too bad. + pass + senderrs={} + if type(to_ids) == types.StringType: + to_ids = [to_ids] + for each in to_ids: + (code,resp)=self.page(each) + if (code <> 250) and (code <> 251): + senderrs[each]=(code,resp) + if len(senderrs)==len(to_ids): + # the server refused all our recipients + self.rese() + raise SNPPRecipientsRefused(senderrs) + (code,resp)=self.data(msg) + if code <> 250: + self.rese() + raise SNPPDataError(code, resp) + (code,resp)=self.send() + if code <> 250: + self.rese() + raise SNPPDataError(code, resp) + #if we got here then somebody got our page + return senderrs + + + def close(self): + """Close the connection to the SNPP server.""" + if self.file: + self.file.close() + self.file = None + if self.sock: + self.sock.close() + self.sock = None + + + def quit(self): + """Terminate the SNPP session.""" + self.docmd("quit") + self.close() + + +# Test the sendmail method, which tests most of the others. +# Note: This always sends to localhost. +if __name__ == '__main__': + import sys, rfc822 + + def prompt(prompt): + sys.stdout.write(prompt + ": ") + return sys.stdin.readline().strip() + + fromaddr = prompt("From") + toaddrs = prompt("To").split(',') + print "Enter message, end with ^D:" + msg = '' + while 1: + line = sys.stdin.readline() + if not line: + break + msg = msg + line + print "Message length is " + `len(msg)` + + server = SNPP('localhost') + server.set_debuglevel(1) + server.sendpage(fromaddr, toaddrs, msg) + server.quit() diff --git a/src/python/spampot.py b/src/python/spampot.py new file mode 100755 index 0000000..0ee0cb9 --- /dev/null +++ b/src/python/spampot.py @@ -0,0 +1,325 @@ +#! /usr/bin/env python + +### spampot.py -- Spam honeypot SMTP server +### Copyright (C) 2003 Neale Pikett +### Time-stamp: <2003-05-06 09:08:52 neale> +### +### This 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, 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 software; see the file COPYING. If not, write to +### the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, +### USA. + +"""Spam honeypot SMTP server. + +This just sits on port 25 of whatever IP you pass in as an argument, and +spools every message out to MAILDIR. It tries to look like an old +Sendmail server, to maximize chances of being tagged as an open relay. + +""" + +import cStringIO as StringIO +import asynchat +import asyncore +import syslog +import smtplib +import rfc822 +import socket +import time +import sys +import os +import re +import struct +import maildir # get maildir.py from the same place you got this file + +# suid to this user +USER = 'spam' + +# host to relay probes for us +SMARTHOST = '127.0.0.1' + +# save multiple messages from the same IP? You probably don't want this +# -- it can consume a gigabyte a day. +SAVEDUPES = False + +# slow down if multiple mails are getting sent over a single connection? +# This could save bandwidth. +TARPIT = True + +# chroot to this directory and spool messages there +MAILDIR = '/home/spam/Maildir/spampot' + +# My hostname +HOST = socket.gethostname() + +# write to this PID file +PIDFILE = '/var/run/spampot.pid' + +# syslog levels (you shouldn't need to change this) +LEVELS = {'info': syslog.LOG_INFO, + 'warning': syslog.LOG_WARNING, + 'error': syslog.LOG_ERR} + +### + +# Hosts seen +seen = {} + +def shescape(str): + return "'" + str.replace("'", "'\"'\"'") + "'" + + +class Daemon: + """Helpful class to make a process a daemon""" + + def __init__(self, pidfile): + try: + f = file(pidfile, "r") + pid = int(f.read()) + f.close() + os.kill(pid, 0) + print "Already running at pid %d" % pid + sys.exit(1) + except (IOError, OSError, ValueError): + pass + self.pidf = file(pidfile, 'w') + + def daemonize(self): + self.pid = os.fork() + if self.pid: + self.pidf.write("%d\n" % self.pid) + sys.exit(0) + # Decouple from parent + self.pidf.close() + os.chdir("/") + os.setsid() + os.umask(0) + os.close(sys.stdin.fileno()) + os.close(sys.stdout.fileno()) + os.close(sys.stderr.fileno()) + syslog.syslog(syslog.LOG_INFO, "starting") + return self.pid + + def jail(self, root, user=None, group=None): + uid, gid = None, None + if group: + import grp + + gr = grp.getgrnam(group) + gid = gr[2] + if user: + import pwd + + pw = pwd.getpwnam(user) + uid = pw[2] + if not gid: gid = pw[3] + os.chroot(root) + os.chdir('/') + if gid: os.setgid(gid) + if uid: os.setuid(uid) + + +class Listener(asyncore.dispatcher): + """Listens for incoming socket connections and spins off + dispatchers created by a factory callable. + """ + + def __init__(self, bindaddr, port, + factory, factoryArgs=()): + asyncore.dispatcher.__init__(self) + self.factory = factory + self.factoryArgs = factoryArgs + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind((bindaddr, port)) + self.listen(40) + syslog.syslog(syslog.LOG_INFO, 'Listening on %s:%d' % (bindaddr, port)) + + def handle_accept(self): + # If an incoming connection is instantly reset, eg. by following a + # link in the web interface then instantly following another one or + # hitting stop, handle_accept() will be triggered but accept() will + # return None. + result = self.accept() + if result: + clientSocket, clientAddress = result + args = [clientSocket] + list(self.factoryArgs) + self.factory(*args) + + +class Server(asynchat.async_chat): + """A stupid SMTP server.""" + + def __init__(self, sock): + self.msg_count = 0 + self.host = 'internal.nat' + self.request = '' + self.hello = None + self.reset() + asynchat.async_chat.__init__(self) + self.set_socket(sock) + + def reset(self): + self.mailfrom = None + self.rcptto = [] + + def log(self, message): + syslog.syslog(syslog.LOG_INFO, message) + + def log_info(self, message, type='info'): + lvl = LEVELS.get(type, syslog.LOG_INFO) + syslog.syslog(lvl, message) + + def handle_connect(self): + self.peername = self.getpeername() + self.sockname = self.getsockname() + self.socknamehex = "%X" % struct.unpack('L', socket.inet_aton(self.sockname[0])) + self.set_terminator('\r\n') + + self.log('Connect from %s' % (self.peername,)) + now = time.localtime() + ts = time.strftime('%a, ' + str(now[2]) + ' %b %y %H:%M:%S %Z') + self.push("220 %s Sendmail ready at %s\r\n" % (self.host, ts)) + + def handle_close(self): + self.log('Close from %s; relayed %d messages' % (self.peername, self.msg_count)) + self.close() + + def collect_incoming_data(self, data): + self.request += data + + def envelope_found_terminator(self): + data = self.request + self.request = "" + command = data[:4].upper() + if command in ["HELO", "EHLO"]: + whom = data[5:].strip() + self.hello = whom + self.push("250 Hello %s, pleased to meet you.\r\n" % whom) + elif command == 'MAIL': + whom = data[10:].strip() + self.mailfrom = whom + self.push("250 %s... Sender ok\r\n" % whom) + elif command == 'RCPT': + whom = data[8:].strip() + self.rcptto.append(whom) + self.push("250 %s... Recipient ok\r\n" % whom) + elif command == "DATA": + self.set_terminator('\r\n.\r\n') + self.found_terminator = self.data_found_terminator + self.push('354 Enter mail, end with "." on a line by itself\r\n') + elif command == "QUIT": + self.push("221 %s closing connection\r\n" % self.host) + self.close_when_done() + elif command == "RSET": + self.reset() + self.push('250 Reset state\r\n') + else: + self.push("500 Command unrecognized\r\n") + found_terminator = envelope_found_terminator + + def data_found_terminator(self): + self.message = self.request + self.request = '' + self.set_terminator('\r\n') + self.found_terminator = self.envelope_found_terminator + self.deliver_message() + self.push("250 Mail accepted\r\n") + self.reset() + + def relay_message(self): + s = os.popen("/usr/lib/sendmail -f %s %s" + % (shescape(self.string), + ' '.join([shescape(s) for s in self.rcptto])), + 'w') + s.write(self.message) + s.close() + + probe_re = None + + def is_probe(self): + """Returns true if the current message is a probe message""" + + # Compile the probe regular expression the first time through + if not self.probe_re: + self.probe_re = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|%s:25' % self.socknamehex, + re.IGNORECASE) + + # If it's not the first message this connection, it's probably + # not a probe. + if self.msg_count: + return False + + # Probes also don't have multiple recipients + if len(self.rcptto) != 1: + return False + + # And they're short + if len(self.message) > 1024: + return False + + # Check for the probe regex + if self.probe_re.search(self.message): + # we have a bite: now do some more intense investigation + f = StringIO.StringIO(self.message) + m = rfc822.Message(f) + + # IP address in subject? + subj = m.get('Subject') + if subj and subj.find(self.sockname[0]) != -1: + return True + + # Hex-encoded IP address anywhere in message? + if m.find(self.socknamehex) != -1: + return True + + return False + + def deliver_message(self): + global seen + + headers = [ + "SMTP-Date: %s" % time.ctime(), + "SMTP-Sock: %s:%d" % self.sockname, + "SMTP-Peer: %s:%d" % self.peername, + "SMTP-Hello: %s" % self.hello, + "SMTP-Mail-From: %s" % self.mailfrom, + "SMTP-Messages-This-Connection: %s" % self.msg_count, + ] + for t in self.rcptto: + headers.append("SMTP-Rcpt-To: %s" % t) + if self.is_probe(): + self.relay_message() + self.log('Relayed probe from=%s to=%s' % (self.mailfrom, self.rcptto)) + headers.append("SMTP-Relayed: Yes") + msg_count = seen.get(self.peername) + 1 + seen[self.peername] = msg_count + if msg_count in (0, 1, 2, 3, 4, 8, 64, 512, 4096, 32768, 262144): + # Hopefully nobody running this will ever hit that last one ;) + msg = '\r\n'.join(headers) + '\r\n' + self.message + m = maildir.write(time.strftime('%Y-%m'), msg) + self.log('Trapped from=%s to=%s msg_count=%d' % (self.mailfrom, self.rcptto, msg_count)) + self.msg_count += 1 + + +def main(): + dmn = Daemon(PIDFILE) + syslog.openlog('spampot', syslog.LOG_PID, syslog.LOG_MAIL) + listener = Listener(sys.argv[1], 25, Server) + dmn.jail(MAILDIR, USER) + dmn.daemonize() + asyncore.loop() + + +if __name__ == '__main__': + main() + diff --git a/src/python/status.py b/src/python/status.py new file mode 100755 index 0000000..a2e14e6 --- /dev/null +++ b/src/python/status.py @@ -0,0 +1,329 @@ +#! /usr/bin/python + +"""Status dealiemajigger for dzen2 (or others, probably). + +This outputs: + + * Debian packages in need of upgrade (if you have the apt module) + * master volume (if you have alsa) + * battery life (if you have a battery) + * load average if it goes over 0.25 + * current date and time + +It prints it again whenever any of these change. It also watches for Mute, +Volume Up, and Volume Down key events on multimedia keyboards, and adjusts +the mixer appropriately. + +Lastly, it registers itself with D-Bus so you can send yourself notices +like so: + + $ dbus-send --dest=org.woozle.Status \ + /org/woozle/Status \ + org.woozle.Status.notice \ + string:'this is where your message goes' + +They will show up green in dzen2. + +This only polls the ALSA mixer settings, and those only every minute. +Master mixer changes will take from 0 to 60 seconds to register if you +don't use the multimedia keys on your keyboard. + +You need to be running HAL for this to do much other than tell you +the time. Don't worry, HAL is pretty small. + +""" + +import signal +import dbus +import dbus.service +import sys +import os +import gobject +import time +from sets import Set +try: + import apt + import socket +except ImportError: + apt = None + +try: + import alsaaudio +except ImportError: + alsaaudio = None + +# Set to true if you want dzen2 colors +dzen2 = False + +def color(name, text): + if dzen2: + return '^fg(%s)%s^fg()' % (name, text) + else: + return '-=[ %s ]=-' % text + +class Status(dbus.service.Object): + debug = False + + def __init__(self, *args, **kwargs): + dbus.service.Object.__init__(self, *args, **kwargs) + + self.system_bus = dbus.SystemBus() + + self.hal_manager = self.system_bus.get_object('org.freedesktop.Hal', + '/org/freedesktop/Hal/Manager') + self.hal_computer = self.system_bus.get_object('org.freedesktop.Hal', + '/org/freedesktop/Hal/devices/computer') + + # Listen for HAL events + self.system_bus.add_signal_receiver(self.handle_hal_event, + dbus_interface='org.freedesktop.Hal.Device', + path_keyword='path', + member_keyword='member') + + # For now I only care about battery 0. + batteries = self.hal_manager.FindDeviceByCapability('battery', + dbus_interface='org.freedesktop.Hal.Manager') + if batteries: + bat = self.system_bus.get_object('org.freedesktop.Hal', + batteries[0]) + self.battery = dbus.Interface(bat, + dbus_interface='org.freedesktop.Hal.Device') + else: + self.battery = None + + # Audio mixer, if we have the ALSA module installed. + if alsaaudio: + try: + self.mixer = alsaaudio.Mixer() + except alsaaudio.ALSAAudioError: + # Some weird mixers have no Master control (eg. Mac Mini) + self.mixer = alsaaudio.Mixer('PCM') + except ImportError: + self.mixer = None + else: + self.mixer = None + + # How many debian packages can be upgraded? + self.upgradable = [] + + # All my children + self.pids = Set() + + def run(self, file, *args): + pid = os.spawnlp(os.P_NOWAIT, file, file, *args) + self.pids.add(pid) + + def battery_level(self): + if self.battery: + return self.battery.GetProperty('battery.charge_level.percentage') + + def battery_capacity_state(self): + if self.battery: + try: + return self.battery.GetProperty('battery.charge_level.capacity_state') + except dbus.exceptions.DBusException: + bat = self.battery_level() + if bat > 20: + return 'ok' + else: + return 'battery low' + + def suspend(self, num_secs_to_wakeup=0): + def ignore(*d): + pass + + self.run('screenlock') + self.hal_computer.Suspend(num_secs_to_wakeup, + dbus_interface='org.freedesktop.Hal.Device.SystemPowerManagement', + reply_handler=ignore, + error_handler=ignore) + + def muted(self): + if self.mixer: + try: + return self.mixer.getmute()[0] + except alsaaudio.ALSAAudioError: + pass + raise ValueError('No mute control') + + def toggle_mute(self): + try: + mute = self.muted() + self.mixer.setmute(not mute) + self.update() + except ValueError: + pass + + @dbus.service.method('org.woozle.Status', + in_signature='d') + def adjust_volume(self, adj): + if self.mixer: + vol = self.mixer.getvolume()[0] + vol += adj + vol = min(vol, 100) + vol = max(vol, 0) + self.mixer.setvolume(vol) + self.update() + + def volume(self): + if self.mixer: + try: + mute = self.muted() + except ValueError: + mute = False + if mute: + return '--' + else: + return str(self.mixer.getvolume()[0]) + + def handle_button_pressed(self, button, path): + if button == 'lid': + lid = self.system_bus.get_object('org.freedesktop.Hal', + path) + if (lid.GetProperty('button.state.value', + dbus_interface='org.freedesktop.Hal.Device') and + not os.path.exists(os.path.expanduser('~/nosusp'))): + self.suspend() + elif button == 'mute': + self.toggle_mute() + elif button == 'volume-down': + self.adjust_volume(-5) + elif button == 'volume-up': + self.adjust_volume(+5) + elif button == 'sleep': + self.suspend() + elif self.debug: + self.update('button pressed: %s' % button) + + def handle_property_modified(self, name, added, removed): + if name == 'battery.charge_level.percentage': + self.update() + elif name.startswith('battery.'): + pass + else: + self.update('property modified: %s' % name) + + def handle_hal_event(self, *d, **k): + member = k.get('member') + if member == 'Condition': + if d[0] == 'ButtonPressed': + self.handle_button_pressed(d[1], k.get('path')) + elif member == 'PropertyModified': + assert int(d[0]) == len(d[1]) + for p in d[1]: + self.handle_property_modified(*p) + else: + self.update('%s: %s' % (d, kwargs)) + + def update(self, msg=None): + out = [] + if msg: + out.append(color('green', msg)) + + if self.upgradable: + out.append(color('cyan', '%d upgradable packages' % self.upgradable)) + + la = file('/proc/loadavg').read() + load = la.split()[0] + if float(load) > 0.2: + out.append('L %s' % load) + + vol = self.volume() + if vol: + out.append('V %s' % vol) + + bat = self.battery_level() + if bat: + out.append('B %s%%' % bat) + + cap_state = self.battery_capacity_state() + if cap_state != 'ok': + out.append(color('green', cap)) + + out.append(time.strftime('%b %e %l:%M%P')) + + print ' '.join(out) + sys.stdout.flush() + + try: + pid, status = os.waitpid(0, os.WNOHANG) + self.pids.remove(pid) + except (OSError, KeyError): + pass + + return True + + @dbus.service.method('org.woozle.Status', + in_signature='s') + def notice(self, msg): + self.update(msg) + + @dbus.service.method('org.woozle.Status', + in_signature='') + def restart(self): + re_exec() + + @dbus.service.method('org.woozle.Status', + in_signature='b') + def set_debug(self, debug): + self.debug = debug + + def debcheck(self): + # I used to do this in a thread. Unfortunately, this expensive + # operation kept the entire apt cache around in RAM forever, + # making the status program the biggest memory user on my entire + # machine. That offends my sense of aesthetics, so now we fork + # and read from a pipe. + def cb(source, cb_condition): + s = source.recv(8192) + self.upgradable = int(s) + return False + + a,b = socket.socketpair() + if os.fork(): + gobject.io_add_watch(a, gobject.IO_IN, cb) + else: + l = [p for p in apt.Cache() if p.isUpgradable] + b.send(str(len(l))) + sys.exit(0) + return True + + def start(self): + self.update() + gobject.timeout_add(12 * 1000, self.update) + if apt: + self.debcheck() + gobject.timeout_add(900 * 1000, self.debcheck) + + +def re_exec(*ign): + os.execv(sys.argv[0], sys.argv) + + +def main(): + import signal + import gobject + import dbus.mainloop.glib + + # Set up main loop + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + # kill -HUP makes this re-exec itself + signal.signal(signal.SIGHUP, re_exec) + + # Register ourselves as a service + session_bus = dbus.SessionBus() + dbus_name = dbus.service.BusName('org.woozle.Status', + session_bus) + + # Our founder + s = Status(session_bus, '/org/woozle/Status') + + # Go! + loop = gobject.MainLoop() + s.start() + loop.run() + + +if __name__ == '__main__': + main() diff --git a/src/python/watch.py b/src/python/watch.py new file mode 100755 index 0000000..3801189 --- /dev/null +++ b/src/python/watch.py @@ -0,0 +1,130 @@ +#!/usr/bin/python + +"""watch.py -- Web site change notification tool +Author: Neale Pickett +Time-stamp: <2007-09-19 15:00:43 neale> + +Usage: urlwatch [-c|--config WATCHRC] + +This is something you can run from a cron job to notify you of changes +to a web site. You just set up a ~/.watchrc file, and run watcher.py +from cron. It mails you when a page has changed. + +I use this to check for new software releases on sites that just change +web pages; my wife uses it to check pages for classes she's in. + +You'll want a ~/.watchrc that looks something like this: + + to: your.email.address@example.com + http://www.example.com/path/to/some/page.html + +The 'to:' line tells watch.py where to send change notification email. +You can also specify 'from:' for an address the message should come from +(defaults to whatever to: is), and 'host:' for what SMTP server to send +the message through (defaults to localhost). + +When watch.py checks a URL for the first time, it will send you a +message (so you know it's working) and write some funny characters after +the URL in the .watchrc file. This is normal--watch.py uses these +characters to remember what the page looked like the last time it +checked. + +""" + +import os +import urllib2 as urllib +import sha +import smtplib +import sys + +host = 'localhost' +fromaddr = None +toaddr = None + +rc = os.path.expanduser('~/.watchrc') + +def usage(errmsg=None): + if errmsg: + print "error: %s" % errmsg + print + + print globals()['__doc__'] + + if errmsg: + sys.exit(1) + sys.exit(0) + +def myhash(data): + return sha.new(data).hexdigest() + +def notify(fromaddr, toaddr, url): + msg = """From: URL Watcher <%(from)s> +To: %(to)s +Subject: %(url)s changed + +%(url)s has changed! +""" % {'from': fromaddr, + 'to': toaddr, + 'url': url} + s = smtplib.SMTP(host) + s.sendmail(fromaddr, [toaddr], msg) + s.quit() + +def watch(rcfile): + global host, fromaddr, toaddr + + f = open(rcfile) + outlines = [] + for line in f.xreadlines(): + if line[0] == '#': + continue + + line = line.strip() + if not line: + continue + + splits = line.split(' ', 1) + url = splits[0] + if url == 'from:': + fromaddr = splits[1] + elif url == 'to:': + toaddr = splits[1] + if not fromaddr: + fromaddr = toaddr + elif url == 'mailhost:': + host = splits[1] + else: + if (not fromaddr) or (not toaddr): + raise ValueError("must set to: before any urls") + page = urllib.urlopen(url).read() + ph = myhash(page) + + try: + h = splits[1] + except IndexError: + h = None + if h != ph: + notify(fromaddr, toaddr, url) + line = '%s %s' % (url, ph) + outlines.append(line) + + f.close() + + f = open(rcfile, 'w') + f.write('\n'.join(outlines) + '\n') + f.close() + +if __name__ == '__main__': + import getopt + import sys + + opts, args = getopt.getopt(sys.argv[1:], 'c:h', ['config=', 'help']) + for k,v in opts: + if k in ('-c', '--config'): + rc = v + elif k in ('-h', '--help'): + usage() + else: + usage("Unknown option: %s" % k) + + watch(rc) diff --git a/src/xss.mdwn b/src/xss.mdwn index 2a275f6..57caa47 100644 --- a/src/xss.mdwn +++ b/src/xss.mdwn @@ -1,4 +1,4 @@ -[[!meta title="xss"]] +[[!meta title="xss: X Screensaver Construction Kit"]] [xss](http://woozle.org/~neale/src/xss) is a suite of X screensaver utilities. You can use shell scripts to glue the tools together to