From 37da2899f40661e3e9631e497da8dc59b971cbd0 Mon Sep 17 00:00:00 2001 From: "Charles.Forsyth" Date: Fri, 22 Dec 2006 17:07:39 +0000 Subject: 20060303a --- appl/cmd/9660srv.b | 1504 ++++++++++++ appl/cmd/9export.b | 180 ++ appl/cmd/9srvfs.b | 99 + appl/cmd/9win.b | 453 ++++ appl/cmd/B.b | 107 + appl/cmd/archfs.b | 630 +++++ appl/cmd/auplay.b | 114 + appl/cmd/auth/aescbc.b | 254 ++ appl/cmd/auth/changelogin.b | 305 +++ appl/cmd/auth/convpasswd.b | 120 + appl/cmd/auth/countersigner.b | 59 + appl/cmd/auth/createsignerkey.b | 144 ++ appl/cmd/auth/factotum/authio.m | 80 + appl/cmd/auth/factotum/factotum.b | 978 ++++++++ appl/cmd/auth/factotum/feedkey.b | 321 +++ appl/cmd/auth/factotum/mkfile | 27 + appl/cmd/auth/factotum/proto/infauth.b | 362 +++ appl/cmd/auth/factotum/proto/keyreps.b | 173 ++ appl/cmd/auth/factotum/proto/keyreps.m | 23 + appl/cmd/auth/factotum/proto/mkfile | 22 + appl/cmd/auth/factotum/proto/p9any.b | 232 ++ appl/cmd/auth/factotum/proto/pass.b | 29 + appl/cmd/auth/factotum/rpc.b | 68 + appl/cmd/auth/getpk.b | 83 + appl/cmd/auth/keyfs.b | 806 ++++++ appl/cmd/auth/keysrv.b | 199 ++ appl/cmd/auth/logind.b | 244 ++ appl/cmd/auth/mkauthinfo.b | 125 + appl/cmd/auth/mkfile | 38 + appl/cmd/auth/passwd.b | 290 +++ appl/cmd/auth/secstore.b | 317 +++ appl/cmd/auth/signer.b | 132 + appl/cmd/auth/verify.b | 85 + appl/cmd/auxi/cpuslave.b | 79 + appl/cmd/auxi/digest.b | 91 + appl/cmd/auxi/fpgaload.b | 67 + appl/cmd/auxi/mangaload.b | 362 +++ appl/cmd/auxi/mkfile | 24 + appl/cmd/auxi/pcmcia.b | 491 ++++ appl/cmd/auxi/rdbgsrv.b | 222 ++ appl/cmd/auxi/rstyxd.b | 114 + appl/cmd/avr/burn.b | 859 +++++++ appl/cmd/avr/mkfile | 10 + appl/cmd/basename.b | 50 + appl/cmd/bind.b | 66 + appl/cmd/bit2gif.b | 86 + appl/cmd/broke.b | 84 + appl/cmd/bytes.b | 212 ++ appl/cmd/cal.b | 295 +++ appl/cmd/cat.b | 57 + appl/cmd/cd.b | 48 + appl/cmd/chgrp.b | 58 + appl/cmd/chmod.b | 125 + appl/cmd/cleanname.b | 45 + appl/cmd/cmp.b | 151 ++ appl/cmd/comm.b | 124 + appl/cmd/cook.b | 1924 +++++++++++++++ appl/cmd/cp.b | 237 ++ appl/cmd/cprof.b | 190 ++ appl/cmd/cpu.b | 168 ++ appl/cmd/crypt.b | 234 ++ appl/cmd/date.b | 71 + appl/cmd/dbfs.b | 518 ++++ appl/cmd/dbm/delete.b | 34 + appl/cmd/dbm/fetch.b | 37 + appl/cmd/dbm/keys.b | 32 + appl/cmd/dbm/list.b | 34 + appl/cmd/dbm/mkfile | 19 + appl/cmd/dbm/store.b | 69 + appl/cmd/dd.b | 625 +++++ appl/cmd/dial.b | 148 ++ appl/cmd/diff.b | 858 +++++++ appl/cmd/disdep.b | 250 ++ appl/cmd/disdump.b | 52 + appl/cmd/disk/format.b | 755 ++++++ appl/cmd/disk/ftl.b | 911 +++++++ appl/cmd/disk/kfs.b | 3842 +++++++++++++++++++++++++++++ appl/cmd/disk/kfscmd.b | 53 + appl/cmd/disk/mbr.b | 134 + appl/cmd/disk/mkext.b | 377 +++ appl/cmd/disk/mkfile | 25 + appl/cmd/disk/mkfs.b | 778 ++++++ appl/cmd/disk/prep/calc.tab.b | 454 ++++ appl/cmd/disk/prep/calc.tab.m | 7 + appl/cmd/disk/prep/calc.y | 174 ++ appl/cmd/disk/prep/fdisk.b | 925 +++++++ appl/cmd/disk/prep/mkfile | 26 + appl/cmd/disk/prep/pedit.b | 504 ++++ appl/cmd/disk/prep/pedit.m | 53 + appl/cmd/disk/prep/prep.b | 509 ++++ appl/cmd/dossrv.b | 3432 ++++++++++++++++++++++++++ appl/cmd/du.b | 163 ++ appl/cmd/echo.b | 36 + appl/cmd/ed.b | 1588 ++++++++++++ appl/cmd/emuinit.b | 110 + appl/cmd/env.b | 53 + appl/cmd/export.b | 57 + appl/cmd/fc.b | 612 +++++ appl/cmd/fcp.b | 312 +++ appl/cmd/fmt.b | 204 ++ appl/cmd/fone.b | 560 +++++ appl/cmd/fortune.b | 100 + appl/cmd/freq.b | 112 + appl/cmd/fs.b | 109 + appl/cmd/fs/and.b | 65 + appl/cmd/fs/bundle.b | 195 ++ appl/cmd/fs/chstat.b | 185 ++ appl/cmd/fs/compose.b | 100 + appl/cmd/fs/depth.b | 49 + appl/cmd/fs/entries.b | 86 + appl/cmd/fs/eval.b | 648 +++++ appl/cmd/fs/exec.b | 162 ++ appl/cmd/fs/filter.b | 64 + appl/cmd/fs/ls.b | 97 + appl/cmd/fs/match.b | 79 + appl/cmd/fs/merge.b | 187 ++ appl/cmd/fs/mergewrite.b | 186 ++ appl/cmd/fs/mkfile | 60 + appl/cmd/fs/mode.b | 120 + appl/cmd/fs/not.b | 48 + appl/cmd/fs/or.b | 65 + appl/cmd/fs/path.b | 77 + appl/cmd/fs/pipe.b | 223 ++ appl/cmd/fs/print.b | 51 + appl/cmd/fs/proto.b | 388 +++ appl/cmd/fs/query.b | 130 + appl/cmd/fs/readfile.b | 144 ++ appl/cmd/fs/run.b | 60 + appl/cmd/fs/select.b | 56 + appl/cmd/fs/setroot.b | 104 + appl/cmd/fs/size.b | 54 + appl/cmd/fs/template.b | 35 + appl/cmd/fs/unbundle.b | 259 ++ appl/cmd/fs/void.b | 33 + appl/cmd/fs/walk.b | 233 ++ appl/cmd/fs/write.b | 111 + appl/cmd/ftest.b | 153 ++ appl/cmd/ftpfs.b | 1959 +++++++++++++++ appl/cmd/getauthinfo.b | 185 ++ appl/cmd/getfile.b | 74 + appl/cmd/gettar.b | 248 ++ appl/cmd/gif2bit.b | 101 + appl/cmd/grep.b | 155 ++ appl/cmd/gunzip.b | 139 ++ appl/cmd/gzip.b | 228 ++ appl/cmd/idea.b | 116 + appl/cmd/import.b | 192 ++ appl/cmd/install/NOTICE | 6 + appl/cmd/install/applylog.b | 699 ++++++ appl/cmd/install/arch.b | 288 +++ appl/cmd/install/arch.m | 36 + appl/cmd/install/archfs.b | 579 +++++ appl/cmd/install/archfs.m | 7 + appl/cmd/install/ckproto.b | 267 ++ appl/cmd/install/create.b | 445 ++++ appl/cmd/install/eproto.b | 357 +++ appl/cmd/install/info.b | 73 + appl/cmd/install/inst.b | 500 ++++ appl/cmd/install/install.b | 430 ++++ appl/cmd/install/log.b | 76 + appl/cmd/install/logs.b | 287 +++ appl/cmd/install/logs.m | 44 + appl/cmd/install/mergelog.b | 239 ++ appl/cmd/install/mkfile | 43 + appl/cmd/install/mkproto.b | 99 + appl/cmd/install/proto.b | 320 +++ appl/cmd/install/proto.m | 6 + appl/cmd/install/proto2list.b | 209 ++ appl/cmd/install/protocaller.m | 8 + appl/cmd/install/updatelog.b | 386 +++ appl/cmd/install/wdiff.b | 148 ++ appl/cmd/install/wfind.b | 204 ++ appl/cmd/install/wrap.b | 684 ++++++ appl/cmd/install/wrap.m | 41 + appl/cmd/install/wrap2list.b | 305 +++ appl/cmd/iostats.b | 635 +++++ appl/cmd/ip/bootpd.b | 662 +++++ appl/cmd/ip/dhcp.b | 162 ++ appl/cmd/ip/mkfile | 30 + appl/cmd/ip/nppp/mkfile | 24 + appl/cmd/ip/nppp/modem.b | 469 ++++ appl/cmd/ip/nppp/modem.m | 47 + appl/cmd/ip/nppp/pppchat.b | 322 +++ appl/cmd/ip/nppp/ppplink.b | 782 ++++++ appl/cmd/ip/nppp/ppptest.b | 90 + appl/cmd/ip/nppp/script.b | 171 ++ appl/cmd/ip/nppp/script.m | 15 + appl/cmd/ip/obootpd.b | 777 ++++++ appl/cmd/ip/ping.b | 369 +++ appl/cmd/ip/ppp/mkfile | 27 + appl/cmd/ip/ppp/modem.b | 468 ++++ appl/cmd/ip/ppp/modem.m | 41 + appl/cmd/ip/ppp/pppclient.b | 216 ++ appl/cmd/ip/ppp/pppclient.m | 31 + appl/cmd/ip/ppp/pppdial.b | 283 +++ appl/cmd/ip/ppp/pppgui.b | 373 +++ appl/cmd/ip/ppp/pppgui.m | 21 + appl/cmd/ip/ppp/ppptest.b | 86 + appl/cmd/ip/ppp/script.b | 168 ++ appl/cmd/ip/ppp/script.m | 14 + appl/cmd/ip/rip.b | 620 +++++ appl/cmd/ip/sntp.b | 313 +++ appl/cmd/ip/tftpd.b | 514 ++++ appl/cmd/ip/virgild.b | 127 + appl/cmd/irtest.b | 70 + appl/cmd/itest.b | 478 ++++ appl/cmd/itreplay.b | 230 ++ appl/cmd/kill.b | 146 ++ appl/cmd/lc.b | 156 ++ appl/cmd/lego/clock.b | 214 ++ appl/cmd/lego/clockface.b | 384 +++ appl/cmd/lego/firmdl.b | 294 +++ appl/cmd/lego/link.b | 603 +++++ appl/cmd/lego/mkfile | 23 + appl/cmd/lego/rcxsend.b | 240 ++ appl/cmd/lego/rcxsend.m | 6 + appl/cmd/lego/send.b | 86 + appl/cmd/lego/timers.b | 263 ++ appl/cmd/lego/timers.m | 17 + appl/cmd/limbo/arg.m | 50 + appl/cmd/limbo/asm.b | 263 ++ appl/cmd/limbo/com.b | 1387 +++++++++++ appl/cmd/limbo/decls.b | 1177 +++++++++ appl/cmd/limbo/dis.b | 560 +++++ appl/cmd/limbo/disoptab.m | 355 +++ appl/cmd/limbo/ecom.b | 2345 ++++++++++++++++++ appl/cmd/limbo/gen.b | 1012 ++++++++ appl/cmd/limbo/isa.m | 247 ++ appl/cmd/limbo/lex.b | 1146 +++++++++ appl/cmd/limbo/limbo.b | 3099 +++++++++++++++++++++++ appl/cmd/limbo/limbo.m | 527 ++++ appl/cmd/limbo/limbo.y | 1973 +++++++++++++++ appl/cmd/limbo/mkfile | 35 + appl/cmd/limbo/nodes.b | 1402 +++++++++++ appl/cmd/limbo/opname.m | 109 + appl/cmd/limbo/optim.b | 3 + appl/cmd/limbo/sbl.b | 397 +++ appl/cmd/limbo/stubs.b | 575 +++++ appl/cmd/limbo/typecheck.b | 3223 ++++++++++++++++++++++++ appl/cmd/limbo/types.b | 4234 ++++++++++++++++++++++++++++++++ appl/cmd/listen.b | 261 ++ appl/cmd/lockfs.b | 773 ++++++ appl/cmd/logfile.b | 259 ++ appl/cmd/look.b | 393 +++ appl/cmd/lookman.b | 250 ++ appl/cmd/ls.b | 318 +++ appl/cmd/lstar.b | 120 + appl/cmd/man.b | 199 ++ appl/cmd/man2txt.b | 79 + appl/cmd/manufacture.b | 42 + appl/cmd/mash/builtins.b | 347 +++ appl/cmd/mash/depends.b | 228 ++ appl/cmd/mash/dump.b | 199 ++ appl/cmd/mash/exec.b | 401 +++ appl/cmd/mash/expr.b | 158 ++ appl/cmd/mash/eyacc.b | 2785 +++++++++++++++++++++ appl/cmd/mash/eyaccpar | 223 ++ appl/cmd/mash/history.b | 206 ++ appl/cmd/mash/lex.b | 547 +++++ appl/cmd/mash/make.b | 723 ++++++ appl/cmd/mash/mash.b | 154 ++ appl/cmd/mash/mash.m | 372 +++ appl/cmd/mash/mash.y | 269 ++ appl/cmd/mash/mashfile | 36 + appl/cmd/mash/mashlib.b | 60 + appl/cmd/mash/mashparse.b | 662 +++++ appl/cmd/mash/mashparse.m | 56 + appl/cmd/mash/misc.b | 313 +++ appl/cmd/mash/mkfile | 78 + appl/cmd/mash/serve.b | 154 ++ appl/cmd/mash/symb.b | 265 ++ appl/cmd/mash/tk.b | 603 +++++ appl/cmd/mash/xeq.b | 543 ++++ appl/cmd/mathcalc.b | 79 + appl/cmd/mc.b | 2547 +++++++++++++++++++ appl/cmd/md5sum.b | 65 + appl/cmd/mdb.b | 335 +++ appl/cmd/memfs.b | 648 +++++ appl/cmd/metamorph.b | 94 + appl/cmd/mk/ar.m | 26 + appl/cmd/mk/mk.b | 4211 +++++++++++++++++++++++++++++++ appl/cmd/mk/mkbinds | 2 + appl/cmd/mk/mkconfig | 28 + appl/cmd/mk/mkfile | 19 + appl/cmd/mk/mksubdirs | 16 + appl/cmd/mkdir.b | 75 + appl/cmd/mkfile | 219 ++ appl/cmd/mntgen.b | 188 ++ appl/cmd/mount.b | 348 +++ appl/cmd/mouse.b | 394 +++ appl/cmd/mpc/mkfile | 14 + appl/cmd/mpc/qconfig.b | 193 ++ appl/cmd/mpc/qflash.b | 188 ++ appl/cmd/mprof.b | 260 ++ appl/cmd/mv.b | 184 ++ appl/cmd/ndb/cs.b | 676 +++++ appl/cmd/ndb/csquery.b | 97 + appl/cmd/ndb/dns.b | 1860 ++++++++++++++ appl/cmd/ndb/dnsquery.b | 177 ++ appl/cmd/ndb/mkfile | 28 + appl/cmd/ndb/mkhash.b | 119 + appl/cmd/ndb/query.b | 135 + appl/cmd/ndb/registry.b | 671 +++++ appl/cmd/ndb/regquery.b | 104 + appl/cmd/netkey.b | 166 ++ appl/cmd/netstat.b | 91 + appl/cmd/newer.b | 36 + appl/cmd/ns.b | 157 ++ appl/cmd/nsbuild.b | 41 + appl/cmd/os.b | 155 ++ appl/cmd/p.b | 141 ++ appl/cmd/palm/connex.b | 124 + appl/cmd/palm/desklink.b | 843 +++++++ appl/cmd/palm/desklink.m | 90 + appl/cmd/palm/mkfile | 16 + appl/cmd/palm/palmsrv.b | 901 +++++++ appl/cmd/pause.b | 17 + appl/cmd/plumb.b | 115 + appl/cmd/plumber.b | 766 ++++++ appl/cmd/prof.b | 243 ++ appl/cmd/promptstring.b | 66 + appl/cmd/ps.b | 61 + appl/cmd/puttar.b | 183 ++ appl/cmd/pwd.b | 28 + appl/cmd/ramfile.b | 97 + appl/cmd/randpass.b | 45 + appl/cmd/raw2iaf.b | 122 + appl/cmd/rawdbfs.b | 813 ++++++ appl/cmd/rcmd.b | 170 ++ appl/cmd/rdp.b | 1230 ++++++++++ appl/cmd/read.b | 62 + appl/cmd/rioimport.b | 620 +++++ appl/cmd/rm.b | 99 + appl/cmd/runas.b | 60 + appl/cmd/sed.b | 908 +++++++ appl/cmd/sendmail.b | 252 ++ appl/cmd/sh/arg.b | 181 ++ appl/cmd/sh/csv.b | 244 ++ appl/cmd/sh/doc/History | 14 + appl/cmd/sh/echo.b | 96 + appl/cmd/sh/expr.b | 281 +++ appl/cmd/sh/file2chan.b | 459 ++++ appl/cmd/sh/mkfile | 60 + appl/cmd/sh/regex.b | 220 ++ appl/cmd/sh/sexprs.b | 271 ++ appl/cmd/sh/sh.b | 2843 +++++++++++++++++++++ appl/cmd/sh/sh.y | 2592 +++++++++++++++++++ appl/cmd/sh/std.b | 812 ++++++ appl/cmd/sh/string.b | 212 ++ appl/cmd/sh/test.b | 96 + appl/cmd/sh/tk.b | 426 ++++ appl/cmd/sha1sum.b | 65 + appl/cmd/shutdown.b | 72 + appl/cmd/sleep.b | 46 + appl/cmd/sort.b | 129 + appl/cmd/spki/mkfile | 22 + appl/cmd/spki/verify.b | 107 + appl/cmd/src.b | 28 + appl/cmd/stack.b | 184 ++ appl/cmd/stackv.b | 445 ++++ appl/cmd/stream.b | 98 + appl/cmd/strings.b | 87 + appl/cmd/styxchat.b | 557 +++++ appl/cmd/styxlisten.b | 262 ++ appl/cmd/styxmon.b | 110 + appl/cmd/sum.b | 59 + appl/cmd/tail.b | 379 +++ appl/cmd/tarfs.b | 411 ++++ appl/cmd/tclsh.b | 48 + appl/cmd/tcs.b | 184 ++ appl/cmd/tee.b | 79 + appl/cmd/telnet.b | 482 ++++ appl/cmd/test.b | 278 +++ appl/cmd/time.b | 97 + appl/cmd/timestamp.b | 42 + appl/cmd/tkcmd.b | 190 ++ appl/cmd/tokenize.b | 33 + appl/cmd/touch.b | 77 + appl/cmd/touchcal.b | 278 +++ appl/cmd/tr.b | 319 +++ appl/cmd/tsort.b | 133 + appl/cmd/unicode.b | 162 ++ appl/cmd/uniq.b | 79 + appl/cmd/units.b | 1061 ++++++++ appl/cmd/units.y | 771 ++++++ appl/cmd/unmount.b | 44 + appl/cmd/usb/mkfile | 11 + appl/cmd/usb/usbd.b | 835 +++++++ appl/cmd/uudecode.b | 132 + appl/cmd/uuencode.b | 101 + appl/cmd/wav2iaf.b | 171 ++ appl/cmd/wc.b | 303 +++ appl/cmd/webgrab.b | 532 ++++ appl/cmd/wish.b | 191 ++ appl/cmd/wmexport.b | 557 +++++ appl/cmd/wmimport.b | 64 + appl/cmd/xargs.b | 86 + appl/cmd/xd.b | 316 +++ appl/cmd/xmount.b | 231 ++ appl/cmd/yacc.b | 2810 +++++++++++++++++++++ appl/cmd/zeros.b | 68 + 401 files changed, 142101 insertions(+) create mode 100644 appl/cmd/9660srv.b create mode 100644 appl/cmd/9export.b create mode 100644 appl/cmd/9srvfs.b create mode 100644 appl/cmd/9win.b create mode 100644 appl/cmd/B.b create mode 100644 appl/cmd/archfs.b create mode 100644 appl/cmd/auplay.b create mode 100644 appl/cmd/auth/aescbc.b create mode 100644 appl/cmd/auth/changelogin.b create mode 100644 appl/cmd/auth/convpasswd.b create mode 100644 appl/cmd/auth/countersigner.b create mode 100644 appl/cmd/auth/createsignerkey.b create mode 100644 appl/cmd/auth/factotum/authio.m create mode 100644 appl/cmd/auth/factotum/factotum.b create mode 100644 appl/cmd/auth/factotum/feedkey.b create mode 100644 appl/cmd/auth/factotum/mkfile create mode 100644 appl/cmd/auth/factotum/proto/infauth.b create mode 100644 appl/cmd/auth/factotum/proto/keyreps.b create mode 100644 appl/cmd/auth/factotum/proto/keyreps.m create mode 100644 appl/cmd/auth/factotum/proto/mkfile create mode 100644 appl/cmd/auth/factotum/proto/p9any.b create mode 100644 appl/cmd/auth/factotum/proto/pass.b create mode 100644 appl/cmd/auth/factotum/rpc.b create mode 100644 appl/cmd/auth/getpk.b create mode 100644 appl/cmd/auth/keyfs.b create mode 100644 appl/cmd/auth/keysrv.b create mode 100644 appl/cmd/auth/logind.b create mode 100644 appl/cmd/auth/mkauthinfo.b create mode 100644 appl/cmd/auth/mkfile create mode 100644 appl/cmd/auth/passwd.b create mode 100644 appl/cmd/auth/secstore.b create mode 100644 appl/cmd/auth/signer.b create mode 100644 appl/cmd/auth/verify.b create mode 100644 appl/cmd/auxi/cpuslave.b create mode 100644 appl/cmd/auxi/digest.b create mode 100644 appl/cmd/auxi/fpgaload.b create mode 100644 appl/cmd/auxi/mangaload.b create mode 100644 appl/cmd/auxi/mkfile create mode 100644 appl/cmd/auxi/pcmcia.b create mode 100644 appl/cmd/auxi/rdbgsrv.b create mode 100644 appl/cmd/auxi/rstyxd.b create mode 100644 appl/cmd/avr/burn.b create mode 100644 appl/cmd/avr/mkfile create mode 100644 appl/cmd/basename.b create mode 100644 appl/cmd/bind.b create mode 100644 appl/cmd/bit2gif.b create mode 100644 appl/cmd/broke.b create mode 100644 appl/cmd/bytes.b create mode 100644 appl/cmd/cal.b create mode 100644 appl/cmd/cat.b create mode 100644 appl/cmd/cd.b create mode 100644 appl/cmd/chgrp.b create mode 100644 appl/cmd/chmod.b create mode 100644 appl/cmd/cleanname.b create mode 100644 appl/cmd/cmp.b create mode 100755 appl/cmd/comm.b create mode 100644 appl/cmd/cook.b create mode 100644 appl/cmd/cp.b create mode 100644 appl/cmd/cprof.b create mode 100644 appl/cmd/cpu.b create mode 100644 appl/cmd/crypt.b create mode 100644 appl/cmd/date.b create mode 100644 appl/cmd/dbfs.b create mode 100755 appl/cmd/dbm/delete.b create mode 100755 appl/cmd/dbm/fetch.b create mode 100755 appl/cmd/dbm/keys.b create mode 100755 appl/cmd/dbm/list.b create mode 100644 appl/cmd/dbm/mkfile create mode 100755 appl/cmd/dbm/store.b create mode 100644 appl/cmd/dd.b create mode 100644 appl/cmd/dial.b create mode 100644 appl/cmd/diff.b create mode 100644 appl/cmd/disdep.b create mode 100644 appl/cmd/disdump.b create mode 100644 appl/cmd/disk/format.b create mode 100644 appl/cmd/disk/ftl.b create mode 100644 appl/cmd/disk/kfs.b create mode 100644 appl/cmd/disk/kfscmd.b create mode 100644 appl/cmd/disk/mbr.b create mode 100644 appl/cmd/disk/mkext.b create mode 100644 appl/cmd/disk/mkfile create mode 100644 appl/cmd/disk/mkfs.b create mode 100644 appl/cmd/disk/prep/calc.tab.b create mode 100644 appl/cmd/disk/prep/calc.tab.m create mode 100644 appl/cmd/disk/prep/calc.y create mode 100644 appl/cmd/disk/prep/fdisk.b create mode 100644 appl/cmd/disk/prep/mkfile create mode 100644 appl/cmd/disk/prep/pedit.b create mode 100644 appl/cmd/disk/prep/pedit.m create mode 100644 appl/cmd/disk/prep/prep.b create mode 100644 appl/cmd/dossrv.b create mode 100644 appl/cmd/du.b create mode 100644 appl/cmd/echo.b create mode 100644 appl/cmd/ed.b create mode 100644 appl/cmd/emuinit.b create mode 100644 appl/cmd/env.b create mode 100644 appl/cmd/export.b create mode 100644 appl/cmd/fc.b create mode 100644 appl/cmd/fcp.b create mode 100755 appl/cmd/fmt.b create mode 100644 appl/cmd/fone.b create mode 100755 appl/cmd/fortune.b create mode 100755 appl/cmd/freq.b create mode 100644 appl/cmd/fs.b create mode 100644 appl/cmd/fs/and.b create mode 100644 appl/cmd/fs/bundle.b create mode 100644 appl/cmd/fs/chstat.b create mode 100644 appl/cmd/fs/compose.b create mode 100644 appl/cmd/fs/depth.b create mode 100644 appl/cmd/fs/entries.b create mode 100644 appl/cmd/fs/eval.b create mode 100644 appl/cmd/fs/exec.b create mode 100644 appl/cmd/fs/filter.b create mode 100644 appl/cmd/fs/ls.b create mode 100644 appl/cmd/fs/match.b create mode 100644 appl/cmd/fs/merge.b create mode 100644 appl/cmd/fs/mergewrite.b create mode 100644 appl/cmd/fs/mkfile create mode 100644 appl/cmd/fs/mode.b create mode 100644 appl/cmd/fs/not.b create mode 100644 appl/cmd/fs/or.b create mode 100644 appl/cmd/fs/path.b create mode 100644 appl/cmd/fs/pipe.b create mode 100644 appl/cmd/fs/print.b create mode 100644 appl/cmd/fs/proto.b create mode 100644 appl/cmd/fs/query.b create mode 100644 appl/cmd/fs/readfile.b create mode 100644 appl/cmd/fs/run.b create mode 100644 appl/cmd/fs/select.b create mode 100644 appl/cmd/fs/setroot.b create mode 100644 appl/cmd/fs/size.b create mode 100644 appl/cmd/fs/template.b create mode 100644 appl/cmd/fs/unbundle.b create mode 100644 appl/cmd/fs/void.b create mode 100644 appl/cmd/fs/walk.b create mode 100644 appl/cmd/fs/write.b create mode 100644 appl/cmd/ftest.b create mode 100644 appl/cmd/ftpfs.b create mode 100644 appl/cmd/getauthinfo.b create mode 100644 appl/cmd/getfile.b create mode 100644 appl/cmd/gettar.b create mode 100644 appl/cmd/gif2bit.b create mode 100644 appl/cmd/grep.b create mode 100644 appl/cmd/gunzip.b create mode 100644 appl/cmd/gzip.b create mode 100644 appl/cmd/idea.b create mode 100644 appl/cmd/import.b create mode 100644 appl/cmd/install/NOTICE create mode 100644 appl/cmd/install/applylog.b create mode 100644 appl/cmd/install/arch.b create mode 100644 appl/cmd/install/arch.m create mode 100644 appl/cmd/install/archfs.b create mode 100644 appl/cmd/install/archfs.m create mode 100644 appl/cmd/install/ckproto.b create mode 100644 appl/cmd/install/create.b create mode 100644 appl/cmd/install/eproto.b create mode 100644 appl/cmd/install/info.b create mode 100644 appl/cmd/install/inst.b create mode 100644 appl/cmd/install/install.b create mode 100644 appl/cmd/install/log.b create mode 100644 appl/cmd/install/logs.b create mode 100644 appl/cmd/install/logs.m create mode 100644 appl/cmd/install/mergelog.b create mode 100644 appl/cmd/install/mkfile create mode 100644 appl/cmd/install/mkproto.b create mode 100644 appl/cmd/install/proto.b create mode 100644 appl/cmd/install/proto.m create mode 100644 appl/cmd/install/proto2list.b create mode 100644 appl/cmd/install/protocaller.m create mode 100644 appl/cmd/install/updatelog.b create mode 100644 appl/cmd/install/wdiff.b create mode 100644 appl/cmd/install/wfind.b create mode 100644 appl/cmd/install/wrap.b create mode 100644 appl/cmd/install/wrap.m create mode 100644 appl/cmd/install/wrap2list.b create mode 100644 appl/cmd/iostats.b create mode 100644 appl/cmd/ip/bootpd.b create mode 100644 appl/cmd/ip/dhcp.b create mode 100644 appl/cmd/ip/mkfile create mode 100644 appl/cmd/ip/nppp/mkfile create mode 100644 appl/cmd/ip/nppp/modem.b create mode 100644 appl/cmd/ip/nppp/modem.m create mode 100644 appl/cmd/ip/nppp/pppchat.b create mode 100644 appl/cmd/ip/nppp/ppplink.b create mode 100644 appl/cmd/ip/nppp/ppptest.b create mode 100644 appl/cmd/ip/nppp/script.b create mode 100644 appl/cmd/ip/nppp/script.m create mode 100644 appl/cmd/ip/obootpd.b create mode 100644 appl/cmd/ip/ping.b create mode 100644 appl/cmd/ip/ppp/mkfile create mode 100644 appl/cmd/ip/ppp/modem.b create mode 100644 appl/cmd/ip/ppp/modem.m create mode 100644 appl/cmd/ip/ppp/pppclient.b create mode 100644 appl/cmd/ip/ppp/pppclient.m create mode 100644 appl/cmd/ip/ppp/pppdial.b create mode 100644 appl/cmd/ip/ppp/pppgui.b create mode 100644 appl/cmd/ip/ppp/pppgui.m create mode 100644 appl/cmd/ip/ppp/ppptest.b create mode 100644 appl/cmd/ip/ppp/script.b create mode 100644 appl/cmd/ip/ppp/script.m create mode 100644 appl/cmd/ip/rip.b create mode 100644 appl/cmd/ip/sntp.b create mode 100644 appl/cmd/ip/tftpd.b create mode 100644 appl/cmd/ip/virgild.b create mode 100644 appl/cmd/irtest.b create mode 100644 appl/cmd/itest.b create mode 100644 appl/cmd/itreplay.b create mode 100644 appl/cmd/kill.b create mode 100644 appl/cmd/lc.b create mode 100644 appl/cmd/lego/clock.b create mode 100644 appl/cmd/lego/clockface.b create mode 100644 appl/cmd/lego/firmdl.b create mode 100644 appl/cmd/lego/link.b create mode 100644 appl/cmd/lego/mkfile create mode 100644 appl/cmd/lego/rcxsend.b create mode 100644 appl/cmd/lego/rcxsend.m create mode 100644 appl/cmd/lego/send.b create mode 100644 appl/cmd/lego/timers.b create mode 100644 appl/cmd/lego/timers.m create mode 100644 appl/cmd/limbo/arg.m create mode 100644 appl/cmd/limbo/asm.b create mode 100644 appl/cmd/limbo/com.b create mode 100644 appl/cmd/limbo/decls.b create mode 100644 appl/cmd/limbo/dis.b create mode 100644 appl/cmd/limbo/disoptab.m create mode 100644 appl/cmd/limbo/ecom.b create mode 100644 appl/cmd/limbo/gen.b create mode 100644 appl/cmd/limbo/isa.m create mode 100644 appl/cmd/limbo/lex.b create mode 100644 appl/cmd/limbo/limbo.b create mode 100644 appl/cmd/limbo/limbo.m create mode 100644 appl/cmd/limbo/limbo.y create mode 100644 appl/cmd/limbo/mkfile create mode 100644 appl/cmd/limbo/nodes.b create mode 100644 appl/cmd/limbo/opname.m create mode 100644 appl/cmd/limbo/optim.b create mode 100644 appl/cmd/limbo/sbl.b create mode 100644 appl/cmd/limbo/stubs.b create mode 100644 appl/cmd/limbo/typecheck.b create mode 100644 appl/cmd/limbo/types.b create mode 100644 appl/cmd/listen.b create mode 100644 appl/cmd/lockfs.b create mode 100644 appl/cmd/logfile.b create mode 100755 appl/cmd/look.b create mode 100644 appl/cmd/lookman.b create mode 100644 appl/cmd/ls.b create mode 100644 appl/cmd/lstar.b create mode 100644 appl/cmd/man.b create mode 100644 appl/cmd/man2txt.b create mode 100644 appl/cmd/manufacture.b create mode 100644 appl/cmd/mash/builtins.b create mode 100644 appl/cmd/mash/depends.b create mode 100644 appl/cmd/mash/dump.b create mode 100644 appl/cmd/mash/exec.b create mode 100644 appl/cmd/mash/expr.b create mode 100644 appl/cmd/mash/eyacc.b create mode 100644 appl/cmd/mash/eyaccpar create mode 100644 appl/cmd/mash/history.b create mode 100644 appl/cmd/mash/lex.b create mode 100644 appl/cmd/mash/make.b create mode 100644 appl/cmd/mash/mash.b create mode 100644 appl/cmd/mash/mash.m create mode 100644 appl/cmd/mash/mash.y create mode 100644 appl/cmd/mash/mashfile create mode 100644 appl/cmd/mash/mashlib.b create mode 100644 appl/cmd/mash/mashparse.b create mode 100644 appl/cmd/mash/mashparse.m create mode 100644 appl/cmd/mash/misc.b create mode 100644 appl/cmd/mash/mkfile create mode 100644 appl/cmd/mash/serve.b create mode 100644 appl/cmd/mash/symb.b create mode 100644 appl/cmd/mash/tk.b create mode 100644 appl/cmd/mash/xeq.b create mode 100644 appl/cmd/mathcalc.b create mode 100644 appl/cmd/mc.b create mode 100644 appl/cmd/md5sum.b create mode 100644 appl/cmd/mdb.b create mode 100644 appl/cmd/memfs.b create mode 100644 appl/cmd/metamorph.b create mode 100644 appl/cmd/mk/ar.m create mode 100644 appl/cmd/mk/mk.b create mode 100644 appl/cmd/mk/mkbinds create mode 100644 appl/cmd/mk/mkconfig create mode 100644 appl/cmd/mk/mkfile create mode 100644 appl/cmd/mk/mksubdirs create mode 100644 appl/cmd/mkdir.b create mode 100644 appl/cmd/mkfile create mode 100644 appl/cmd/mntgen.b create mode 100644 appl/cmd/mount.b create mode 100644 appl/cmd/mouse.b create mode 100644 appl/cmd/mpc/mkfile create mode 100644 appl/cmd/mpc/qconfig.b create mode 100644 appl/cmd/mpc/qflash.b create mode 100644 appl/cmd/mprof.b create mode 100644 appl/cmd/mv.b create mode 100644 appl/cmd/ndb/cs.b create mode 100644 appl/cmd/ndb/csquery.b create mode 100644 appl/cmd/ndb/dns.b create mode 100644 appl/cmd/ndb/dnsquery.b create mode 100644 appl/cmd/ndb/mkfile create mode 100644 appl/cmd/ndb/mkhash.b create mode 100644 appl/cmd/ndb/query.b create mode 100644 appl/cmd/ndb/registry.b create mode 100644 appl/cmd/ndb/regquery.b create mode 100644 appl/cmd/netkey.b create mode 100644 appl/cmd/netstat.b create mode 100644 appl/cmd/newer.b create mode 100644 appl/cmd/ns.b create mode 100644 appl/cmd/nsbuild.b create mode 100644 appl/cmd/os.b create mode 100644 appl/cmd/p.b create mode 100644 appl/cmd/palm/connex.b create mode 100644 appl/cmd/palm/desklink.b create mode 100644 appl/cmd/palm/desklink.m create mode 100644 appl/cmd/palm/mkfile create mode 100644 appl/cmd/palm/palmsrv.b create mode 100644 appl/cmd/pause.b create mode 100644 appl/cmd/plumb.b create mode 100644 appl/cmd/plumber.b create mode 100644 appl/cmd/prof.b create mode 100644 appl/cmd/promptstring.b create mode 100644 appl/cmd/ps.b create mode 100644 appl/cmd/puttar.b create mode 100644 appl/cmd/pwd.b create mode 100644 appl/cmd/ramfile.b create mode 100644 appl/cmd/randpass.b create mode 100644 appl/cmd/raw2iaf.b create mode 100644 appl/cmd/rawdbfs.b create mode 100644 appl/cmd/rcmd.b create mode 100644 appl/cmd/rdp.b create mode 100644 appl/cmd/read.b create mode 100644 appl/cmd/rioimport.b create mode 100644 appl/cmd/rm.b create mode 100644 appl/cmd/runas.b create mode 100644 appl/cmd/sed.b create mode 100644 appl/cmd/sendmail.b create mode 100644 appl/cmd/sh/arg.b create mode 100644 appl/cmd/sh/csv.b create mode 100644 appl/cmd/sh/doc/History create mode 100644 appl/cmd/sh/echo.b create mode 100644 appl/cmd/sh/expr.b create mode 100644 appl/cmd/sh/file2chan.b create mode 100644 appl/cmd/sh/mkfile create mode 100644 appl/cmd/sh/regex.b create mode 100644 appl/cmd/sh/sexprs.b create mode 100644 appl/cmd/sh/sh.b create mode 100644 appl/cmd/sh/sh.y create mode 100644 appl/cmd/sh/std.b create mode 100644 appl/cmd/sh/string.b create mode 100644 appl/cmd/sh/test.b create mode 100644 appl/cmd/sh/tk.b create mode 100644 appl/cmd/sha1sum.b create mode 100644 appl/cmd/shutdown.b create mode 100644 appl/cmd/sleep.b create mode 100644 appl/cmd/sort.b create mode 100644 appl/cmd/spki/mkfile create mode 100644 appl/cmd/spki/verify.b create mode 100644 appl/cmd/src.b create mode 100644 appl/cmd/stack.b create mode 100644 appl/cmd/stackv.b create mode 100644 appl/cmd/stream.b create mode 100644 appl/cmd/strings.b create mode 100644 appl/cmd/styxchat.b create mode 100644 appl/cmd/styxlisten.b create mode 100644 appl/cmd/styxmon.b create mode 100644 appl/cmd/sum.b create mode 100644 appl/cmd/tail.b create mode 100644 appl/cmd/tarfs.b create mode 100644 appl/cmd/tclsh.b create mode 100644 appl/cmd/tcs.b create mode 100644 appl/cmd/tee.b create mode 100644 appl/cmd/telnet.b create mode 100644 appl/cmd/test.b create mode 100644 appl/cmd/time.b create mode 100644 appl/cmd/timestamp.b create mode 100644 appl/cmd/tkcmd.b create mode 100644 appl/cmd/tokenize.b create mode 100644 appl/cmd/touch.b create mode 100644 appl/cmd/touchcal.b create mode 100644 appl/cmd/tr.b create mode 100644 appl/cmd/tsort.b create mode 100644 appl/cmd/unicode.b create mode 100644 appl/cmd/uniq.b create mode 100644 appl/cmd/units.b create mode 100644 appl/cmd/units.y create mode 100644 appl/cmd/unmount.b create mode 100644 appl/cmd/usb/mkfile create mode 100644 appl/cmd/usb/usbd.b create mode 100644 appl/cmd/uudecode.b create mode 100644 appl/cmd/uuencode.b create mode 100644 appl/cmd/wav2iaf.b create mode 100644 appl/cmd/wc.b create mode 100644 appl/cmd/webgrab.b create mode 100644 appl/cmd/wish.b create mode 100644 appl/cmd/wmexport.b create mode 100644 appl/cmd/wmimport.b create mode 100644 appl/cmd/xargs.b create mode 100644 appl/cmd/xd.b create mode 100644 appl/cmd/xmount.b create mode 100644 appl/cmd/yacc.b create mode 100644 appl/cmd/zeros.b (limited to 'appl/cmd') diff --git a/appl/cmd/9660srv.b b/appl/cmd/9660srv.b new file mode 100644 index 00000000..17fa053b --- /dev/null +++ b/appl/cmd/9660srv.b @@ -0,0 +1,1504 @@ +implement ISO9660; + +include "sys.m"; + sys: Sys; + Dir, Qid, QTDIR, QTFILE, DMDIR: import sys; + +include "draw.m"; + +include "daytime.m"; + daytime: Daytime; + +include "string.m"; + str: String; + +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; + +include "arg.m"; + +ISO9660: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Sectorsize: con 2048; +Maxname: con 256; + +Enonexist: con "file does not exist"; +Eperm: con "permission denied"; +Enofile: con "no file system specified"; +Eauth: con "authentication failed"; +Ebadfid: con "invalid fid"; +Efidinuse: con "fid already in use"; +Enotdir: con "not a directory"; +Esyntax: con "file name syntax"; + +devname: string; + +chatty := 0; +showstyx := 0; +progname := "9660srv"; +stderr: ref Sys->FD; +noplan9 := 0; +nojoliet := 0; +norock := 0; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: %s [-rabc] [-9JR] [-s] cd_device dir\n", progname); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + stderr = sys->fildes(2); + + if(args != nil) + progname = hd args; + styx = load Styx Styx->PATH; + if(styx == nil) + noload(Styx->PATH); + styx->init(); + + if(args != nil) + progname = hd args; + mountopt := Sys->MREPL; + copt := 0; + stdio := 0; + + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'v' or 'D' => chatty = 1; showstyx = 1; + 'r' => mountopt = Sys->MREPL; + 'a' => mountopt = Sys->MAFTER; + 'b' => mountopt = Sys->MBEFORE; + 'c' => copt = Sys->MCREATE; + 's' => stdio = 1; + '9' => noplan9 = 1; + 'J' => nojoliet = 1; + 'R' => norock = 1; + * => usage(); + } + args = arg->argv(); + arg = nil; + + if(args == nil || tl args == nil) + usage(); + what := hd args; + mountpt := hd tl args; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + noload(Daytime->PATH); + + iobufinit(Sectorsize); + + pip := array[2] of ref Sys->FD; + if(stdio){ + pip[0] = sys->fildes(0); + pip[1] = sys->fildes(1); + }else + if(sys->pipe(pip) < 0) + error(sys->sprint("can't create pipe: %r")); + + devname = what; + + sync := chan of int; + spawn fileserve(pip[1], sync); + <-sync; + + if(sys->mount(pip[0], nil, mountpt, mountopt|copt, nil) < 0) { + sys->fprint(sys->fildes(2), "%s: mount %s %s failed: %r\n", progname, what, mountpt); + exit; + } +} + +noload(s: string) +{ + sys->fprint(sys->fildes(2), "%s: can't load %s: %r\n", progname, s); + raise "fail:load"; +} + +error(p: string) +{ + sys->fprint(sys->fildes(2), "9660srv: %s\n", p); + raise "fail:error"; +} + +fileserve(rfd: ref Sys->FD, sync: chan of int) +{ + sys->pctl(Sys->NEWFD|Sys->FORKNS, list of {2, rfd.fd}); + rfd = sys->fildes(rfd.fd); + stderr = sys->fildes(2); + sync <-= 1; + while((m := Tmsg.read(rfd, 0)) != nil){ + if(showstyx) + chat(sys->sprint("%s...", m.text())); + r: ref Rmsg; + pick t := m { + Readerror => + error(sys->sprint("mount read error: %s", t.error)); + Version => + r = rversion(t); + Auth => + r = rauth(t); + Flush => + r = rflush(t); + Attach => + r = rattach(t); + Walk => + r = rwalk(t); + Open => + r = ropen(t); + Create => + r = rcreate(t); + Read => + r = rread(t); + Write => + r = rwrite(t); + Clunk => + r = rclunk(t); + Remove => + r = rremove(t); + Stat => + r = rstat(t); + Wstat => + r = rwstat(t); + * => + error(sys->sprint("invalid T-message tag: %d", tagof m)); + } + pick e := r { + Error => + r.tag = m.tag; + } + rbuf := r.pack(); + if(rbuf == nil) + error("bad R-message conversion"); + if(showstyx) + chat(r.text()+"\n"); + if(styx->write(rfd, rbuf, len rbuf) != len rbuf) + error(sys->sprint("connection write error: %r")); + } + + if(chatty) + chat("server end of file\n"); +} + +E(s: string): ref Rmsg.Error +{ + return ref Rmsg.Error(0, s); +} + +rversion(t: ref Tmsg.Version): ref Rmsg +{ + (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); + return ref Rmsg.Version(t.tag, msize, version); +} + +rauth(t: ref Tmsg.Auth): ref Rmsg +{ + return ref Rmsg.Error(t.tag, "authentication not required"); +} + +rflush(t: ref Tmsg.Flush): ref Rmsg +{ + return ref Rmsg.Flush(t.tag); +} + +rattach(t: ref Tmsg.Attach): ref Rmsg +{ + dname := devname; + if(t.aname != "") + dname = t.aname; + (dev, err) := devattach(dname, Sys->OREAD, Sectorsize); + if(dev == nil) + return E(err); + + xf := Xfs.new(dev); + root := cleanfid(t.fid); + root.qid = Sys->Qid(big 0, 0, Sys->QTDIR); + root.xf = xf; + err = root.attach(); + if(err != nil){ + clunkfid(t.fid); + return E(err); + } + xf.rootqid = root.qid; + return ref Rmsg.Attach(t.tag, root.qid); +} + +walk1(f: ref Xfile, name: string): string +{ + if(!(f.qid.qtype & Sys->QTDIR)) + return Enotdir; + case name { + "." => + return nil; # nop, but shouldn't happen + ".." => + if(f.qid.path==f.xf.rootqid.path) + return nil; + return f.walkup(); + * => + return f.walk(name); + } +} + +rwalk(t: ref Tmsg.Walk): ref Rmsg +{ + f:=findfid(t.fid); + if(f == nil) + return E(Ebadfid); + nf, sf: ref Xfile; + if(t.newfid != t.fid){ + nf = cleanfid(t.newfid); + if(nf == nil) + return E(Efidinuse); + f.clone(nf); + f = nf; + }else + sf = f.save(); + + qids: array of Sys->Qid; + if(len t.names > 0){ + qids = array[len t.names] of Sys->Qid; + for(i := 0; i < len t.names; i++){ + e := walk1(f, t.names[i]); + if(e != nil){ + if(nf != nil){ + nf.clunk(); + clunkfid(t.newfid); + }else + f.restore(sf); + if(i == 0) + return E(e); + return ref Rmsg.Walk(t.tag, qids[0:i]); + } + qids[i] = f.qid; + } + } + return ref Rmsg.Walk(t.tag, qids); +} + +ropen(t: ref Tmsg.Open): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + if(f.flags&Omodes) + return E("open on open file"); + e := f.open(t.mode); + if(e != nil) + return E(e); + f.flags = openflags(t.mode); + return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA); +} + +rcreate(t: ref Tmsg.Create): ref Rmsg +{ + name := t.name; + if(name == "." || name == "..") + return E(Esyntax); + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + if(f.flags&Omodes) + return E("create on open file"); + if(!(f.qid.qtype&Sys->QTDIR)) + return E("create in non-directory"); + e := f.create(name, t.perm, t.mode); + if(e != nil) + return E(e); + f.flags = openflags(t.mode); + return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA); +} + +rread(t: ref Tmsg.Read): ref Rmsg +{ + err: string; + + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + if (!(f.flags&Oread)) + return E("file not opened for reading"); + if(t.count < 0 || t.offset < big 0) + return E("negative offset or count"); + b := array[Styx->MAXFDATA] of byte; + count: int; + if(f.qid.qtype & Sys->QTDIR) + (count, err) = f.readdir(b, int t.offset, t.count); + else + (count, err) = f.read(b, int t.offset, t.count); + if(err != nil) + return E(err); + if(count != len b) + b = b[0:count]; + return ref Rmsg.Read(t.tag, b); +} + +rwrite(nil: ref Tmsg.Write): ref Rmsg +{ + return E(Eperm); +} + +rclunk(t: ref Tmsg.Clunk): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + f.clunk(); + clunkfid(t.fid); + return ref Rmsg.Clunk(t.tag); +} + +rremove(t: ref Tmsg.Remove): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + f.clunk(); + clunkfid(t.fid); + return E(Eperm); +} + +rstat(t: ref Tmsg.Stat): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + (dir, nil) := f.stat(); + return ref Rmsg.Stat(t.tag, *dir); +} + +rwstat(nil: ref Tmsg.Wstat): ref Rmsg +{ + return E(Eperm); +} + +openflags(mode: int): int +{ + flags := 0; + case mode & ~(Sys->OTRUNC|Sys->ORCLOSE) { + Sys->OREAD => + flags = Oread; + Sys->OWRITE => + flags = Owrite; + Sys->ORDWR => + flags = Oread|Owrite; + } + if(mode & Sys->ORCLOSE) + flags |= Orclose; + return flags; +} + +chat(s: string) +{ + if(chatty) + sys->fprint(stderr, "%s", s); +} + +Fid: adt { + fid: int; + file: ref Xfile; +}; + +FIDMOD: con 127; # prime +fids := array[FIDMOD] of list of ref Fid; + +hashfid(fid: int): (ref Fid, array of list of ref Fid) +{ + nl: list of ref Fid; + + hp := fids[fid%FIDMOD:]; + nl = nil; + for(l := hp[0]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + l = tl l; # excluding f + for(; nl != nil; nl = tl nl) + l = (hd nl) :: l; # put examined ones back, in order + hp[0] = l; + return (f, hp); + } else + nl = f :: nl; + } + return (nil, hp); +} + +findfid(fid: int): ref Xfile +{ + (f, hp) := hashfid(fid); + if(f == nil){ + chat("unassigned fid"); + return nil; + } + hp[0] = f :: hp[0]; + return f.file; +} + +cleanfid(fid: int): ref Xfile +{ + (f, hp) := hashfid(fid); + if(f != nil){ + chat("fid in use"); + return nil; + } + f = ref Fid; + f.fid = fid; + f.file = Xfile.new(); + hp[0] = f :: hp[0]; + return f.file.clean(); +} + +clunkfid(fid: int) +{ + (f, nil) := hashfid(fid); + if(f != nil) + f.file.clean(); +} + +# +# +# + +Xfs: adt { + d: ref Device; + inuse: int; + issusp: int; # system use sharing protocol in use? + suspoff: int; # LEN_SKP, if so + isplan9: int; # has Plan 9-specific directory info + isrock: int; # is rock ridge + rootqid: Sys->Qid; + ptr: int; # tag for private data + + new: fn(nil: ref Device): ref Xfs; + incref: fn(nil: self ref Xfs); + decref: fn(nil: self ref Xfs); +}; + +Xfile: adt { + xf: ref Xfs; + flags: int; + qid: Sys->Qid; + ptr: ref Isofile; # tag for private data + + new: fn(): ref Xfile; + clean: fn(nil: self ref Xfile): ref Xfile; + + save: fn(nil: self ref Xfile): ref Xfile; + restore: fn(nil: self ref Xfile, s: ref Xfile); + + attach: fn(nil: self ref Xfile): string; + clone: fn(nil: self ref Xfile, nil: ref Xfile); + walkup: fn(nil: self ref Xfile): string; + walk: fn(nil: self ref Xfile, nil: string): string; + open: fn(nil: self ref Xfile, nil: int): string; + create: fn(nil: self ref Xfile, nil: string, nil: int, nil: int): string; + readdir: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string); + read: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string); + write: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string); + clunk: fn(nil: self ref Xfile); + remove: fn(nil: self ref Xfile): string; + stat: fn(nil: self ref Xfile): (ref Sys->Dir, string); + wstat: fn(nil: self ref Xfile, nil: ref Sys->Dir): string; +}; + +Oread, Owrite, Orclose: con 1<sprint("iso, blksize=%d...", blksize)); + haveplan9 = eqs(v[8:8+6], "PLAN 9"); # v.z.boot.sysid + if(haveplan9){ + if(noplan9) { + chat("ignoring plan9"); + haveplan9 = 0; + }else{ + fmt = '9'; + chat("plan9 iso..."); + } + } + continue; + } + if(eqs(v[8:8+7], "\u0001CDROM\u0001")){ # high sierra + if(dirp != nil) + dirp.put(); + dirp = p; + fmt = 'r'; + convM2Drec(v[180:], dp, 1); # v.r.desc.rootdir + blksize = l16(v[136:]); # v.r.desc.blksize + if(chatty) + chat(sys->sprint("high sierra, blksize=%d...", blksize)); + continue; + } + if(haveplan9==0 && !nojoliet && eqs(v[0:7], "\u0002CD001\u0001")){ + q := v[88:]; # v.z.desc.escapes + if(q[0] == byte 16r25 && q[1] == byte 16r2F && + (q[2] == byte 16r40 || q[2] == byte 16r43 || q[2] == byte 16r45)){ # joliet, it appears + if(dirp != nil) + dirp.put(); + dirp = p; + fmt = 'J'; + convM2Drec(v[156:], dp, 0); # v.z.desc.rootdir + if(blksize != l16(v[128:])) # v.z.desc.blksize + sys->fprint(stderr, "9660srv: warning: suspicious Joliet block size: %d\n", l16(v[128:])); + chat("joliet..."); + continue; + } + }else{ + p.put(); + if(v[0] == byte 16rFF) + break; + } + } + + if(fmt == 0){ + if(dirp != nil) + dirp.put(); + return "CD format not recognised"; + } + + if(chatty) + showdrec(stderr, fmt, dp); + if(blksize > Sectorsize){ + dirp.put(); + return "blocksize too big"; + } + fp := iso(root); + root.xf.isplan9 = haveplan9; + fp.fmt = fmt; + fp.blksize = blksize; + fp.offset = 0; + fp.doffset = 0; + fp.d = dp; + root.qid.path = big dp.addr; + root.qid.qtype = QTDIR; + root.qid.vers = 0; + dirp.put(); + dp = ref Drec; + if(getdrec(root, dp) >= 0){ + s := dp.data; + n := len s; + if(n >= 7 && s[0] == byte 'S' && s[1] == byte 'P' && s[2] == byte 7 && + s[3] == byte 1 && s[4] == byte 16rBE && s[5] == byte 16rEF){ + root.xf.issusp = 1; + root.xf.suspoff = int s[6]; + n -= root.xf.suspoff; + s = s[root.xf.suspoff:]; + while(n >= 4){ + l := int s[2]; + if(s[0] == byte 'E' && s[1] == byte 'R'){ + if(int s[4] == 10 && eqs(s[8:18], "RRIP_1991A")) + root.xf.isrock = 1; + break; + } else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){ + (s, n) = getcontin(root.xf.d, s); + continue; + } else if(s[0] == byte 'R' && s[1] == byte 'R'){ + if(!norock) + root.xf.isrock = 1; + break; # can skip search for ER + } else if(s[0] == byte 'S' && s[1] == byte 'T') + break; + s = s[l:]; + n -= l; + } + } + } + if(root.xf.isrock) + chat("Rock Ridge..."); + fp.offset = 0; + fp.doffset = 0; + return nil; +} + +Xfile.clone(oldf: self ref Xfile, newf: ref Xfile) +{ + *newf = *oldf; + newf.ptr = nil; + newf.xf.incref(); + ip := iso(oldf); + np := iso(newf); + *np = *ip; # might not be right; shares ip.d +} + +Xfile.walkup(f: self ref Xfile): string +{ + pf := Xfile.new(); + ppf := Xfile.new(); + e := walkup(f, pf, ppf); + pf.clunk(); + ppf.clunk(); + return e; +} + +walkup(f, pf, ppf: ref Xfile): string +{ + e := opendotdot(f, pf); + if(e != nil) + return sys->sprint("can't open pf: %s", e); + paddr := iso(pf).d.addr; + if(iso(f).d.addr == paddr) + return nil; + e = opendotdot(pf, ppf); + if(e != nil) + return sys->sprint("can't open ppf: %s", e); + d := ref Drec; + while(getdrec(ppf, d) >= 0){ + if(d.addr == paddr){ + newdrec(f, d); + f.qid.path = big paddr; + f.qid.qtype = QTDIR; + f.qid.vers = 0; + return nil; + } + } + return "can't find addr of .."; +} + +Xfile.walk(f: self ref Xfile, name: string): string +{ + ip := iso(f); + if(!f.xf.isplan9){ + for(i := 0; i < len name; i++) + if(name[i] == ';') + break; + if(i >= Maxname) + i = Maxname-1; + name = name[0:i]; + } + if(chatty) + chat(sys->sprint("%d \"%s\"...", len name, name)); + ip.offset = 0; + dir := ref Dir; + d := ref Drec; + while(getdrec(f, d) >= 0) { + dvers := rzdir(f.xf, dir, ip.fmt, d); + if(name != dir.name) + continue; + newdrec(f, d); + f.qid.path = dir.qid.path; + f.qid.qtype = dir.qid.qtype; + f.qid.vers = dir.qid.vers; + if(dvers){ + # versions ignored + } + return nil; + } + return Enonexist; +} + +Xfile.open(f: self ref Xfile, mode: int): string +{ + if(mode != Sys->OREAD) + return Eperm; + ip := iso(f); + ip.offset = 0; + ip.doffset = 0; + return nil; +} + +Xfile.create(nil: self ref Xfile, nil: string, nil: int, nil: int): string +{ + return Eperm; +} + +Xfile.readdir(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string) +{ + ip := iso(f); + d := ref Dir; + drec := ref Drec; + if(offset < ip.doffset){ + ip.offset = 0; + ip.doffset = 0; + } + rcnt := 0; + while(rcnt < count && getdrec(f, drec) >= 0){ + if(len drec.name == 1){ + if(drec.name[0] == byte 0) + continue; + if(drec.name[0] == byte 1) + continue; + } + rzdir(f.xf, d, ip.fmt, drec); + d.qid.vers = f.qid.vers; + a := styx->packdir(*d); + if(ip.doffset < offset){ + ip.doffset += len a; + continue; + } + if(rcnt+len a > count) + break; + buf[rcnt:] = a; # BOTCH: copy + rcnt += len a; + } + ip.doffset += rcnt; + return (rcnt, nil); +} + +Xfile.read(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string) +{ + ip := iso(f); + if(offset >= ip.d.size) + return (0, nil); + if(offset+count > ip.d.size) + count = ip.d.size - offset; + addr := (ip.d.addr+ip.d.attrlen)*ip.blksize + offset; + o := addr % Sectorsize; + addr /= Sectorsize; + if(chatty) + chat(sys->sprint("d.addr=0x%x, addr=0x%x, o=0x%x...", ip.d.addr, addr, o)); + n := Sectorsize - o; + rcnt := 0; + while(count > 0){ + if(n > count) + n = count; + p := Block.get(f.xf.d, addr); + if(p == nil) + return (-1, "i/o error"); + buf[rcnt:] = p.data[o:o+n]; + p.put(); + count -= n; + rcnt += n; + addr++; + o = 0; + n = Sectorsize; + } + return (rcnt, nil); +} + +Xfile.write(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string) +{ + return (-1, Eperm); +} + +Xfile.clunk(f: self ref Xfile) +{ + f.ptr = nil; +} + +Xfile.remove(nil: self ref Xfile): string +{ + return Eperm; +} + +Xfile.stat(f: self ref Xfile): (ref Dir, string) +{ + ip := iso(f); + d := ref Dir; + rzdir(f.xf, d, ip.fmt, ip.d); + d.qid.vers = f.qid.vers; + if(d.qid.path==f.xf.rootqid.path){ + d.qid.path = big 0; + d.qid.qtype = QTDIR; + } + return (d, nil); +} + +Xfile.wstat(nil: self ref Xfile, nil: ref Dir): string +{ + return Eperm; +} + +Xfs.new(d: ref Device): ref Xfs +{ + xf := ref Xfs; + xf.inuse = 1; + xf.d = d; + xf.isplan9 = 0; + xf.issusp = 0; + xf.isrock = 0; + xf.suspoff = 0; + xf.ptr = 0; + xf.rootqid = Qid(big 0, 0, QTDIR); + return xf; +} + +Xfs.incref(xf: self ref Xfs) +{ + xf.inuse++; +} + +Xfs.decref(xf: self ref Xfs) +{ + xf.inuse--; + if(xf.inuse == 0){ + if(xf.d != nil) + xf.d.detach(); + } +} + +showdrec(fd: ref Sys->FD, fmt: int, d: ref Drec) +{ + if(d.reclen == 0) + return; + sys->fprint(fd, "%d %d %d %d ", + d.reclen, d.attrlen, d.addr, d.size); + sys->fprint(fd, "%s 0x%2.2x %d %d %d ", + rdate(d.date, fmt), d.flags, + d.unitsize, d.gapsize, d.vseqno); + sys->fprint(fd, "%d %s", len d.name, nstr(d.name)); + syslen := len d.data; + if(syslen != 0) + sys->fprint(fd, " %s", nstr(d.data)); + sys->fprint(fd, "\n"); +} + +newdrec(f: ref Xfile, dp: ref Drec) +{ + x := iso(f); + n := ref Isofile; + n.fmt = x.fmt; + n.blksize = x.blksize; + n.offset = 0; + n.doffset = 0; + n.d = dp; + f.ptr = n; +} + +getdrec(f: ref Xfile, d: ref Drec): int +{ + if(f.ptr == nil) + return -1; + boff := 0; + ip := iso(f); + size := ip.d.size; + while(ip.offset Sectorsize-34){ + ip.offset += Sectorsize-boff; + continue; + } + p := Block.get(f.xf.d, addr/Sectorsize); + if(p == nil) + return -1; + nb := int p.data[boff]; + if(nb >= 34) { + convM2Drec(p.data[boff:], d, ip.fmt=='r'); + #chat(sys->sprint("off %d", ip.offset)); + #showdrec(stderr, ip.fmt, d); + p.put(); + ip.offset += nb + (nb&1); + return 0; + } + p.put(); + p = nil; + ip.offset += Sectorsize-boff; + } + return -1; +} + +# getcontin returns a slice of the Iobuf, valid until next i/o call +getcontin(d: ref Device, a: array of byte): (array of byte, int) +{ + bn := l32(a[4:]); + off := l32(a[12:]); + n := l32(a[20:]); + p := Block.get(d, bn); + if(p == nil) + return (nil, 0); + return (p.data[off:off+n], n); +} + +iso(f: ref Xfile): ref Isofile +{ + if(f.ptr == nil){ + f.ptr = ref Isofile; + f.ptr.d = ref Drec; + } + return f.ptr; +} + +opendotdot(f: ref Xfile, pf: ref Xfile): string +{ + d := ref Drec; + ip := iso(f); + ip.offset = 0; + if(getdrec(f, d) < 0) + return "opendotdot: getdrec(.) failed"; + if(len d.name != 1 || d.name[0] != byte 0) + return "opendotdot: no . entry"; + if(d.addr != ip.d.addr) + return "opendotdot: bad . address"; + if(getdrec(f, d) < 0) + return "opendotdot: getdrec(..) failed"; + if(len d.name != 1 || d.name[0] != byte 1) + return "opendotdot: no .. entry"; + + pf.xf = f.xf; + pip := iso(pf); + pip.fmt = ip.fmt; + pip.blksize = ip.blksize; + pip.offset = 0; + pip.doffset = 0; + pip.d = d; + return nil; +} + +rzdir(fs: ref Xfs, d: ref Dir, fmt: int, dp: ref Drec): int +{ + Hmode, Hname: con 1< d.name = "."; have |= Hname; + 1 => d.name = ".."; have |= Hname; + * => d.name = ""; d.name[0] = tolower(int dp.name[0]); + } + } else { + if(fmt == 'J'){ # Joliet, 16-bit Unicode + d.name = ""; + for(i:=0; i= Maxname) + n = Maxname-1; + d.name = ""; + for(i:=0; i34+len dp.name) { + # + # get gid, uid, mode and possibly name + # from plan9 directory extension + # + s := dp.data; + n = int s[0]; + if(n) + d.name = string s[1:1+n]; + l := 1+n; + n = int s[l++]; + d.uid = string s[l:l+n]; + l += n; + n = int s[l++]; + d.gid = string s[l:l+n]; + l += n; + if(l & 1) + l++; + d.mode = l32(s[l:]); + if(d.mode & DMDIR) + d.qid.qtype = QTDIR; + } else { + d.mode = 8r444; + case fmt { + 'z' => + if(fs.isrock) + d.gid = "ridge"; + else + d.gid = "iso"; + 'r' => + d.gid = "sierra"; + 'J' => + d.gid = "joliet"; + * => + d.gid = "???"; + } + flags := dp.flags; + if(flags & 2){ + d.qid.qtype = QTDIR; + d.mode |= DMDIR|8r111; + } + d.uid = "cdrom"; + for(i := 0; i < len d.name; i++) + if(d.name[i] == ';') { + vers = int string d.name[i+1:]; # inefficient + d.name = d.name[0:i]; # inefficient + break; + } + n = len dp.data - fs.suspoff; + if(fs.isrock && n >= 4){ + s := dp.data[fs.suspoff:]; + nm := 0; + while(n >= 4 && have != (Hname|Hmode)){ + l := int s[2]; + if(s[0] == byte 'P' && s[1] == byte 'X' && s[3] == byte 1){ + # posix file attributes + mode := l32(s[4:12]); + d.mode = mode & 8r777; + if((mode & 8r170000) == 8r0040000){ + d.mode |= DMDIR; + d.qid.qtype = QTDIR; + } + have |= Hmode; + } else if(s[0] == byte 'N' && s[1] == byte 'M' && s[3] == byte 1){ + # alternative name + flags = int s[4]; + if((flags & ~1) == 0){ + if(nm == 0){ + d.name = string s[5:l]; + nm = 1; + } else + d.name += string s[5:l]; + if(flags == 0) + have |= Hname; # no more + } + } else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){ + (s, n) = getcontin(fs.d, s); + continue; + } else if(s[0] == byte 'S' && s[1] == byte 'T') + break; + n -= l; + s = s[l:]; + } + } + } + d.length = big 0; + if((d.mode & DMDIR) == 0) + d.length = big dp.size; + d.dtype = 0; + d.dev = 0; + d.atime = dp.time; + d.mtime = d.atime; + return vers; +} + +convM2Drec(a: array of byte, d: ref Drec, highsierra: int) +{ + d.reclen = int a[0]; + d.attrlen = int a[1]; + d.addr = int l32(a[2:10]); + d.size = int l32(a[10:18]); + d.time = gtime(a[18:24]); + d.date = array[7] of byte; + d.date[0:] = a[18:25]; + if(highsierra){ + d.tzone = 0; + d.flags = int a[24]; + d.unitsize = 0; + d.gapsize = 0; + d.vseqno = 0; + } else { + d.tzone = int a[24]; + d.flags = int a[25]; + d.unitsize = int a[26]; + d.gapsize = int a[27]; + d.vseqno = l32(a[28:32]); + } + n := int a[32]; + d.name = array[n] of byte; + d.name[0:] = a[33:33+n]; + n += 33; + if(n & 1) + n++; # check this + syslen := d.reclen - n; + if(syslen > 0){ + d.data = array[syslen] of byte; + d.data[0:] = a[n:n+syslen]; + } else + d.data = nil; +} + +nstr(p: array of byte): string +{ + q := ""; + n := len p; + for(i := 0; i < n; i++){ + if(int p[i] == '\\') + q[len q] = '\\'; + if(' ' <= int p[i] && int p[i] <= '~') + q[len q] = int p[i]; + else + q += sys->sprint("\\%2.2ux", int p[i]); + } + return q; +} + +rdate(p: array of byte, fmt: int): string +{ + c: int; + + s := sys->sprint("%2.2d.%2.2d.%2.2d %2.2d:%2.2d:%2.2d", + int p[0], int p[1], int p[2], int p[3], int p[4], int p[5]); + if(fmt == 'z'){ + htz := int p[6]; + if(htz >= 128){ + htz = 256-htz; + c = '-'; + }else + c = '+'; + s += sys->sprint(" (%c%.1f)", c, real htz/2.0); + } + return s; +} + +dmsize := array[] of { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, +}; + +dysize(y: int): int +{ + if((y%4) == 0) + return 366; + return 365; +} + +gtime(p: array of byte): int # yMdhms +{ + y:=int p[0]; M:=int p[1]; d:=int p[2]; + h:=int p[3]; m:=int p[4]; s:=int p[5];; + if(y < 70) + return 0; + if(M < 1 || M > 12) + return 0; + if(d < 1 || d > dmsize[M-1]) + return 0; + if(h > 23) + return 0; + if(m > 59) + return 0; + if(s > 59) + return 0; + y += 1900; + t := 0; + for(i:=1970; i= 3) + t++; + M--; + while(M-- > 0) + t += dmsize[M]; + t += d-1; + t = 24*t + h; + t = 60*t + m; + t = 60*t + s; + return t; +} + +l16(p: array of byte): int +{ + v := (int p[1]<<8)| int p[0]; + if (v >= 16r8000) + v -= 16r10000; + return v; +} + +l32(p: array of byte): int +{ + return (((((int p[3]<<8)| int p[2])<<8)| int p[1])<<8)| int p[0]; +} + +eqs(a: array of byte, b: string): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(int a[i] != b[i]) + return 0; + return 1; +} + +tolower(c: int): int +{ + if(c >= 'A' && c <= 'Z') + return c-'A' + 'a'; + return c; +} + +# +# I/O buffers +# + +Device: adt { + inuse: int; # attach count + name: string; # of underlying file + fd: ref Sys->FD; + sectorsize: int; + qid: Sys->Qid; # (qid,dtype,dev) identify uniquely + dtype: int; + dev: int; + + detach: fn(nil: self ref Device); +}; + +Block: adt { + dev: ref Device; + addr: int; + data: array of byte; + + # internal + next: cyclic ref Block; + prev: cyclic ref Block; + busy: int; + + get: fn(nil: ref Device, addr: int): ref Block; + put: fn(nil: self ref Block); +}; + +devices: list of ref Device; + +NIOB: con 100; # for starters +HIOB: con 127; # prime + +hiob := array[HIOB] of list of ref Block; # hash buckets +iohead: ref Block; +iotail: ref Block; +bufsize := 0; + +iobufinit(bsize: int) +{ + bufsize = bsize; + for(i:=0; i= 0) { + hp := hiob[p.addr%HIOB:]; + l = nil; + for(f := hp[0]; f != nil; f = tl f) + if(hd f != p) + l = (hd f) :: l; + hp[0] = l; + } + + # Hash and fill + p.addr = addr; + p.dev = dev; + p.busy++; + sys->seek(dev.fd, big addr*big dev.sectorsize, 0); + if(sys->read(dev.fd, p.data, dev.sectorsize) != dev.sectorsize){ + p.addr = -1; # stop caching + p.put(); + purge(dev); + return nil; + } + dh[0] = p :: dh[0]; + return p; +} + +Block.put(p: self ref Block) +{ + p.busy--; + if(p.busy < 0) + panic("Block.put"); + + if(p == iohead) + return; + + # Link onto head for lru + if(p.prev != nil) + p.prev.next = p.next; + else + iohead = p.next; + + if(p.next != nil) + p.next.prev = p.prev; + else + iotail = p.prev; + + p.prev = nil; + p.next = iohead; + iohead.prev = p; + iohead = p; +} + +purge(dev: ref Device) +{ + for(i := 0; i < HIOB; i++){ + l := hiob[i]; + hiob[i] = nil; + for(; l != nil; l = tl l){ # reverses bucket's list, but never mind + p := hd l; + if(p.dev == dev) + p.busy = 0; + else + hiob[i] = p :: hiob[i]; + } + } +} + +devattach(name: string, mode: int, sectorsize: int): (ref Device, string) +{ + if(sectorsize > bufsize) + return (nil, "sector size too big"); + fd := sys->open(name, mode); + if(fd == nil) + return(nil, sys->sprint("%s: can't open: %r", name)); + (rc, dir) := sys->fstat(fd); + if(rc < 0) + return (nil, sys->sprint("%r")); + for(dl := devices; dl != nil; dl = tl dl){ + d := hd dl; + if(d.qid.path != dir.qid.path || d.qid.vers != dir.qid.vers) + continue; + if(d.dtype != dir.dtype || d.dev != dir.dev) + continue; + d.inuse++; + if(chatty) + sys->print("inuse=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd); + return (d, nil); + } + if(chatty) + sys->print("alloc \"%s\", dev=%H...\n", name, fd); + d := ref Device; + d.inuse = 1; + d.name = name; + d.qid = dir.qid; + d.dtype = dir.dtype; + d.dev = dir.dev; + d.fd = fd; + d.sectorsize = sectorsize; + devices = d :: devices; + return (d, nil); +} + +Device.detach(d: self ref Device) +{ + d.inuse--; + if(d.inuse < 0) + panic("putxdata"); + if(chatty) + sys->print("decref=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd); + if(d.inuse == 0){ + if(chatty) + sys->print("purge...\n"); + purge(d); + dl := devices; + devices = nil; + for(; dl != nil; dl = tl dl) + if((hd dl) != d) + devices = (hd dl) :: devices; + } +} + +panic(s: string) +{ + sys->print("panic: %s\n", s); + a: array of byte; + a[5] = byte 0; # trap +} diff --git a/appl/cmd/9export.b b/appl/cmd/9export.b new file mode 100644 index 00000000..5df1c8cf --- /dev/null +++ b/appl/cmd/9export.b @@ -0,0 +1,180 @@ +implement P9export; + +include "sys.m"; + sys: Sys; + +include "draw.m"; +include "keyring.m"; +include "security.m"; +include "factotum.m"; +include "encoding.m"; +include "arg.m"; + +P9export: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +factotumfile := "/mnt/factotum/rpc"; + +fail(status, msg: string) +{ + sys->fprint(sys->fildes(2), "9export: %s\n", msg); + raise "fail:"+status; +} + +nomod(mod: string) +{ + fail("load", sys->sprint("can't load %s: %r", mod)); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + + arg->init(args); + arg->setusage("9export [-aA9] [-k keyspec] [-e enc digest]"); + flags := 0; + cryptalg := ""; # will be rc4_256 sha1 + keyspec := ""; + noauth := 0; + xflag := Sys->EXPWAIT; + while((o := arg->opt()) != 0) + case o { + 'a' => + xflag = Sys->EXPASYNC; + 'A' => + noauth = 1; + 'e' => + cryptalg = arg->earg(); + if(cryptalg == "clear") + cryptalg = nil; + 'k' => + keyspec = arg->earg(); + '9' => + ; + * => + arg->usage(); + } + args = arg->argv(); + arg = nil; + + sys->pctl(Sys->FORKFD|Sys->FORKNS, nil); + + fd := sys->fildes(0); + + secret: array of byte; + if(noauth == 0){ + factotum := load Factotum Factotum->PATH; + if(factotum == nil) + nomod(Factotum->PATH); + factotum->init(); + facfd := sys->open(factotumfile, Sys->ORDWR); + if(facfd == nil) + fail("factotum", sys->sprint("can't open %s: %r", factotumfile)); + ai := factotum->proxy(fd, facfd, "proto=p9any role=server "+keyspec); + if(ai == nil) + fail("auth", sys->sprint("can't authenticate 9export: %r")); + secret = ai.secret; + } + + # read tree; it's a Plan 9 bug that there's no reliable delimiter + btree := array[2048] of byte; + n := sys->read(fd, btree, len btree); + if(n <= 0) + fail("tree", sys->sprint("can't read tree: %r")); + tree := string btree[0:n]; + if(sys->chdir(tree) < 0){ + sys->fprint(fd, "chdir(%d:\"%s\"): %r", n, tree); + fail("tree", sys->sprint("bad tree: %s", tree)); + } + if(sys->write(fd, array of byte "OK", 2) != 2) + fail("tree", sys->sprint("can't OK tree: %r")); + impo := array[2048] of byte; + for(n = 0; n < len impo; n++) + if(sys->read(fd, impo[n:], 1) != 1) + fail("impo", sys->sprint("can't read impo: %r")); + else if(impo[n] == byte 0 || impo[n] == byte '\n') + break; + if(n < 4 || string impo[0:4] != "impo") + fail("impo", "wasn't impo: possibly old import/cpu"); + if(noauth == 0 && cryptalg != nil){ + if(secret == nil) + fail("import", "didn't establish shared secret"); + random := load Random Random->PATH; + if(random == nil) + nomod(Random->PATH); + kr := load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + ssl := load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + base64 := load Encoding Encoding->BASE64PATH; + if(base64 == nil) + nomod(Encoding->BASE64PATH); + key := array[16] of byte; # myrand[4] secret[8] hisrand[4] + key[0:] = random->randombuf(Random->ReallyRandom, 4); + ns := len secret; + if(ns > 8) + ns = 8; + key[12:] = secret[0:ns]; + if(sys->write(fd, key[12:], 4) != 4) + fail("import", sys->sprint("can't write key to remote: %r")); + if(readn(fd, key, 4) != 4) + fail("import", sys->sprint("can't read remote key: %r")); + digest := array[Keyring->SHA1dlen] of byte; + kr->sha1(key, len key, digest, nil); + err: string; + (fd, err) = pushssl(fd, base64->dec(S(digest[10:20])), base64->dec(S(digest[0:10])), cryptalg); + if(err != nil) + fail("import", sys->sprint("can't push security layer: %s", err)); + } + if(sys->export(fd, ".", xflag) < 0) + fail("export", sys->sprint("can't export %s: %r", tree)); +} + +readn(fd: ref Sys->FD, buf: array of byte, nb: int): int +{ + for(nr := 0; nr < nb;){ + n := sys->read(fd, buf[nr:], nb-nr); + if(n <= 0){ + if(nr == 0) + return n; + break; + } + nr += n; + } + return nr; +} + +S(a: array of byte): string +{ + s := ""; + for(i:=0; isprint("%.2ux", int a[i]); + return s; +} + +pushssl(fd: ref Sys->FD, secretin, secretout: array of byte, alg: string): (ref Sys->FD, string) +{ + ssl := load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + + (err, c) := ssl->connect(fd); + if(err != nil) + return (nil, "can't connect ssl: " + err); + + err = ssl->secret(c, secretin, secretout); + if(err != nil) + return (nil, "can't write secret: " + err); + if(sys->fprint(c.cfd, "alg %s", alg) < 0) + return (nil, sys->sprint("can't push algorithm %s: %r", alg)); + + return (c.dfd, nil); +} diff --git a/appl/cmd/9srvfs.b b/appl/cmd/9srvfs.b new file mode 100644 index 00000000..d152d1bb --- /dev/null +++ b/appl/cmd/9srvfs.b @@ -0,0 +1,99 @@ +implement P9srvfs; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "sh.m"; + sh: Sh; + +include "arg.m"; + +P9srvfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + if(str == nil) + nomod(String->PATH); + + perm := 8r600; + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("9srvfs [-p perm] name path|{command}"); + while((o := arg->opt()) != 0) + case o { + 'p' => + s := arg->earg(); + if(s == nil) + arg->usage(); + (perm, s) = str->toint(s, 8); + if(s != nil) + arg->usage(); + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 2) + arg->usage(); + arg = nil; + + srvname := hd args; + args = tl args; + dest := hd args; + if(dest == nil) + dest = "."; + iscmd := dest[0] == '{' && dest[len dest-1] == '}'; + if(!iscmd){ # quick check before creating service file + (ok, d) := sys->stat(dest); + if(ok < 0) + error(sys->sprint("can't stat %s: %r", dest)); + if((d.mode & Sys->DMDIR) == 0) + error(sys->sprint("%s: not a directory", dest)); + }else{ + sh = load Sh Sh->PATH; + if(sh == nil) + nomod(Sh->PATH); + } + srvfd := sys->create("/srv/"+srvname, Sys->ORDWR, perm); + if(srvfd == nil) + error(sys->sprint("can't create /srv/%s: %r", srvname)); + if(iscmd){ + sync := chan of int; + spawn runcmd(sh, ctxt, dest :: nil, srvfd, sync); + <-sync; + }else{ + if(sys->export(srvfd, dest, Sys->EXPWAIT) < 0) + error(sys->sprint("export failed: %r")); + } +} + +error(msg: string) +{ + sys->fprint(sys->fildes(2), "9srvfs: %s\n", msg); + raise "fail:error"; +} + +nomod(mod: string) +{ + error(sys->sprint("can't load %s: %r", mod)); +} + +runcmd(sh: Sh, ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, sync: chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sync <-= 0; + sh->run(ctxt, argv); +} diff --git a/appl/cmd/9win.b b/appl/cmd/9win.b new file mode 100644 index 00000000..b2d2bd47 --- /dev/null +++ b/appl/cmd/9win.b @@ -0,0 +1,453 @@ +implement Ninewin; +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Image, Display, Pointer: import draw; +include "arg.m"; +include "keyboard.m"; +include "tk.m"; +include "wmclient.m"; + wmclient: Wmclient; + Window: import wmclient; +include "sh.m"; + sh: Sh; + +# run a p9 graphics program (default rio) under inferno wm, +# making available to it: +# /dev/winname - naming the current inferno window (changing on resize) +# /dev/mouse - pointer file + resize events; write to change position +# /dev/cursor - change appearance of cursor. +# /dev/draw - inferno draw device +# /dev/cons - read keyboard events, write to 9win stdout. + +Ninewin: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; +winname: string; + +init(ctxt: ref Draw->Context, argv: list of string) +{ + size := Draw->Point(500, 500); + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + wmclient = load Wmclient Wmclient->PATH; + wmclient->init(); + sh = load Sh Sh->PATH; + + buts := Wmclient->Resize; + if(ctxt == nil){ + ctxt = wmclient->makedrawcontext(); + buts = Wmclient->Plain; + } + arg := load Arg Arg->PATH; + arg->init(argv); + arg->setusage("9win [-s] [-x width] [-y height]"); + exportonly := 0; + while(((opt := arg->opt())) != 0){ + case opt { + 's' => + exportonly = 1; + 'x' => + size.x = int arg->earg(); + 'y' => + size.y = int arg->earg(); + * => + arg->usage(); + } + } + if(size.x < 1 || size.y < 1) + arg->usage(); + argv = arg->argv(); + if(argv != nil && hd argv == "-s"){ + exportonly = 1; + argv = tl argv; + } + if(argv == nil && !exportonly) + argv = "rio" :: nil; + if(argv != nil && exportonly){ + sys->fprint(sys->fildes(2), "9win: no command allowed with -s flag\n"); + raise "fail:usage"; + } + title := "9win"; + if(!exportonly) + title += " " + hd argv; + w := wmclient->window(ctxt, title, buts); + w.reshape(((0, 0), size)); + w.onscreen(nil); + if(w.image == nil){ + sys->fprint(sys->fildes(2), "9win: cannot get image to draw on\n"); + raise "fail:no window"; + } + + sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil); + ld := "/n/9win"; + if(sys->bind("#s", ld, Sys->MREPL) == -1 && + sys->bind("#s", ld = "/n/local", Sys->MREPL) == -1){ + sys->fprint(sys->fildes(2), "9win: cannot bind files: %r\n"); + raise "fail:error"; + } + w.startinput("kbd" :: "ptr" :: nil); + spawn ptrproc(rq := chan of Sys->Rread, ptr := chan[10] of ref Pointer, reshape := chan[1] of int); + + + fwinname := sys->file2chan(ld, "winname"); + fconsctl := sys->file2chan(ld, "consctl"); + fcons := sys->file2chan(ld, "cons"); + fmouse := sys->file2chan(ld, "mouse"); + fcursor := sys->file2chan(ld, "cursor"); + if(!exportonly){ + spawn run(sync := chan of string, w.ctl, ld, argv); + if((e := <-sync) != nil){ + sys->fprint(sys->fildes(2), "9win: %s", e); + raise "fail:error"; + } + } + spawn serveproc(w, rq, fwinname, fconsctl, fcons, fmouse, fcursor); + if(!exportonly){ + # handle events synchronously so that we don't get a "killed" message + # from the shell. + handleevents(w, ptr, reshape); + }else{ + spawn handleevents(w, ptr, reshape); + sys->bind(ld, "/dev", Sys->MBEFORE); + export(sys->fildes(0), w.ctl); + } +} + +handleevents(w: ref Window, ptr: chan of ref Pointer, reshape: chan of int) +{ + for(;;)alt{ + c := <-w.ctxt.ctl or + c = <-w.ctl => + e := w.wmctl(c); + if(e != nil) + sys->fprint(sys->fildes(2), "9win: ctl error: %s\n", e); + if(e == nil && c != nil && c[0] == '!'){ + alt{ + reshape <-= 1 => + ; + * => + ; + } + winname = nil; + } + p := <-w.ctxt.ptr => + if(w.pointer(*p) == 0){ + # XXX would block here if client isn't reading mouse... but we do want to + # extert back-pressure, which conflicts. + alt{ + ptr <-= p => + ; + * => + ; # sys->fprint(sys->fildes(2), "9win: discarding mouse event\n"); + } + } + } +} + +serveproc(w: ref Window, mouserq: chan of Sys->Rread, fwinname, fconsctl, fcons, fmouse, fcursor: ref Sys->FileIO) +{ + winid := 0; + krc: list of Sys->Rread; + ks: string; + + for(;;)alt { + c := <-w.ctxt.kbd => + ks[len ks] = inf2p9key(c); + if(krc != nil){ + hd krc <-= (array of byte ks, nil); + ks = nil; + krc = tl krc; + } + (nil, d, nil, wc) := <-fcons.write => + if(wc != nil){ + sys->write(sys->fildes(1), d, len d); + wc <-= (len d, nil); + } + (nil, nil, nil, rc) := <-fcons.read => + if(rc != nil){ + if(ks != nil){ + rc <-= (array of byte ks, nil); + ks = nil; + }else + krc = rc :: krc; + } + (offset, nil, nil, rc) := <-fwinname.read => + if(rc != nil){ + if(winname == nil){ + winname = sys->sprint("noborder.9win.%d", winid++); + if(w.image.name(winname, 1) == -1){ + sys->fprint(sys->fildes(2), "9win: namewin %q failed: %r", winname); + rc <-= (nil, "namewin failure"); + break; + } + } + d := array of byte winname; + if(offset < len d) + d = d[offset:]; + else + d = nil; + rc <-= (d, nil); + } + (nil, nil, nil, wc) := <-fwinname.write => + if(wc != nil) + wc <-= (-1, "permission denied"); + (nil, nil, nil, rc) := <-fconsctl.read => + if(rc != nil) + rc <-= (nil, "permission denied"); + (nil, d, nil, wc) := <-fconsctl.write => + if(wc != nil){ + if(string d != "rawon") + wc <-= (-1, "cannot change console mode"); + else + wc <-= (len d, nil); + } + (nil, nil, nil, rc) := <-fmouse.read => + if(rc != nil) + mouserq <-= rc; + (nil, d, nil, wc) := <-fmouse.write => + if(wc != nil){ + e := cursorset(w, string d); + if(e == nil) + wc <-= (len d, nil); + else + wc <-= (-1, e); + } + (nil, nil, nil, rc) := <-fcursor.read => + if(rc != nil) + rc <-= (nil, "permission denied"); + (nil, d, nil, wc) := <-fcursor.write => + if(wc != nil){ + e := cursorswitch(w, d); + if(e == nil) + wc <-= (len d, nil); + else + wc <-= (-1, e); + } + } +} + +ptrproc(rq: chan of Sys->Rread, ptr: chan of ref Pointer, reshape: chan of int) +{ + rl: list of Sys->Rread; + c := ref Pointer(0, (0, 0), 0); + for(;;){ + ch: int; + alt{ + p := <-ptr => + ch = 'm'; + c = p; + <-reshape => + ch = 'r'; + rc := <-rq => + rl = rc :: rl; + continue; + } + if(rl == nil) + rl = <-rq :: rl; + hd rl <-= (sys->aprint("%c%11d %11d %11d %11d ", ch, c.xy.x, c.xy.y, c.buttons, c.msec), nil); + rl = tl rl; + } +} + +cursorset(w: ref Window, m: string): string +{ + if(m == nil || m[0] != 'm') + return "invalid mouse message"; + x := int m[1:]; + for(i := 1; i < len m; i++) + if(m[i] == ' '){ + while(m[i] == ' ') + i++; + break; + } + if(i == len m) + return "invalid mouse message"; + y := int m[i:]; + return w.wmctl(sys->sprint("ptr %d %d", x, y)); +} + +cursorswitch(w: ref Window, d: array of byte): string +{ + Hex: con "0123456789abcdef"; + if(len d != 2*4+64) + return w.wmctl("cursor"); + hot := Draw->Point(bglong(d, 0*4), bglong(d, 1*4)); + s := sys->sprint("cursor %d %d 16 32 ", hot.x, hot.y); + for(i := 2*4; i < len d; i++){ + c := int d[i]; + s[len s] = Hex[c >> 4]; + s[len s] = Hex[c & 16rf]; + } + return w.wmctl(s); +} + +run(sync, ctl: chan of string, ld: string, argv: list of string) +{ + Rcmeta: con "|<>&^*[]?();"; + sys->pctl(Sys->FORKNS, nil); + if(sys->bind("#₪", "/srv", Sys->MCREATE) == -1){ + sync <-= sys->sprint("cannot bind srv device: %r"); + exit; + } + srvname := "/srv/9win."+string sys->pctl(0, nil); # XXX do better. + fd := sys->create(srvname, Sys->ORDWR, 8r600); + if(fd == nil){ + sync <-= sys->sprint("cannot create %s: %r", srvname); + exit; + } + sync <-= nil; + spawn export(fd, ctl); + sh->run(nil, "os" :: + "rc" :: "-c" :: + "mount "+srvname+" /mnt/term;"+ + "rm "+srvname+";"+ + "bind -b /mnt/term"+ld+" /dev;"+ + "bind /mnt/term/dev/draw /dev/draw ||"+ + "bind -a /mnt/term/dev /dev;"+ + quotedc("cd"::"/mnt/term"+cwd()::nil, Rcmeta)+";"+ + quotedc(argv, Rcmeta)+";":: + nil + ); +} + +export(fd: ref Sys->FD, ctl: chan of string) +{ + sys->export(fd, "/", Sys->EXPWAIT); + ctl <-= "exit"; +} + +inf2p9key(c: int): int +{ + KF: import Keyboard; + + P9KF: con 16rF000; + Spec: con 16rF800; + Khome: con P9KF|16r0D; + Kup: con P9KF|16r0E; + Kpgup: con P9KF|16r0F; + Kprint: con P9KF|16r10; + Kleft: con P9KF|16r11; + Kright: con P9KF|16r12; + Kdown: con Spec|16r00; + Kview: con Spec|16r00; + Kpgdown: con P9KF|16r13; + Kins: con P9KF|16r14; + Kend: con P9KF|16r18; + Kalt: con P9KF|16r15; + Kshift: con P9KF|16r16; + Kctl: con P9KF|16r17; + + case c { + Keyboard->LShift => + return Kshift; + Keyboard->LCtrl => + return Kctl; + Keyboard->LAlt => + return Kalt; + Keyboard->Home => + return Khome; + Keyboard->End => + return Kend; + Keyboard->Up => + return Kup; + Keyboard->Down => + return Kdown; + Keyboard->Left => + return Kleft; + Keyboard->Right => + return Kright; + Keyboard->Pgup => + return Kpgup; + Keyboard->Pgdown => + return Kpgdown; + Keyboard->Ins => + return Kins; + + # function keys + KF|1 or + KF|2 or + KF|3 or + KF|4 or + KF|5 or + KF|6 or + KF|7 or + KF|8 or + KF|9 or + KF|10 or + KF|11 or + KF|12 => + return (c - KF) + P9KF; + } + return c; +} + +cwd(): string +{ + return sys->fd2path(sys->open(".", Sys->OREAD)); +} + +# from string.b, waiting for declaration to be uncommented. +quotedc(argv: list of string, cl: string): string +{ + s := ""; + while (argv != nil) { + arg := hd argv; + for (i := 0; i < len arg; i++) { + c := arg[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\'' || in(c, cl)) + break; + } + if (i < len arg || arg == nil) { + s += "'" + arg[0:i]; + for (; i < len arg; i++) { + if (arg[i] == '\'') + s[len s] = '\''; + s[len s] = arg[i]; + } + s[len s] = '\''; + } else + s += arg; + if (tl argv != nil) + s[len s] = ' '; + argv = tl argv; + } + return s; +} + +in(c: int, s: string): int +{ + n := len s; + if(n == 0) + return 0; + ans := 0; + negate := 0; + if(s[0] == '^') { + negate = 1; + s = s[1:]; + n--; + } + for(i := 0; i < n; i++) { + if(s[i] == '-' && i > 0 && i < n-1) { + if(c >= s[i-1] && c <= s[i+1]) { + ans = 1; + break; + } + i++; + } + else + if(c == s[i]) { + ans = 1; + break; + } + } + if(negate) + ans = !ans; + return ans; +} + +bglong(d: array of byte, i: int): int +{ + return int d[i] | (int d[i+1]<<8) | (int d[i+2]<<16) | (int d[i+3]<<24); +} diff --git a/appl/cmd/B.b b/appl/cmd/B.b new file mode 100644 index 00000000..910e3d06 --- /dev/null +++ b/appl/cmd/B.b @@ -0,0 +1,107 @@ +implement B; + +include "sys.m"; +include "draw.m"; +include "workdir.m"; + +FD: import Sys; +Context: import Draw; + +B: module +{ + init: fn(nil: ref Context, argv: list of string); +}; + +sys: Sys; +stderr: ref FD; +wkdir: string; + +init(nil: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + if(len argv < 2) { + sys->fprint(stderr, "Usage: B file ...\n"); + return; + } + argv = tl argv; + + cmd := "exec B "; + while(argv != nil) { + f := hd argv; + if(len f > 0 && f[0] != '/' && f[0] != '-') + f = wd() + f; + cmd += "/usr/inferno"+f; + argv = tl argv; + if(argv != nil) + cmd += " "; + } + cfd := sys->open("/cmd/clone", sys->ORDWR); + if(cfd == nil) { + sys->fprint(stderr, "B: open /cmd/clone: %r\n"); + return; + } + + buf := array[32] of byte; + n := sys->read(cfd, buf, len buf); + if(n <= 0) { + sys->fprint(stderr, "B: read /cmd/#/ctl: %r\n"); + return; + } + dir := "/cmd/"+string buf[0:n]; + + # Start the Command + n = sys->fprint(cfd, "%s", cmd); + if(n <= 0) { + sys->fprint(stderr, "B: exec: %r\n"); + return; + } + + io := sys->open(dir+"/data", sys->ORDWR); + if(io == nil) { + sys->fprint(stderr, "B: open /cmd/#/data: %r\n"); + return; + } + + sys->pctl(sys->NEWPGRP, nil); + copy(io, sys->fildes(1), nil); +} + +wd(): string +{ + if(wkdir != nil) + return wkdir; + + gwd := load Workdir Workdir->PATH; + + wkdir = gwd->init(); + if(wkdir == nil) { + sys->fprint(stderr, "B: can't get working dir: %r"); + exit; + } + wkdir = wkdir+"/"; + return wkdir; +} + +copy(f, t: ref FD, c: chan of int) +{ + if(c != nil) + c <-= sys->pctl(0, nil); + + buf := array[8192] of byte; + for(;;) { + r := sys->read(f, buf, len buf); + if(r <= 0) + break; + w := sys->write(t, buf, r); + if(w != r) + break; + } +} + +kill(pid: int) +{ + fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE); + sys->fprint(fd, "kill"); +} diff --git a/appl/cmd/archfs.b b/appl/cmd/archfs.b new file mode 100644 index 00000000..11567731 --- /dev/null +++ b/appl/cmd/archfs.b @@ -0,0 +1,630 @@ +implement Archfs; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + +include "string.m"; + str: String; + +include "daytime.m"; + daytime: Daytime; + +include "styx.m"; + styx: Styx; + NOFID: import Styx; + +include "arg.m"; + +Archfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Ahdr: adt { + name: string; + modestr: string; + d: ref Sys->Dir; +}; + +Archive: adt { + b: ref Bufio->Iobuf; + nexthdr: big; + canseek: int; + hdr: ref Ahdr; + err: string; +}; + +Iobuf: import bufio; +Tmsg, Rmsg: import styx; + +Einuse : con "fid already in use"; +Ebadfid : con "bad fid"; +Eopen : con "fid already opened"; +Enotfound : con "file does not exist"; +Enotdir : con "not a directory"; +Eperm : con "permission denied"; + +UID: con "inferno"; +GID: con "inferno"; + +debug := 0; + +Dir: adt { + dir: Sys->Dir; + offset: big; + parent: cyclic ref Dir; + child: cyclic ref Dir; + sibling: cyclic ref Dir; +}; + +Fid: adt { + fid: int; + open: int; + dir: ref Dir; +}; + +HTSZ: con 32; +fidtab := array[HTSZ] of list of ref Fid; + +root: ref Dir; +qid: int; +mtpt := "/mnt/arch"; +bio: ref Iobuf; +buf: array of byte; +skip := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + daytime = load Daytime Daytime->PATH; + styx = load Styx Styx->PATH; + if(bufio == nil || styx == nil || daytime == nil || str == nil) + fatal("failed to load modules"); + styx->init(); + + flags := Sys->MREPL; + arg := load Arg Arg->PATH; + if(arg == nil) + fatal("failed to load "+Arg->PATH); + arg->init(args); + arg->setusage("archfs [-ab] [-m mntpt] archive [prefix ...]"); + while((c := arg->opt()) != 0){ + case c { + 'D' => + debug = 1; + 'a' => + flags = Sys->MAFTER; + 'b' => + flags = Sys->MBEFORE; + 'm' => + mtpt = arg->earg(); + 's' => + skip = 1; + * => + arg->usage(); + } + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + buf = array[Sys->ATOMICIO] of byte; + # root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now()); + root = newdir(basename(mtpt), UID, GID, 8r555|Sys->DMDIR, daytime->now()); + root.parent = root; + readarch(hd args, tl args); + p := array[2] of ref Sys->FD; + if(sys->pipe(p) < 0) + fatal("can't create pipe"); + pidch := chan of int; + spawn serve(p[1], pidch); + pid := <- pidch; + if(sys->mount(p[0], nil, mtpt, flags, nil) < 0) + fatal(sys->sprint("cannot mount archive on %s: %r", mtpt)); +} + +reply(fd: ref Sys->FD, m: ref Rmsg): int +{ + if(debug) + sys->fprint(sys->fildes(2), "-> %s\n", m.text()); + s := m.pack(); + if(s == nil) + return -1; + return sys->write(fd, s, len s); +} + +error(fd: ref Sys->FD, m: ref Tmsg, e: string) +{ + reply(fd, ref Rmsg.Error(m.tag, e)); +} + +serve(fd: ref Sys->FD, pidch: chan of int) +{ + e: string; + f: ref Fid; + + pidch <-= sys->pctl(Sys->NEWNS|Sys->NEWFD, 1 :: 2 :: fd.fd :: bio.fd.fd :: nil); + bio.fd = sys->fildes(bio.fd.fd); + fd = sys->fildes(fd.fd); +Work: + while((m0 := Tmsg.read(fd, Styx->MAXRPC)) != nil){ + if(debug) + sys->fprint(sys->fildes(2), "<- %s\n", m0.text()); + pick m := m0 { + Readerror => + fatal("read error on styx server"); + Version => + (s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION); + reply(fd, ref Rmsg.Version(m.tag, s, v)); + Auth => + error(fd, m, "authentication not required"); + Flush => + reply(fd, ref Rmsg.Flush(m.tag)); + Walk => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + if(f.open){ + error(fd, m, Eopen); + continue; + } + dir := f.dir; + nq := 0; + nn := len m.names; + qids := array[nn] of Sys->Qid; + if(nn > 0){ + for(k := 0; k < nn; k++){ + if((dir.dir.mode & Sys->DMDIR) == 0){ + if(k == 0){ + error(fd, m, Enotdir); + continue Work; + } + break; + } + dir = lookup(dir, m.names[k]); + if(dir == nil){ + if(k == 0){ + error(fd, m, Enotfound); + continue Work; + } + break; + } + qids[nq++] = dir.dir.qid; + } + } + if(nq < nn) + qids = qids[0: nq]; + if(nq == nn){ + if(m.newfid != m.fid){ + f = newfid(m.newfid); + if(f == nil){ + error(fd, m, Einuse); + continue Work; + } + } + f.dir = dir; + } + reply(fd, ref Rmsg.Walk(m.tag, qids)); + Open => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + if(m.mode != Sys->OREAD){ + error(fd, m, Eperm); + continue; + } + f.open = 1; + reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA)); + Create => + error(fd, m, Eperm); + Read => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + data := read(f.dir, m.offset, m.count); + reply(fd, ref Rmsg.Read(m.tag, data)); + Write => + error(fd, m, Eperm); + Clunk => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + freefid(f); + reply(fd, ref Rmsg.Clunk(m.tag)); + Stat => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir)); + Remove => + error(fd, m, Eperm); + Wstat => + error(fd, m, Eperm); + Attach => + f = newfid(m.fid); + if(f == nil){ + error(fd, m, Einuse); + continue; + } + f.dir = root; + reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid)); + * => + fatal("unknown styx message"); + } + } +} + +newfid(fid: int): ref Fid +{ + if(fid == NOFID) + return nil; + hv := hashval(fid); + ff: ref Fid; + for(l := fidtab[hv]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid) + return nil; + if(ff == nil && f.fid == NOFID) + ff = f; + } + if((f := ff) == nil){ + f = ref Fid; + fidtab[hv] = f :: fidtab[hv]; + } + f.fid = fid; + f.open = 0; + return f; +} + +freefid(f: ref Fid) +{ + hv := hashval(f.fid); + for(l := fidtab[hv]; l != nil; l = tl l) + if(hd l == f){ + f.fid = NOFID; + f.dir = nil; + f.open = 0; + return; + } + fatal("cannot find fid"); +} + +mapfid(fid: int): (ref Fid, string) +{ + if(fid == NOFID) + return (nil, Ebadfid); + hv := hashval(fid); + for(l := fidtab[hv]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + if(f.dir == nil) + return (nil, Enotfound); + return (f, nil); + } + } + return (nil, Ebadfid); +} + +hashval(n: int): int +{ + n %= HTSZ; + if(n < 0) + n += HTSZ; + return n; +} + +readarch(f: string, args: list of string) +{ + ar := openarch(f); + if(ar == nil || ar.b == nil) + fatal(sys->sprint("cannot open %s: %r", f)); + bio = ar.b; + while((a := gethdr(ar)) != nil){ + if(args != nil){ + if(!selected(a.name, args)){ + if(skip) + return; + #drain(ar, int a.d.length); + continue; + } + mkdirs("/", a.name); + } + d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0); + if((a.d.mode & Sys->DMDIR) == 0){ + d.dir.length = a.d.length; + d.offset = bio.offset(); + } + #drain(ar, int a.d.length); + } + if(ar.err != nil) + fatal(ar.err); +} + +selected(s: string, args: list of string): int +{ + for(; args != nil; args = tl args) + if(fileprefix(hd args, s)) + return 1; + return 0; +} + +fileprefix(prefix, s: string): int +{ + n := len prefix; + m := len s; + if(n > m || !str->prefix(prefix, s)) + return 0; + if(m > n && s[n] != '/') + return 0; + return 1; +} + +basename(f: string): string +{ + for(i := len f; i > 0; ) + if(f[--i] == '/') + return f[i+1:]; + return f; +} + +split(p: string): (string, string) +{ + if(p == nil) + fatal("nil string in split"); + if(p[0] != '/') + fatal("p0 not / in split"); + while(p[0] == '/') + p = p[1:]; + i := 0; + while(i < len p && p[i] != '/') + i++; + if(i == len p) + return (p, nil); + else + return (p[0:i], p[i:]); +} + +mkdirs(basedir, name: string) +{ + (nil, names) := sys->tokenize(name, "/"); + while(names != nil){ + # sys->print("mkdir %s\n", basedir); + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + if(tl names == nil) + break; + basedir = basedir + "/" + hd names; + names = tl names; + } +} + +read(d: ref Dir, offset: big, n: int): array of byte +{ + if(d.dir.mode & Sys->DMDIR) + return readdir(d, int offset, n); + return readfile(d, offset, n); +} + +readdir(d: ref Dir, o: int, n: int): array of byte +{ + k := 0; + m := 0; + b := array[n] of byte; + for(s := d.child; s != nil; s = s.sibling){ + l := styx->packdirsize(s.dir); + if(k < o){ + k += l; + continue; + } + if(m+l > n) + break; + b[m: ] = styx->packdir(s.dir); + m += l; + } + return b[0: m]; +} + +readfile(d: ref Dir, offset: big, n: int): array of byte +{ + if(offset+big n > d.dir.length) + n = int(d.dir.length-offset); + if(n <= 0 || offset < big 0) + return nil; + bio.seek(d.offset+offset, Bufio->SEEKSTART); + a := array[n] of byte; + p := 0; + m := 0; + for( ; n != 0; n -= m){ + l := len buf; + if(n < l) + l = n; + m = bio.read(buf, l); + if(m <= 0 || m != l) + fatal("premature eof"); + a[p:] = buf[0:m]; + p += m; + } + return a; +} + +mkdir(f: string, mode: int, mtime: int, uid: string, gid: string, existsok: int): ref Dir +{ + if(f == "/") + return nil; + d := newdir(basename(f), uid, gid, mode, mtime); + addfile(d, f, existsok); + return d; +} + +addfile(d: ref Dir, path: string, existsok: int) +{ + elem: string; + + opath := path; + p := prev := root; + basedir := ""; +# sys->print("addfile %s: %s\n", d.dir.name, path); + while(path != nil){ + (elem, path) = split(path); + basedir += "/" + elem; + op := p; + p = lookup(p, elem); + if(path == nil){ + if(p != nil){ + if(!existsok && (p.dir.mode&Sys->DMDIR) == 0) + sys->fprint(sys->fildes(2), "addfile: %s already there", opath); + # fatal(sys->sprint("addfile: %s already there", opath)); + return; + } + if(prev.child == nil) + prev.child = d; + else { + for(s := prev.child; s.sibling != nil; s = s.sibling) + ; + s.sibling = d; + } + d.parent = prev; + } + else { + if(p == nil){ + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + p = lookup(op, elem); + if(p == nil) + fatal("bad file system"); + } + } + prev = p; + } +} + +lookup(p: ref Dir, f: string): ref Dir +{ + if((p.dir.mode&Sys->DMDIR) == 0) + fatal("not a directory in lookup"); + if(f == ".") + return p; + if(f == "..") + return p.parent; + for(d := p.child; d != nil; d = d.sibling) + if(d.dir.name == f) + return d; + return nil; +} + +newdir(name, uid, gid: string, mode, mtime: int): ref Dir +{ + dir := sys->zerodir; + dir.name = name; + dir.uid = uid; + dir.gid = gid; + dir.mode = mode; + dir.qid.path = big (qid++); + dir.qid.qtype = mode>>24; + dir.qid.vers = 0; + dir.atime = dir.mtime = mtime; + dir.length = big 0; + + d := ref Dir; + d.dir = dir; + d.offset = big 0; + return d; +} + +prd(d: ref Dir) +{ + dir := d.dir; + sys->print("%q %q %q %bx %x %x %d %d %bd %d %d %bd\n", + dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset); +} + +fatal(e: string) +{ + sys->fprint(sys->fildes(2), "archfs: %s\n", e); + raise "fail:error"; +} + +openarch(file: string): ref Archive +{ + b := bufio->open(file, Bufio->OREAD); + if(b == nil) + return nil; + ar := ref Archive; + ar.b = b; + ar.nexthdr = big 0; + ar.canseek = 1; + ar.hdr = ref Ahdr; + ar.hdr.d = ref Sys->Dir; + return ar; +} + +NFLDS: con 6; + +gethdr(ar: ref Archive): ref Ahdr +{ + a := ar.hdr; + b := ar.b; + m := b.offset(); + n := ar.nexthdr; + if(m != n){ + if(ar.canseek) + b.seek(n, Bufio->SEEKSTART); + else { + if(m > n) + fatal(sys->sprint("bad offset in gethdr: m=%bd n=%bd", m, n)); + if(drain(ar, int(n-m)) < 0) + return nil; + } + } + if((s := b.gets('\n')) == nil){ + ar.err = "premature end of archive"; + return nil; + } + if(s == "end of archive\n") + return nil; + (nf, fs) := sys->tokenize(s, " \t\n"); + if(nf != NFLDS){ + ar.err = "too few fields in file header"; + return nil; + } + a.name = hd fs; fs = tl fs; + (a.d.mode, nil) = str->toint(hd fs, 8); fs = tl fs; + a.d.uid = hd fs; fs = tl fs; + a.d.gid = hd fs; fs = tl fs; + (a.d.mtime, nil) = str->toint(hd fs, 10); fs = tl fs; + (tmp, nil) := str->toint(hd fs, 10); fs = tl fs; + a.d.length = big tmp; + ar.nexthdr = b.offset()+a.d.length; + return a; +} + +drain(ar: ref Archive, n: int): int +{ + while(n > 0){ + m := n; + if(m > len buf) + m = len buf; + p := ar.b.read(buf, m); + if(p != m){ + ar.err = "unexpectedly short read"; + return -1; + } + n -= m; + } + return 0; +} diff --git a/appl/cmd/auplay.b b/appl/cmd/auplay.b new file mode 100644 index 00000000..0be6f556 --- /dev/null +++ b/appl/cmd/auplay.b @@ -0,0 +1,114 @@ +implement AuPlay; + +include "sys.m"; +include "draw.m"; + +sys: Sys; +FD: import sys; +stderr: ref FD; + +include "string.m"; + +str: String; + +prog: string; +play: int; +Magic: con "rate"; +data: con "/dev/audio"; +ctl: con "/dev/audioctl"; +buffz: con Sys->ATOMICIO; + +AuPlay: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +process(f: string) +{ + buff := array[buffz] of byte; + inf := sys->open(f, Sys->OREAD); + if (inf == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", prog, f); + return; + } + n := sys->read(inf, buff, buffz); + if (n < 0) { + sys->fprint(stderr, "%s: could not read %s: %r\n", prog, f); + return; + } + if (n < 10 || string buff[0:4] != Magic) { + sys->fprint(stderr, "%s: %s: not an audio file\n", prog, f); + return; + } + i := 0; + for (;;) { + if (i == n) { + sys->fprint(stderr, "%s: %s: bad header\n", prog, f); + return; + } + if (buff[i] == byte '\n') { + i++; + if (i == n) { + sys->fprint(stderr, "%s: %s: bad header\n", prog, f); + return; + } + if (buff[i] == byte '\n') { + i++; + if ((i % 4) != 0) { + sys->fprint(stderr, "%s: %s: unpadded header\n", prog, f); + return; + } + break; + } + } + else + i++; + } + if (!play) { + sys->write(stderr, buff, i - 1); + return; + } + df := sys->open(data, Sys->OWRITE); + if (df == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", prog, data); + return; + } + cf := sys->open(ctl, Sys->OWRITE); + if (cf == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", prog, ctl); + return; + } + if (sys->write(cf, buff, i - 1) < 0) { + sys->fprint(stderr, "%s: could not write %s: %r\n", prog, ctl); + return; + } + if (n > i && sys->write(df, buff[i:n], n - i) < 0) { + sys->fprint(stderr, "%s: could not write %s: %r\n", prog, data); + return; + } + if (sys->stream(inf, df, Sys->ATOMICIO) < 0) { + sys->fprint(stderr, "%s: could not stream %s: %r\n", prog, data); + return; + } +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + stderr = sys->fildes(2); + p := hd argv; + v := tl argv; + (nil, b) := str->splitr(p, "/"); + if (b != nil) + p = b; + (b, nil) = str->splitr(p, "."); + if (b != nil) + p = b[0:len b - 1]; + prog = p; + play = prog == "auplay"; + while (v != nil) { + process(hd v); + v = tl v; + } +} diff --git a/appl/cmd/auth/aescbc.b b/appl/cmd/auth/aescbc.b new file mode 100644 index 00000000..c5b6e301 --- /dev/null +++ b/appl/cmd/auth/aescbc.b @@ -0,0 +1,254 @@ +implement Aescbc; + +# +# broadly transliterated from the Plan 9 command +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "keyring.m"; + kr: Keyring; + AESbsize, MD5dlen, SHA1dlen: import Keyring; + +include "arg.m"; + +Aescbc: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# +# encrypted file: v2hdr, 16 byte IV, AES-CBC(key, random || file), HMAC_SHA1(md5(key), AES-CBC(random || file)) +# + +Checkpat: con "XXXXXXXXXXXXXXXX"; +Checklen: con len Checkpat; +Bufsize: con 4096; +AESmaxkey: con 32; + +V2hdr: con "AES CBC SHA1 2\n"; + +bin: ref Iobuf; +bout: ref Iobuf; +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + bufio = load Bufio Bufio->PATH; + + sys->pctl(Sys->FORKFD, nil); + stderr = sys->fildes(2); + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("auth/aescbc -d [-k key] [-f keyfile] clear.txt\n or: auth/aescbc -e [-k key] [-f keyfile] file.aes"); + encrypt := -1; + keyfile: string; + pass: string; + while((o := arg->opt()) != 0) + case o { + 'd' or 'e' => + if(encrypt >= 0) + arg->usage(); + encrypt = o == 'e'; + 'f' => + keyfile = arg->earg(); + 'k' => + pass = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(args != nil || encrypt < 0) + arg->usage(); + arg = nil; + + bin = bufio->fopen(sys->fildes(0), Bufio->OREAD); + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + + buf := array[Bufsize+SHA1dlen] of byte; # Checklen <= SHA1dlen + + pwd: array of byte; + if(keyfile != nil){ + fd := sys->open(keyfile, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %q: %r", keyfile), "keyfile"); + n := readn(fd, buf, len buf); + while(n > 0 && buf[n-1] == byte '\n') + n--; + if(n <= 0) + error("no key", "no key"); + pwd = buf[0:n]; + }else{ + if(pass == nil) + pass = readpassword("password"); + if(pass == nil) + error("no key", "no key"); + pwd = array of byte pass; + for(i := 0; i < len pass; i++) + pass[i] = 0; + } + key := array[AESmaxkey] of byte; + key2 := array[SHA1dlen] of byte; + dstate := kr->sha1(array of byte "aescbc file", 11, nil, nil); + kr->sha1(pwd, len pwd, key2, dstate); + for(i := 0; i < len pwd; i++) + pwd[i] = byte 0; + key[0:] = key2[0:MD5dlen]; + nkey := MD5dlen; + kr->md5(key, nkey, key2, nil); # protect key even if HMAC_SHA1 is broken + key2 = key2[0:MD5dlen]; + + if(encrypt){ + Write(array of byte V2hdr, AESbsize); + genrandom(buf, 2*AESbsize); # CBC is semantically secure if IV is unpredictable. + aes := kr->aessetup(key[0:nkey], buf); # use first AESbsize bytes as IV + kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Encrypt); # use second AESbsize bytes as initial plaintext + Write(buf, 2*AESbsize); + dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil); + while((n := bin.read(buf, Bufsize)) > 0){ + kr->aescbc(aes, buf, n, Keyring->Encrypt); + Write(buf, n); + dstate = kr->hmac_sha1(buf, n, key2, nil, dstate); + if(n < Bufsize) + break; + } + if(n < 0) + error(sys->sprint("read error: %r"), "read error"); + kr->hmac_sha1(nil, 0, key2, buf, dstate); + Write(buf, SHA1dlen); + }else{ # decrypt + Read(buf, AESbsize); + if(string buf[0:AESbsize] == V2hdr){ + Read(buf, 2*AESbsize); # read IV and random initial plaintext + aes := kr->aessetup(key[0:nkey], buf); + dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil); + kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Decrypt); + Read(buf, SHA1dlen); + while((n := bin.read(buf[SHA1dlen:], Bufsize)) > 0){ + dstate = kr->hmac_sha1(buf, n, key2, nil, dstate); + kr->aescbc(aes, buf, n, Keyring->Decrypt); + Write(buf, n); + buf[0:] = buf[n:n+SHA1dlen]; # these bytes are not yet decrypted + } + kr->hmac_sha1(nil, 0, key2, buf[SHA1dlen:], dstate); + if(!eqbytes(buf, buf[SHA1dlen:], SHA1dlen)) + error("decrypted file failed to authenticate", "failed to authenticate"); + }else{ # compatibility with past mistake; assume we're decrypting secstore files + aes := kr->aessetup(key[0:AESbsize], buf); + Read(buf, Checklen); + kr->aescbc(aes, buf, Checklen, Keyring->Decrypt); + while((n := bin.read(buf[Checklen:], Bufsize)) > 0){ + kr->aescbc(aes, buf[Checklen:], n, Keyring->Decrypt); + Write(buf, n); + buf[0:] = buf[n:n+Checklen]; + } + if(string buf[0:Checklen] != Checkpat) + error("decrypted file failed to authenticate", "failed to authenticate"); + } + } + bout.flush(); +} + +error(s: string, why: string) +{ + bout.flush(); + sys->fprint(stderr, "aescbc: %s\n", s); + raise "fail:"+why; +} + +eqbytes(a: array of byte, b: array of byte, n: int): int +{ + if(len a < n || len b < n) + return 0; + for(i := 0; i < n; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +readn(fd: ref Sys->FD, buf: array of byte, nb: int): int +{ + for(nr := 0; nr < nb;){ + n := sys->read(fd, buf[nr:], nb-nr); + if(n <= 0){ + if(nr == 0) + return n; + break; + } + nr += n; + } + return nr; +} + +Read(buf: array of byte, n: int) +{ + if(bin.read(buf, n) != n){ + sys->fprint(sys->fildes(2), "aescbc: unexpectedly short read\n"); + raise "fail:read error"; + } +} + +Write(buf: array of byte, n: int) +{ + if(bout.write(buf, n) != n){ + sys->fprint(sys->fildes(2), "aescbc: write error: %r\n"); + raise "fail:write error"; + } +} + +readpassword(prompt: string): string +{ + cons := sys->open("/dev/cons", Sys->ORDWR); + if(cons == nil) + return nil; + stdin := bufio->fopen(cons, Sys->OREAD); + if(stdin == nil) + return nil; + cfd := sys->open("/dev/consctl", Sys->OWRITE); + if (cfd == nil || sys->fprint(cfd, "rawon") <= 0) + sys->fprint(stderr, "aescbc: warning: cannot hide typed password\n"); + s: string; +L: + for(;;){ + sys->fprint(cons, "%s: ", prompt); + s = ""; + while ((c := stdin.getc()) >= 0){ + case c { + '\n' => + break L; + '\b' or 8r177 => + if(len s > 0) + s = s[0:len s - 1]; + 'u' & 8r037 => + sys->fprint(cons, "\n"); + continue L; + * => + s[len s] = c; + } + } + } + sys->fprint(cons, "\n"); + return s; +} + +genrandom(b: array of byte, n: int) +{ + fd := sys->open("/dev/notquiterandom", Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "aescbc: can't open /dev/notquiterandom: %r\n"); + raise "fail:random"; + } + if(sys->read(fd, b, n) != n){ + sys->fprint(stderr, "aescbc: can't read random numbers: %r\n"); + raise "fail:read random"; + } +} diff --git a/appl/cmd/auth/changelogin.b b/appl/cmd/auth/changelogin.b new file mode 100644 index 00000000..97141408 --- /dev/null +++ b/appl/cmd/auth/changelogin.b @@ -0,0 +1,305 @@ +implement Changelogin; + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime: Daytime; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +Changelogin: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; +keydb := "/mnt/keys"; + +init(nil: ref Draw->Context, args: list of string) +{ + ok: int; + word: string; + + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + argv0 := hd args; + args = tl args; + + if(args == nil){ + sys->fprint(stderr, "usage: %s userid\n", argv0); + raise "fail:usage"; + } + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) { + sys->fprint(stderr, "%s: can't load Daytime: %r\n", argv0); + raise "fail:load"; + } + + # get password + id := hd args; + (dbdir, secret, expiry, err) := getuser(id); + if(dbdir == nil){ + if(err != nil){ + sys->fprint(stderr, "%s: can't get auth info for %s in %s: %s\n", argv0, id, keydb, err); + raise "fail:no key"; + } + sys->print("new account\n"); + } + for(;;){ + if(secret != nil) + sys->print("secret [default = don't change]: "); + else + sys->print("secret: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok) + exit; + if(word == "" && secret != nil) + break; + if(len word >= 8) + break; + sys->print("!secret must be at least 8 characters\n"); + } + newsecret: array of byte; + if(word != ""){ + # confirm password change + word1 := word; + sys->print("confirm: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word != word1) { + sys->print("Entries do not match. Authinfo record unchanged.\n"); + raise "fail:mismatch"; + } + + pwbuf := array of byte word; + newsecret = array[Keyring->SHA1dlen] of byte; + kr->sha1(pwbuf, len pwbuf, newsecret, nil); + } + + # get expiration time (midnight of date specified) + maxdate := "17012038"; # largest date possible without incurring integer overflow + now := daytime->now(); + tm := daytime->local(now); + tm.sec = 59; + tm.min = 59; + tm.hour = 23; + tm.year += 1; + if(dbdir == nil) + expsecs := daytime->tm2epoch(tm); # set expiration date to 23:59:59 one year from today + else + expsecs = expiry; + for(;;){ + defexpdate := "permanent"; + if(expsecs != 0) { + otm := daytime->local(expsecs); + defexpdate = sys->sprint("%2.2d%2.2d%4.4d", otm.mday, otm.mon+1, otm.year+1900); + } + sys->print("expires [DDMMYYYY/permanent, return = %s]: ", defexpdate); + (ok, word) = readline(stdin, "rawoff"); + if(!ok) + exit; + if(word == "") + word = defexpdate; + if(word == "permanent"){ + expsecs = 0; + break; + } + if(len word != 8){ + sys->print("!bad date format %s\n", word); + continue; + } + tm.mday = int word[0:2]; + if(tm.mday > 31 || tm.mday < 1){ + sys->print("!bad day of month %d\n", tm.mday); + continue; + } + tm.mon = int word[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0){ + sys->print("!bad month %d\n", tm.mon + 1); + continue; + } + tm.year = int word[4:8] - 1900; + if(tm.year < 70){ + sys->print("!bad year %d (year may be no earlier than 1970)\n", tm.year + 1900); + continue; + } + expsecs = daytime->tm2epoch(tm); + if(expsecs > now) + break; + else { + newexpdate := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900); + tm = daytime->local(daytime->now()); + today := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900); + sys->print("!bad expiration date %s (must be between %s and %s)\n", newexpdate, today, maxdate); + expsecs = now; + } + } + newexpiry := expsecs; + +# # get the free form field +# if(pw != nil) +# npw.other = pw.other; +# else +# npw.other = ""; +# sys->print("free form info [return = %s]: ", npw.other); +# (ok, word) = readline(stdin,"rawoff"); +# if(!ok) +# exit; +# if(word != "") +# npw.other = word; + + if(dbdir == nil){ + dbdir = keydb+"/"+id; + fd := sys->create(dbdir, Sys->OREAD, Sys->DMDIR|8r700); + if(fd == nil){ + sys->fprint(stderr, "%s: can't create account %s: %r\n", argv0, id); + raise "fail:create user"; + } + } + changed := 0; + if(!eq(newsecret, secret)){ + if(putsecret(dbdir, newsecret) < 0){ + sys->fprint(stderr, "%s: can't update secret for %s: %r\n", argv0, id); + raise "fail:update"; + } + changed = 1; + } + if(newexpiry != expiry){ + if(putexpiry(dbdir, newexpiry) < 0){ + sys->fprint(stderr, "%s: can't update expiry time for %s: %r\n", argv0, id); + raise "fail:update"; + } + changed = 1; + } + sys->print("change written\n"); +} + +getuser(id: string): (string, array of byte, int, string) +{ + (ok, nil) := sys->stat(keydb); + if(ok < 0) + return (nil, nil, 0, sys->sprint("can't stat %s: %r", id)); + dbdir := keydb+"/"+id; + (ok, nil) = sys->stat(dbdir); + if(ok < 0) + return (nil, nil, 0, nil); + fd := sys->open(dbdir+"/secret", Sys->OREAD); + if(fd == nil) + return (nil, nil, 0, sys->sprint("can't open %s/secret: %r", id)); + d: Sys->Dir; + (ok, d) = sys->fstat(fd); + if(ok < 0) + return (nil, nil, 0, sys->sprint("can't stat %s/secret: %r", id)); + l := int d.length; + secret: array of byte; + if(l > 0){ + secret = array[l] of byte; + if(sys->read(fd, secret, len secret) != len secret) + return (nil, nil, 0, sys->sprint("error reading %s/secret: %r", id)); + } + expiry := 0; + fd = sys->open(dbdir+"/expire", Sys->OREAD); + if(fd == nil) + return (nil, nil, 0, sys->sprint("can't open %s/expiry: %r", id)); + b := array[32] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return (nil, nil, 0, sys->sprint("error reading %s/expiry: %r", id)); + return (dbdir, secret, int string b[0:n], nil); +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +putsecret(dir: string, secret: array of byte): int +{ + fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return -1; + return sys->write(fd, secret, len secret); +} + +putexpiry(dir: string, expiry: int): int +{ + fd := sys->open(dir+"/expire", Sys->OWRITE); + if(fd == nil) + return -1; + return sys->fprint(fd, "%d", expiry); +} + +readline(io: ref Sys->FD, mode: string): (int, string) +{ + r : int; + line : string; + buf := array[8192] of byte; + fdctl : ref Sys->FD; + rawoff := array of byte "rawoff"; + + # + # Change console mode to rawon + # + if(mode == "rawon"){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){ + sys->fprint(stderr, "unable to change console mode"); + return (0,nil); + } + } + + # + # Read up to the CRLF + # + line = ""; + for(;;) { + r = sys->read(io, buf, len buf); + if(r <= 0){ + sys->fprint(stderr, "error read from console mode"); + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + return (0, nil); + } + + line += string buf[0:r]; + if ((len line >= 1) && (line[(len line)-1] == '\n')){ + if(mode == "rawon"){ + r = sys->write(stdout,array of byte "\n",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + break; + } + else { + if(mode == "rawon"){ + #r = sys->write(stdout, array of byte "*",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + } + } + + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + + # Total success! + return (1, line[0:len line - 1]); +} diff --git a/appl/cmd/auth/convpasswd.b b/appl/cmd/auth/convpasswd.b new file mode 100644 index 00000000..8463b0fb --- /dev/null +++ b/appl/cmd/auth/convpasswd.b @@ -0,0 +1,120 @@ +implement Convpasswd; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "keyring.m"; + keyring: Keyring; + IPint: import keyring; + +include "security.m"; + +include "arg.m"; + +Convpasswd: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +PW: adt { + id: string; # user id + pw: array of byte; # password hashed by SHA + expire: int; # expiration time (epoch seconds) + other: string; # about the account +}; + +mntpt := "/mnt/keys"; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + noload(Arg->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + force := 0; + verbose := 0; + arg->init(args); + arg->setusage("convpasswd [-f] [-v] [-m /mnt/keys] [passwordfile]"); + while((o := arg->opt()) != 0) + case o { + 'f' => force = 1; + 'm' => mntpt = arg->earg(); + 'v' => verbose = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + f := "/keydb/password"; + if(args != nil) + f = hd args; + iob := bufio->open(f, Bufio->OREAD); + if(iob == nil) + error(sys->sprint("%s: %r", f)); + for(line := 1; (s := iob.gets('\n')) != nil; line++) { + (n, tokl) := sys->tokenize(s, ":\n"); + if (n < 3){ + sys->fprint(sys->fildes(2), "convpasswd: %s:%d: invalid format\n", f, line); + continue; + } + pw := ref PW; + pw.id = hd tokl; + pw.pw = IPint.b64toip(hd tl tokl).iptobytes(); + pw.expire = int hd tl tl tokl; + if (n==3) + pw.other = nil; + else + pw.other = hd tl tl tl tokl; + err := writekey(pw, force); + if(err != nil) + error(sys->sprint("error writing /mnt/keys entry for %s: %s", pw.id, err)); + if(verbose) + sys->print("%s\n", pw.id); + } +} + +noload(p: string) +{ + error(sys->sprint("can't load %s: %r", p)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "convpasswd: %s\n", s); + raise "fail:error"; +} + +writekey(pw: ref PW, force: int): string +{ + dir := mntpt+"/"+pw.id; + if(sys->open(dir, Sys->OREAD) == nil){ + # make it + d := sys->create(dir, Sys->OREAD, Sys->DMDIR|8r600); + if(d == nil) + return sys->sprint("can't create %s: %r", dir); + }else if(!force) + return nil; # leave existing entry alone + secret := dir+"/secret"; + fd := sys->open(secret, Sys->OWRITE); + if(fd == nil) + return sys->sprint("can't open %s: %r", secret); + if(sys->write(fd, pw.pw, len pw.pw) != len pw.pw) + return sys->sprint("error writing %s: %r", secret); + expire := dir+"/expire"; + fd = sys->open(expire, Sys->OWRITE); + if(fd == nil) + return sys->sprint("can't open %s: %r", expire); + if(sys->fprint(fd, "%d", pw.expire) < 0) + return sys->sprint("error writing %s: %r", expire); + # no equivalent of `other' + return nil; +} diff --git a/appl/cmd/auth/countersigner.b b/appl/cmd/auth/countersigner.b new file mode 100644 index 00000000..a444f807 --- /dev/null +++ b/appl/cmd/auth/countersigner.b @@ -0,0 +1,59 @@ +implement Countersigner; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + +Countersigner: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "countersigner: no key database\n"); + raise "fail:no keydb"; + } + + # get boxid + buf := kr->getmsg(stdin); + if(buf == nil){ + sys->fprint(stderr, "countersigner: client hung up\n"); + raise "fail:hungup"; + } + boxid := string buf; + + # read file + file := "countersigned/"+boxid; + fd := sys->open(file, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "countersigner: can't open %s: %r\n", file); + raise "fail:bad boxid"; + } + blind := kr->getmsg(fd); + if(blind == nil){ + sys->fprint(stderr, "countersigner: can't read %s\n", file); + raise "fail:no blind"; + } + + # answer client + kr->sendmsg(stdout, blind, len blind); +} diff --git a/appl/cmd/auth/createsignerkey.b b/appl/cmd/auth/createsignerkey.b new file mode 100644 index 00000000..90a54b6f --- /dev/null +++ b/appl/cmd/auth/createsignerkey.b @@ -0,0 +1,144 @@ +implement Createsignerkey; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "daytime.m"; + +include "keyring.m"; + kr: Keyring; + +include "arg.m"; + +# signer key never expires +SKexpire: con 0; + +# size in bits of modulus for public keys +PKmodlen: con 512; + +# size in bits of modulus for diffie hellman +DHmodlen: con 512; + +algs := array[] of {"rsa", "elgamal"}; # first entry is default + +Createsignerkey: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + err: string; + + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + if(kr == nil) + loaderr(Keyring->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + loaderr(Arg->PATH); + + arg->init(argv); + arg->setusage("createsignerkey [-a algorithm] [-f keyfile] [-e ddmmyyyy] [-b size-in-bits] name-of-owner"); + alg := algs[0]; + filename := "/keydb/signerkey"; + expire := SKexpire; + bits := PKmodlen; + while((c := arg->opt()) != 0){ + case c { + 'a' => + alg = arg->arg(); + if(alg == nil) + arg->usage(); + for(i:=0;; i++){ + if(i >= len algs) + error(sys->sprint("unknown algorithm: %s", alg)); + else if(alg == algs[i]) + break; + } + 'f' or 'k' => + filename = arg->earg(); + 'e' => + s := arg->earg(); + (err, expire) = checkdate(s); + if(err != nil) + error(err); + 'b' => + s := arg->earg(); + bits = int s; + if(bits < 32 || bits > 4096) + error("modulus must be in the range of 32 to 4096 bits"); + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + arg = nil; + + owner := hd argv; + + # generate a local key, self-signed + info := ref Keyring->Authinfo; + info.mysk = kr->genSK(alg, owner, bits); + if(info.mysk == nil) + error(sys->sprint("algorithm %s not configured in system", alg)); + info.mypk = kr->sktopk(info.mysk); + info.spk = kr->sktopk(info.mysk); + myPKbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(myPKbuf, len myPKbuf, nil, nil); + info.cert = kr->sign(info.mysk, expire, state, "sha1"); + (info.alpha, info.p) = kr->dhparams(DHmodlen); + + if(kr->writeauthinfo(filename, info) < 0) + error(sys->sprint("can't write signerkey file %s: %r", filename)); +} + +loaderr(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "createsignerkey: %s\n", s); + raise "fail:error"; +} + +checkdate(word: string): (string, int) +{ + if(len word != 8) + return ("!date must be in form ddmmyyyy", 0); + + daytime := load Daytime Daytime->PATH; + if(daytime == nil) + loaderr(Daytime->PATH); + + now := daytime->now(); + + tm := daytime->local(now); + tm.sec = 59; + tm.min = 59; + tm.hour = 24; + + tm.mday = int word[0:2]; + if(tm.mday > 31 || tm.mday < 1) + return ("!bad day of month", 0); + + tm.mon = int word[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0) + return ("!bad month", 0); + + tm.year = int word[4:8] - 1900; + if(tm.year < 70) + return ("!bad year", 0); + + newdate := daytime->tm2epoch(tm); + if(newdate < now) + return ("!expiration date must be in the future", 0); + + return (nil, newdate); +} diff --git a/appl/cmd/auth/factotum/authio.m b/appl/cmd/auth/factotum/authio.m new file mode 100644 index 00000000..7c0565b5 --- /dev/null +++ b/appl/cmd/auth/factotum/authio.m @@ -0,0 +1,80 @@ +Authio: module +{ + + Aattr, Aval, Aquery: con iota; + + Attr: adt { + tag: int; + name: string; + val: string; + + text: fn(a: self ref Attr): string; + }; + + Key: adt { + attrs: list of ref Attr; + secrets: list of ref Attr; + # proto: Authproto; + + mk: fn(attrs: list of ref Attr): ref Key; + text: fn(k: self ref Key): string; + safetext: fn(k: self ref Key): string; + }; + + Fid: adt + { + fid: int; + pid: int; + err: string; + attrs: list of ref Attr; + write: chan of (array of byte, Sys->Rwrite); + read: chan of (int, Sys->Rread); + # proto: Authproto; + done: int; + ai: ref Authinfo; + }; + + Rpc: adt { + r: ref Fid; + cmd: int; + arg: array of byte; + nbytes: int; + rc: chan of (array of byte, string); + }; + + IO: adt { + f: ref Fid; + rpc: ref Rpc; + + findkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string); + needkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string); + read: fn(io: self ref IO): array of byte; + readn: fn(io: self ref IO, n: int): array of byte; + write: fn(io: self ref IO, buf: array of byte, n: int): int; + toosmall: fn(io: self ref IO, n: int); + error: fn(io: self ref IO, s: string); + ok: fn(io: self ref IO); + done: fn(io: self ref IO, ai: ref Authinfo); + }; + + # need more ... ? + Authinfo: adt { + cuid: string; # caller id + suid: string; # server id + cap: string; # capability (only valid on server side) + secret: array of byte; + }; + + memrandom: fn(a: array of byte, n: int); + eqbytes: fn(a, b: array of byte): int; + netmkaddr: fn(addr, net, svc: string): string; + user: fn(): string; + lookattrval: fn(a: list of ref Attr, n: string): string; + parseline: fn(s: string): list of ref Attr; +}; + +Authproto: module +{ + init: fn(f: Authio): string; + interaction: fn(attrs: list of ref Authio->Attr, io: ref Authio->IO): string; +}; diff --git a/appl/cmd/auth/factotum/factotum.b b/appl/cmd/auth/factotum/factotum.b new file mode 100644 index 00000000..5f5b02a3 --- /dev/null +++ b/appl/cmd/auth/factotum/factotum.b @@ -0,0 +1,978 @@ +implement Factotum, Authio; + +# +# Copyright © 2003-2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + Rread, Rwrite: import Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "keyring.m"; + +include "authio.m"; + +include "arg.m"; + +Factotum: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +#confirm, log + +Files: adt { + ctl: ref Sys->FileIO; + rpc: ref Sys->FileIO; + proto: ref Sys->FileIO; + needkey: ref Sys->FileIO; +}; + +Debug: con 0; +debug := Debug; + +files: Files; +authio: Authio; + +keymanc: chan of (list of ref Attr, int, chan of (ref Key, string)); + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + authio = load Authio "$self"; + + svcname := "#sfactotum"; + mntpt := "/mnt/factotum"; + arg := load Arg Arg->PATH; + if(arg != nil){ + arg->init(args); + arg->setusage("auth/factotum [-d] [-m /mnt/factotum] [-s factotum]"); + while((o := arg->opt()) != 0) + case o { + 'd' => debug = 1; + 'm' => mntpt = arg->earg(); + 's' => svcname = "#s"+arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + } + sys->unmount(nil, mntpt); + if(sys->bind(svcname, mntpt, Sys->MREPL) < 0) + err(sys->sprint("can't bind %s on %s: %r", svcname, mntpt)); + files.ctl = sys->file2chan(mntpt, "ctl"); + files.rpc = sys->file2chan(mntpt, "rpc"); + files.proto = sys->file2chan(mntpt, "proto"); + files.needkey = sys->file2chan(mntpt, "needkey"); + if(files.ctl == nil || files.rpc == nil || files.proto == nil || files.needkey == nil) + err(sys->sprint("can't create %s/*: %r", mntpt)); + keymanc = chan of (list of ref Attr, int, chan of (ref Key, string)); + spawn factotumsrv(); +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return nil; + return string b[0:n]; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "factotum: %s\n", s); + raise "fail:error"; +} + +rlist: list of ref Fid; + +factotumsrv() +{ + sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKENV, nil); + if(!Debug) + privacy(); + allkeys := array[0] of ref Key; + pidc := chan of int; + donec := chan of ref Fid; +# keyc := chan of (list of ref Attr, chan of (ref Key, string)); + needfid := -1; + needed, needy: list of (int, list of ref Attr, chan of (ref Key, string)); + needread: Sys->Rread; + needtag := 0; + for(;;) X: alt{ + r := <-donec => + r.pid = 0; + cleanfid(r.fid); + + (off, nbytes, nil, rc) := <-files.ctl.read => + if(rc == nil) + break; + s := ""; + for(i := 0; i < len allkeys; i++) + if((k := allkeys[i]) != nil) + s += k.safetext()+"\n"; + rc <-= reads(s, off, nbytes); + (nil, data, nil, wc) := <-files.ctl.write => + if(wc == nil) + break; + (nf, flds) := sys->tokenize(string data, "\n\r"); + if(nf > 1){ + # compatibility with plan 9; has the advantage you can tell which key is wrong + wc <-= (0, "multiline write not allowed"); + break; + } + s := hd flds; + if(s == nil || s[0] == '#'){ + wc <-= (len data, nil); + break; + } + for(i := 0; i < len s && s[i] != ' '; i++){ + # skip + } + verb := s[0:i]; + if(i < len s) + i++; + s = s[i:]; + case verb { + "key" => + k := Key.mk(parseline(s)); + if(k == nil){ + wc <-= (len data, nil); # ignore it + break; + } + if(lookattrval(k.attrs, "proto") == nil){ + wc <-= (0, "key without proto"); + break; + } + allkeys = addkey(allkeys, k); + wc <-= (len data, nil); + "delkey" => + attrs := parseline(s); + for(al := attrs; al != nil; al = tl al){ + a := hd al; + if(a.name[0] == '!' && (a.val != nil || a.tag != Aquery)){ + wc <-= (0, "cannot specify values for private fields"); + break X; + } + } + if(delkey(allkeys, attrs) == 0) + wc <-= (0, "no matching keys"); + else + wc <-= (len data, nil); + "debug" => + wc <-= (len data, nil); + * => + wc <-= (0, "unknown ctl request"); + } + + (nil, nbytes, fid, rc) := <-files.rpc.read => + if(rc == nil) + break; + r := findfid(fid); + if(r == nil){ + rc <-= (nil, "unknown request"); + break; + } + alt{ + r.read <-= (nbytes, rc) => + ; + * => + rc <-= (nil, "concurrent rpc read not allowed"); + } + (nil, data, fid, wc) := <-files.rpc.write => + if(wc == nil){ + cleanfid(fid); + break; + } + r := findfid(fid); + if(r == nil){ + r = ref Fid(fid, 0, nil, nil, chan[1] of (array of byte, Rwrite), chan[1] of (int, Rread), 0, nil); + spawn request(r, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + # this non-blocking write avoids a potential deadlock situation that + # can happen when a proto module calls findkey at the same time + # a client tries to write to the rpc file. this might not be the correct fix! + alt{ + r.write <-= (data, wc) => + ; + * => + wc <-= (-1, "concurrent rpc write not allowed"); + } + + (off, nbytes, nil, rc) := <-files.proto.read => + if(rc == nil) + break; + rc <-= reads("pass\np9any\n", off, nbytes); # TO DO + (nil, nil, nil, wc) := <-files.proto.write => + if(wc != nil) + wc <-= (0, "illegal operation"); + + (nil, nil, fid, rc) := <-files.needkey.read => + if(rc == nil) + break; + if(needfid >= 0 && fid != needfid){ + rc <-= (nil, "file in use"); + break; + } + needfid = fid; + if(needy != nil){ + (tag, attr, kc) := hd needy; + needy = tl needy; + needed = (tag, attr, kc) :: needed; + rc <-= (sys->aprint("needkey tag=%ud %s", tag, attrtext(attr)), nil); + break; + } + if(needread != nil){ + rc <-= (nil, "already reading"); + break; + } + needread = rc; + (nil, data, fid, wc) := <-files.needkey.write => + if(wc == nil){ + if(needfid == fid){ + needfid = -1; # TO DO? give needkey errors back to request + needread = nil; + } + break; + } + if(needfid >= 0 && fid != needfid){ + wc <-= (0, "file in use"); + break; + } + needfid = fid; + tagline := parseline(string data); + if(len tagline != 1 || (t := lookattrval(tagline, "tag")) == nil){ + wc <-= (0, "no tag"); + break; + } + tag := int t; + nl: list of (int, list of ref Attr, chan of (ref Key, string)); + found := 0; + for(l := needed; l != nil; l = tl l){ + (ntag, attrs, kc) := hd l; + if(tag == ntag){ + found = 1; + k := findkey(allkeys, attrs); + if(k != nil) + kc <-= (k, nil); + else + kc <-= (nil, "needkey "+attrtext(attrs)); + while((l = tl l) != nil) + nl = hd l :: nl; + break; + } + nl = hd l :: nl; + } + if(found) + wc <-= (len data, nil); + else + wc <-= (0, "tag not found"); + + (attrs, required, kc) := <-keymanc => + # look for key and reply + k := findkey(allkeys, attrs); + if(k != nil){ + kc <-= (k, nil); + break; + }else if(!required || needfid == -1){ + kc <-= (nil, "needkey "+attrtext(attrs)); + break; + } + # query surrounding environment using needkey + if(needread != nil){ + needed = (needtag, attrs, kc) :: needed; + needread <-= (sys->aprint("needkey tag=%ud %s", needtag, attrtext(attrs)), nil); + needread = nil; + needtag++; + }else + needy = (needtag++, attrs, kc) :: needy; + } +} + +findfid(fid: int): ref Fid +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else if(r.pid) + kill(r.pid); + } +} + +kill(pid: int) +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +privacy() +{ + fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "private") < 0) + sys->fprint(sys->fildes(2), "factotum: warning: unable to make memory private: %r\n"); +} + +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +Ogok, Ostart, Oread, Owrite, Oauthinfo, Oattr: con iota; + +ops := array[] of { + (Ostart, "start"), + (Oread, "read"), + (Owrite, "write"), + (Oauthinfo, "authinfo"), + (Oattr, "attr"), +}; + +request(r: ref Fid, pidc: chan of int, donec: chan of ref Fid) +{ + pidc <-= sys->pctl(0, nil); + rpc := rio(r); + while(rpc != nil){ + if(rpc.cmd == Ostart){ + (proto, attrs, e) := startproto(string rpc.arg); + if(e != nil){ + reply(rpc, "error "+e); + rpc = rio(r); + continue; + } + r.attrs = attrs; # saved for attr request + ok(rpc); + io := ref IO(r, nil); + { + err := proto->interaction(attrs, io); + if(debug && err != nil) + sys->fprint(sys->fildes(2), "factotum: failure: %s\n", err); + if(r.err == nil) + r.err = err; + r.done = 1; + }exception ex{ + "*" => + r.done = 0; + r.err = "exception "+ex; + } + if(r.err != nil) + io.error(r.err); + rpc = finish(r); + r.attrs = nil; + r.err = nil; + r.done = 0; + r.ai = nil; + }else + reply(rpc, "no current protocol"); + } + flushreq(r, donec); +} + +startproto(request: string): (Authproto, list of ref Attr, string) +{ + attrs := parseline(request); + if(Debug) + sys->print("-> %s <-\n", attrtext(attrs)); + p := lookattrval(attrs, "proto"); + if(p == nil) + return (nil, nil, "did not specify protocol"); + if(Debug) + sys->print("proto=%s\n", p); + if(any(p, "./")) # avoid unpleasantness + return (nil, nil, "illegal protocol: "+p); + proto := load Authproto "/dis/auth/proto/"+p+".dis"; + if(proto == nil) + return (nil, nil, sys->sprint("protocol %s: %r", p)); + if(Debug) + sys->print("start %s\n", p); + e: string; + { + e = proto->init(authio); + }exception ex{ + "*" => + e = "exception "+ex; + } + if(e != nil) + return (nil, nil, e); + return (proto, attrs, nil); +} + +finish(r: ref Fid): ref Rpc +{ + while((rpc := rio(r)) != nil) + case rpc.cmd { + Owrite => + phase(rpc, "protocol phase error"); + Oread => + if(r.err != nil) + reply(rpc, "error "+r.err); + else + done(rpc, r.ai); + Oauthinfo => + if(r.done){ + if(r.ai == nil) + reply(rpc, "error no authinfo available"); + else{ + a := packai(r.ai); + if(rpc.nbytes-3 < len a) + reply(rpc, sys->sprint("toosmall %d", len a + 3)); + else + okdata(rpc, a); + } + }else + reply(rpc, "error authentication unfinished"); + Ostart => + return rpc; + * => + reply(rpc, "error unexpected request"); + } + return nil; +} + +flushreq(r: ref Fid, donec: chan of ref Fid) +{ + for(;;) alt{ + donec <-= r => + exit; + (nil, wc) := <-r.write => + wc <-= (0, "write rpc protocol error"); + (nil, rc) := <-r.read => + rc <-= (nil, "read rpc protocol error"); + } +} + +rio(r: ref Fid): ref Rpc +{ + req: array of byte; + for(;;) alt{ + (data, wc) := <-r.write => + if(req != nil){ + wc <-= (0, "rpc pending; read to clear"); + break; + } + req = data; + wc <-= (len data, nil); + + (nbytes, rc) := <-r.read => + if(req == nil){ + rc <-= (nil, "no rpc pending"); + break; + } + (cmd, arg) := op(req, ops); + req = nil; + rpc := ref Rpc(r, cmd, arg, nbytes, rc); + case cmd { + Ogok => + reply(rpc, "error unknown rpc"); + break; + Oattr => + if(r.attrs == nil) + reply(rpc, "error no attributes"); + else + reply(rpc, "ok "+attrtext(r.attrs)); + break; + * => + return rpc; + } + } +} + +ok(rpc: ref Rpc) +{ + reply(rpc, "ok"); +} + +okdata(rpc: ref Rpc, a: array of byte) +{ + b := array[len a + 3] of byte; + b[0] = byte 'o'; + b[1] = byte 'k'; + b[2] = byte ' '; + b[3:] = a; + rpc.rc <-= (b, nil); +} + +done(rpc: ref Rpc, ai: ref Authinfo) +{ + rpc.r.ai = ai; + rpc.r.done = 1; + if(ai != nil) + reply(rpc, "done haveai"); + else + reply(rpc, "done"); +} + +phase(rpc: ref Rpc, s: string) +{ + reply(rpc, "phase "+s); +} + +needkey(rpc: ref Rpc, attrs: list of ref Attr) +{ + reply(rpc, "needkey "+attrtext(attrs)); +} + +reply(rpc: ref Rpc, s: string) +{ + rpc.rc <-= reads(s, 0, rpc.nbytes); +} + +puta(a: array of byte, n: int, v: array of byte): int +{ + if(n < 0) + return -1; + c := len v; + if(n+2+c > len a) + return -1; + a[n++] = byte c; + a[n++] = byte (c>>8); + a[n:] = v; + return n + len v; +} + +packai(ai: ref Authinfo): array of byte +{ + a := array[1024] of byte; + i := puta(a, 0, array of byte ai.cuid); + i = puta(a, i, array of byte ai.suid); + i = puta(a, i, array of byte ai.cap); + i = puta(a, i, ai.secret); + if(i < 0) + return nil; + return a[0:i]; +} + +op(a: array of byte, ops: array of (int, string)): (int, array of byte) +{ + arg: array of byte; + for(i := 0; i < len a; i++) + if(a[i] == byte ' '){ + if(i+1 < len a) + arg = a[i+1:]; + break; + } + s := string a[0:i]; + for(i = 0; i < len ops; i++){ + (cmd, name) := ops[i]; + if(s == name) + return (cmd, arg); + } + return (Ogok, arg); +} + +parseline(s: string): list of ref Attr +{ + fld := str->unquoted(s); + rfld := fld; + for(fld = nil; rfld != nil; rfld = tl rfld) + fld = (hd rfld) :: fld; + attrs: list of ref Attr; + for(; fld != nil; fld = tl fld){ + n := hd fld; + a := ""; + tag := Aattr; + for(i:=0; i + return a.name; + Aval => + return a.name+"="+a.val; + Aquery => + return a.name+"?"; + * => + return "??"; + } +} + +attrtext(attrs: list of ref Attr): string +{ + s := ""; + sp := 0; + for(; attrs != nil; attrs = tl attrs){ + if(sp) + s[len s] = ' '; + sp = 1; + s += (hd attrs).text(); + } + return s; +} + +lookattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).tag != Aquery && a.name == n) + return a; + return nil; +} + +lookattrval(attrs: list of ref Attr, n: string): string +{ + if((a := lookattr(attrs, n)) != nil) + return a.val; + return nil; +} + +anyattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).name == n) + return a; + return nil; +} + +reverse[T](l: list of T): list of T +{ + r: list of T; + for(; l != nil; l = tl l) + r = hd l :: r; + return r; +} + +setattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + # new attributes + nl: list of ref Attr; + for(rl := rv; rl != nil; rl = tl rl) + if(anyattr(lv, (hd rl).name) == nil) + nl = ref(*hd rl) :: nl; + + # new values + for(; lv != nil; lv = tl lv){ + a := lookattr(rv, (hd lv).name); # won't take queries + if(a != nil) + nl = ref *a :: nl; + } + + return reverse(nl); +} + +delattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + nl: list of ref Attr; + for(; lv != nil; lv = tl lv) + if(anyattr(rv, (hd lv).name) == nil) + nl = hd lv :: nl; + return reverse(nl); +} + +matchattr(attrs: list of ref Attr, pat: ref Attr): int +{ + return (b := lookattr(attrs, pat.name)) != nil && (pat.tag == Aquery || b.val == pat.val); +} + +matchattrs(pub: list of ref Attr, secret: list of ref Attr, pats: list of ref Attr): int +{ + for(pl := pats; pl != nil; pl = tl pl) + if(!matchattr(pub, hd pl) && !matchattr(secret, hd pl)) + return 0; + return 1; +} + +sortattrs(attrs: list of ref Attr): list of ref Attr +{ + a := array[len attrs] of ref Attr; + i := 0; + for(l := attrs; l != nil; l = tl l) + a[i++] = hd l; + shellsort(a); + for(i = 0; i < len a; i++) + l = a[i] :: l; + return l; +} + +# sort into decreasing order (we'll reverse the list) +shellsort(a: array of ref Attr) +{ + n := len a; + for(gap := n; gap > 0; ) { + gap /= 2; + max := n-gap; + ex: int; + do{ + ex = 0; + for(i := 0; i < max; i++) { + j := i+gap; + if(a[i].name > a[j].name || a[i].name == nil) { + t := a[i]; a[i] = a[j]; a[j] = t; + ex = 1; + } + } + }while(ex); + } +} + +findkey(keys: array of ref Key, attrs: list of ref Attr): ref Key +{ + if(Debug) + sys->print("findkey %q\n", attrtext(attrs)); + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil && matchattrs(k.attrs, k.secrets, attrs)) + return k; + return nil; +} + +delkey(keys: array of ref Key, attrs: list of ref Attr): int +{ + nk := 0; + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil) + if(matchattrs(k.attrs, k.secrets, attrs)){ + nk++; + keys[i] = nil; + } + return nk; +} + +Key.mk(attrs: list of ref Attr): ref Key +{ + k := ref Key; + for(; attrs != nil; attrs = tl attrs){ + a := hd attrs; + if(a.name != nil){ + if(a.name[0] == '!') + k.secrets = a :: k.secrets; + else + k.attrs = a :: k.attrs; + } + } + if(k.attrs != nil || k.secrets != nil) + return k; + return nil; +} + +addkey(keys: array of ref Key, k: ref Key): array of ref Key +{ + for(i := 0; i < len keys; i++) + if(keys[i] == nil){ + keys[i] = k; + return keys; + } + n := array[len keys+1] of ref Key; + n[0:] = keys; + n[len keys] = k; + return n; +} + +Key.text(k: self ref Key): string +{ + s := attrtext(k.attrs); + if(s != nil && k.secrets != nil) + s[len s] = ' '; + return s + attrtext(k.secrets); +} + +Key.safetext(k: self ref Key): string +{ + s := attrtext(sortattrs(k.attrs)); + sp := s != nil; + for(sl := k.secrets; sl != nil; sl = tl sl){ + if(sp) + s[len s] = ' '; + s += sys->sprint("%s?", (hd sl).name); + } + return s; +} + +any(s: string, t: string): int +{ + for(i := 0; i < len s; i++) + for(j := 0; j < len t; j++) + if(s[i] == t[j]) + return 1; + return 0; +} + +IO.findkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); # TO DO: 1 => 0 for not needed + return <-kc; +} + +IO.needkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); + return <-kc; +} + +IO.read(io: self ref IO): array of byte +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + * => + phase(rpc, "protocol phase error"); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + Owrite => + io.rpc = rpc; + if(rpc.arg == nil) + rpc.arg = array[0] of byte; + return rpc.arg; + } + exit; +} + +IO.readn(io: self ref IO, n: int): array of byte +{ + while((buf := io.read()) != nil && len buf < n) + io.toosmall(n); + return buf; +} + +IO.write(io: self ref IO, buf: array of byte, n: int): int +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread => + if(rpc.nbytes-3 >= n){ + okdata(rpc, buf[0:n]); + return n; + } + io.toosmall(n+3); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + * => + phase(rpc, "protocol phase error"); + } + exit; +} + +IO.ok(io: self ref IO) +{ + if(io.rpc != nil){ + reply(io.rpc, "ok"); + io.rpc = nil; + } +} + +IO.toosmall(io: self ref IO, n: int) +{ + if(io.rpc != nil){ + reply(io.rpc, sys->sprint("toosmall %d", n)); + io.rpc = nil; + } +} + +IO.error(io: self ref IO, s: string) +{ + if(io.rpc != nil){ + io.rpc.rc <-= (nil, "error "+s); + io.rpc = nil; + } +} + +IO.done(io: self ref IO, ai: ref Authinfo) +{ + io.f.ai = ai; + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread or Owrite => + done(rpc, ai); + return; + * => + phase(rpc, "protocol phase error"); + } +} + +memrandom(a: array of byte, n: int) +{ + if(0){ + # speed up testing + for(i := 0; i < len a; i++) + a[i] = byte i; + return; + } + fd := sys->open("/dev/notquiterandom", Sys->OREAD); + if(fd == nil) + err("can't open /dev/notquiterandom"); + if(sys->read(fd, a, n) != n) + err("can't read /dev/notquiterandom"); +} + +eqbytes(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auth/factotum/feedkey.b b/appl/cmd/auth/factotum/feedkey.b new file mode 100644 index 00000000..606f065a --- /dev/null +++ b/appl/cmd/auth/factotum/feedkey.b @@ -0,0 +1,321 @@ +implement Feedkey; + +# +# Copyright © 2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "tk.m"; + tk: Tk; + +include "tkclient.m"; + tkclient: Tkclient; + +include "string.m"; + str: String; + +Feedkey: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +config := array[] of { + "frame .f", + "button .f.done -command {send cmd done} -text {Done}", + "frame .f.key -bg white", + "pack .f.key .f.done .f", + "update" +}; + +Debug: con 0; + +init(ctxt: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + str = load String String->PATH; + + needfile := "/mnt/factotum/needkey"; + if(Debug) + needfile = "/dev/null"; + + needs := chan of list of ref Attr; + acks := chan of int; + + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + + fd := sys->open(needfile, Sys->ORDWR); + if(fd == nil) + err(sys->sprint("can't open %s: %r", needfile)); + spawn needy(fd, needs, acks); + fd = nil; + + ctlfile := "/mnt/factotum/ctl"; + keyfd := sys->open(ctlfile, Sys->ORDWR); + if(keyfd == nil) + err(sys->sprint("can't open %s: %r", ctlfile)); + + tkclient->init(); + + spawn feedkey(ctxt, keyfd, needs, acks); +} + +feedkey(ctxt: ref Draw->Context, keyfd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int) +{ + (top, tkctl) := tkclient->toplevel(ctxt, nil, "Need key", Tkclient->Appl); + + cmd := chan of string; + tk->namechan(top, cmd, "cmd"); + + for(i := 0; i < len config; i++) + tkcmd(top, config[i]); + tkclient->startinput(top, "ptr" :: nil); + tkclient->onscreen(top, nil); + if(!Debug) + tkclient->wmctl(top, "task"); + + attrs: list of ref Attr; + for(;;) alt{ + s :=<-tkctl or + s = <-top.ctxt.ctl or + s = <-top.wreq => + tkclient->wmctl(top, s); + p := <-top.ctxt.ptr => + tk->pointer(top, *p); + c := <-top.ctxt.kbd => + tk->keyboard(top, c); + + s := <-cmd => + case s { + "done" => + result := extract(top, ".f.key", attrs); + if(Debug) + sys->print("result: %s\n", attrtext(result)); + if(sys->fprint(keyfd, "key %s", attrtext(result)) < 0) + sys->fprint(sys->fildes(2), "feedkey: can't install key %q: %r\n", attrtext(result)); + acks <-= 0; + tkclient->wmctl(top, "task"); + tk->cmd(top, "pack forget .f.key"); + * => + sys->fprint(sys->fildes(2), "feedkey: odd command: %q\n", s); + } + + attrs = <-needs => + if(attrs == nil) + exit; + tkclient->startinput(top, "kbd" :: nil); + tkcmd(top, "destroy .f.key"); + tkcmd(top, "frame .f.key -bg white"); + populate(top, ".f.key", attrs); + tkcmd(top, "pack forget .f.done"); + tkcmd(top, "pack .f.key .f.done .f"); + tkcmd(top, "update"); + tkclient->wmctl(top, "unhide"); + } +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "feedkey: %s\n", s); + raise "fail:error"; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return nil; + return string b[0:n]; +} + +tkcmd(top: ref Tk->Toplevel, cmd: string): string +{ + if(0) + sys->print("tk: %q\n", cmd); + r := tk->cmd(top, cmd); + if(r != nil && r[0] == '!') + sys->fprint(sys->fildes(2), "feedkey: tk: %q on %q\n", r, cmd); + return r; +} + +populate(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr) +{ + c := 0; + for(al := attrs; al != nil; al = tl al){ + a := hd al; + if(a.name == nil) + tkcmd(top, sys->sprint("entry %s.n%d -bg yellow", tag, c)); + else + tkcmd(top, sys->sprint("label %s.n%d -bg white -text '%s", tag, c, a.name)); + tkcmd(top, sys->sprint("label %s.e%d -bg white -text ' = ", tag, c)); + case a.tag { + Aquery => + show := ""; + if(a.name != nil && a.name[0] == '!') + show = " -show {•}"; + tkcmd(top, sys->sprint("entry %s.v%d%s -bg yellow", tag, c, show)); + if(a.val == nil && a.name == "user") + a.val = user(); + tkcmd(top, sys->sprint("%s.v%d insert 0 '%s", tag, c, a.val)); + tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag)); + Aval => + if(a.name != nil){ + val := a.val; + if(a.name[0] == '!') + val = "..."; # just in case + tkcmd(top, sys->sprint("label %s.v%d -bg white -text %s", tag, c, val)); + }else + tkcmd(top, sys->sprint("entry %s.v%d -bg yellow", tag, c)); + tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag)); + Aattr => + tkcmd(top, sys->sprint("grid %s.n%d x x -in %s -sticky w -pady 1", tag, c, tag)); + } + c++; + } +} + +extract(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr): list of ref Attr +{ + c := 0; + nl: list of ref Attr; + for(al := attrs; al != nil; al = tl al){ + a := ref *hd al; + if(a.tag == Aquery){ + a.val = tkcmd(top, sys->sprint("%s.v%d get", tag, c)); + if(a.name == nil) + a.name = tk->cmd(top, sys->sprint("%s.n%d get", tag, c)); # name might start with `!' + if(a.name != nil){ + a.tag = Aval; + nl = a :: nl; + } + }else + nl = a :: nl; + c++; + } + return nl; +} + +reverse[T](l: list of T): list of T +{ + rl: list of T; + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} + +needy(fd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int) +{ + if(Debug){ + for(;;){ + needs <-= parseline("proto=pass user? server=fred.com service=ftp confirm !password?"); + <-acks; + } + } + + buf := array[512] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + s := string buf[0:n]; + for(i := 0; i < len s; i++) + if(s[i] == ' ') + break; + if(i >= len s) + continue; + attrs := parseline(s[i+1:]); + nl: list of ref Attr; + tag: ref Attr; + for(; attrs != nil; attrs = tl attrs){ + a := hd attrs; + if(a.name == "tag") + tag = a; + else + nl = a :: nl; + } + if(nl == nil) + continue; + attrs = reverse(ref Attr(Aquery, nil, nil) :: ref Attr(Aquery, nil, nil) :: nl); # add a few blank + if(attrs != nil && tag != nil && tag.val != nil){ + needs <-= attrs; + <-acks; + sys->fprint(fd, "tag=%d", int tag.val); + } + } + if(n < 0) + sys->fprint(sys->fildes(2), "feedkey: error reading needkey: %r\n"); + needs <-= nil; +} + +# need a library module + +Aattr, Aval, Aquery: con iota; + +Attr: adt { + tag: int; + name: string; + val: string; + + text: fn(a: self ref Attr): string; +}; + +parseline(s: string): list of ref Attr +{ + fld := str->unquoted(s); + rfld := fld; + for(fld = nil; rfld != nil; rfld = tl rfld) + fld = (hd rfld) :: fld; + attrs: list of ref Attr; + for(; fld != nil; fld = tl fld){ + n := hd fld; + a := ""; + tag := Aattr; + for(i:=0; i + return a.name; + Aval => + return sys->sprint("%q=%q", a.name, a.val); + Aquery => + return a.name+"?"; + * => + return "??"; + } +} + +attrtext(attrs: list of ref Attr): string +{ + s := ""; + sp := 0; + for(; attrs != nil; attrs = tl attrs){ + if(sp) + s[len s] = ' '; + sp = 1; + s += (hd attrs).text(); + } + return s; +} diff --git a/appl/cmd/auth/factotum/mkfile b/appl/cmd/auth/factotum/mkfile new file mode 100644 index 00000000..1979a14c --- /dev/null +++ b/appl/cmd/auth/factotum/mkfile @@ -0,0 +1,27 @@ +<../../../../mkconfig + +DIRS=\ + proto\ + +TARG=\ + factotum.dis\ + feedkey.dis\ + rpc.dis\ + +SYSMODULES=\ + arg.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +MODULES=\ + authio.m\ + +DISBIN=$ROOT/dis/auth + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/auth/factotum/proto/infauth.b b/appl/cmd/auth/factotum/proto/infauth.b new file mode 100644 index 00000000..244979bc --- /dev/null +++ b/appl/cmd/auth/factotum/proto/infauth.b @@ -0,0 +1,362 @@ +implement Authproto; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "keyring.m"; + keyring: Keyring; + IPint: import keyring; + SK, PK, Certificate, DigestState: import Keyring; +include "security.m"; +include "bufio.m"; +include "sexprs.m"; + sexprs: Sexprs; + Sexp: import sexprs; +include "spki.m"; + spki: SPKI; +include "daytime.m"; + daytime: Daytime; +include "keyreps.m"; + keyreps: Keyreps; + Keyrep: import keyreps; +include "../authio.m"; + authio: Authio; + Aattr, Aval, Aquery: import Authio; + Attr, IO, Key, Authinfo: import authio; + +# at end of authentication, sign a hash of the authenticated username and +# a secret known only to factotum. that certificate can act as +# a later proof that this factotum has authenticated that user, +# and hence factotum will disclose certificates that allow disclosure +# only to that username. + +Debug: con 0; + +Maxmsg: con 4000; + +Error0, Error1: exception(string); + +init(f: Authio): string +{ + authio = f; + sys = load Sys Sys->PATH; + spki = load SPKI SPKI->PATH; + spki->init(); + sexprs = load Sexprs Sexprs->PATH; + sexprs->init(); + keyring = load Keyring Keyring->PATH; + daytime = load Daytime Daytime->PATH; + keyreps = load Keyreps Keyreps->PATH; + keyreps->init(); + return nil; +} + +interaction(attrs: list of ref Attr, io: ref IO): string +{ + ai: ref Authinfo; + (key, err) := io.findkey(attrs, "proto=infauth"); + if(key == nil) + return err; + info: ref Keyring->Authinfo; + (info, err) = keytoauthinfo(key); + if(info == nil) + return err; + anysigner := int authio->lookattrval(key.attrs, "anysigner"); + rattrs: list of ref Sexp; + { + # send auth protocol version number + sendmsg(io, array of byte "1"); + + # get auth protocol version number + if(int string getmsg(io) != 1) + raise Error0("incompatible authentication protocol"); + + # generate alpha**r0 + p := info.p; + low := p.shr(p.bits()/4); + r0 := rand(low, p, Random->NotQuiteRandom); + αr0 := info.alpha.expmod(r0, p); + # trim(αr0); the IPint library should do this for us, i think. + + # send alpha**r0 mod p, mycert, and mypk + sendmsg(io, array of byte αr0.iptob64()); + sendmsg(io, array of byte keyring->certtostr(info.cert)); + sendmsg(io, array of byte keyring->pktostr(info.mypk)); + + # get alpha**r1 mod p, hiscert, hispk + αr1 := IPint.b64toip(string getmsg(io)); + + # trying a fast one + if(p.cmp(αr1) <= 0) + raise Error0("implausible parameter value"); + + # if alpha**r1 == alpha**r0, someone may be trying a replay + if(αr0.eq(αr1)) + raise Error0("possible replay attack"); + + hiscert := keyring->strtocert(string getmsg(io)); + if(hiscert == nil && !anysigner) + raise Error0(sys->sprint("bad certificate: %r")); + + buf := getmsg(io); + hispk := keyring->strtopk(string buf); + if(!anysigner){ + # verify their public key + if(verify(info.spk, hiscert, buf) == 0) + raise Error0("pk doesn't match certificate"); # likely the signers don't match. + + # check expiration date - in seconds of epoch + if(hiscert.exp != 0 && hiscert.exp <= now()) + raise Error0("certificate expired"); + } + buf = nil; + + # sign alpha**r0 and alpha**r1 and send + αcert := sign(info.mysk, "sha", 0, array of byte (αr0.iptob64() + αr1.iptob64())); + sendmsg(io, array of byte keyring->certtostr(αcert)); + + # get signature of alpha**r1 and alpha**r0 and verify + αcert = keyring->strtocert(string getmsg(io)); + if(αcert == nil) + raise Error0("alpha**r1 doesn't match certificate"); + + if(verify(hispk, αcert, array of byte (αr1.iptob64() + αr0.iptob64())) == 0) + raise Error0(sys->sprint("bad certificate: %r")); + + ai = ref Authinfo; + # we are now authenticated and have a common secret, alpha**(r0*r1) + if(!anysigner) + rattrs = sl(ss("signer") :: principal(info.spk) :: nil) :: rattrs; + rattrs = sl(ss("remote-pk") :: principal(hispk) :: nil) :: rattrs; + rattrs = sl(ss("local-pk") :: principal(info.mypk) :: nil) :: rattrs; + rattrs = sl(ss("secret") :: sb(αr1.expmod(r0, p).iptobytes()) :: nil) :: rattrs; + ai.suid = hispk.owner; + ai.cuid = info.mypk.owner; + sendmsg(io, array of byte "OK"); + }exception e{ + Error0 => + err = e; + senderr(io, e); + break; + Error1 => + senderr(io, "missing your authentication data"); + x: string = e; + return "remote: "+x; + } + + { + while(string getmsg(io) != "OK") + ; + }exception e{ + Error0 => + return e; + Error1 => + x: string = e; + return "remote: "+x; + } + if(err != nil) + return err; + + return negotiatecrypto(io, key, ai, rattrs); +} + +negotiatecrypto(io: ref IO, key: ref Key, ai: ref Authinfo, attrs: list of ref Sexp): string +{ + role := authio->lookattrval(key.attrs, "role"); + alg: string; + { + if(role == "client"){ + alg = authio->lookattrval(key.attrs, "alg"); + if(alg == nil) + alg = "md5/rc4_256"; + sendmsg(io, array of byte alg); + }else if(role == "server"){ + alg = string getmsg(io); + if(!algcompatible(alg, sys->tokenize(authio->lookattrval(key.attrs, "algs"), " ").t1)) + raise Error0("unsupported client algorithm"); + } + }exception e{ + Error0 or + Error1 => + return e; + } + + if(alg != nil) + attrs = sl(ss("alg") :: ss(alg) :: nil) :: attrs; + ai.secret = sl(attrs).pack(); + + io.done(ai); + return nil; +} + +algcompatible(nil: string, nil: list of string): int +{ + return 1; # XXX +} + +principal(pk: ref Keyring->PK): ref Sexp +{ + return spki->(Keyrep.pk(pk).mkkey()).sexp(); +} + +ipint(i: int): ref IPint +{ + return IPint.inttoip(i); +} + +rand(p, q: ref IPint, nil: int): ref IPint +{ + if(p.cmp(q) > 0) + (p, q) = (q, p); + diff := q.sub(p); + q = nil; + if(diff.cmp(ipint(2)) < 0){ + sys->print("rand range must be at least 2"); + return IPint.inttoip(0); + } + l := diff.bits(); + T := ipint(1).shl(l); + l = ((l + 7) / 8) * 8; + slop := T.div(diff).t1; + r: ref IPint; + do{ + r = IPint.random(0, l); + }while(r.cmp(slop) < 0); + r = r.div(diff).t1.add(p); + return r; +} + +now(): int +{ + return daytime->now(); +} + +Hashfn: type ref fn(a: array of byte, alen: int, digest: array of byte, state: ref DigestState): ref DigestState; + +hashalg(ha: string): Hashfn +{ + case ha { + "sha" or + "sha1" => + return keyring->sha1; + "md4" => + return keyring->md4; + "md5" => + return keyring->md5; + } + return nil; +} + +sign(sk: ref SK, ha: string, exp: int, buf: array of byte): ref Certificate +{ + state := hashalg(ha)(buf, len buf, nil, nil); + return keyring->sign(sk, exp, state, ha); +} + +verify(pk: ref PK, cert: ref Certificate, buf: array of byte): int +{ + state := hashalg(cert.ha)(buf, len buf, nil, nil); + return keyring->verify(pk, cert, state); +} + +getmsg(io: ref IO): array of byte raises (Error0, Error1) +{ + while((buf := io.read()) == nil || (n := len buf) < 5) + io.toosmall(5); + if(len buf != 5) + raise Error0("io error: (impossible?) msg length " + string n); + h := string buf; + if(h[0] == '!') + m := int h[1:]; + else + m = int h; + while((buf = io.read()) == nil || (n = len buf) < m) + io.toosmall(m); + if(len buf != m) + raise Error0("io error: (impossible?) msg length " + string m); + if(h[0] == '!'){ +sys->print("got remote error: %s, len %d\n", string buf, len string buf); + raise Error1(string buf); + } + return buf; +} + +sendmsg(io: ref IO, buf: array of byte) +{ + h := sys->aprint("%4.4d\n", len buf); + io.write(h, len h); + io.write(buf, len buf); +} + +senderr(io: ref IO, e: string) +{ + buf := array of byte e; + h := sys->aprint("!%3.3d\n", len buf); + io.write(h, len h); + io.write(buf, len buf); +} + +keytoauthinfo(key:ref Key): (ref Keyring->Authinfo, string) +{ + if((s := authio->lookattrval(key.secrets, "!authinfo")) == nil){ + # XXX could look up authinfo by hash at this point + return (nil, "no authinfo attribute"); + } + + return strtoauthinfo(s); +} + +strtoauthinfo(s: string): (ref Keyring->Authinfo, string) +{ + (se, err, nil) := Sexp.parse(s); + if(se == nil) + return (nil, err); + els := se.els(); + if(len els != 5) + return (nil, "bad authinfo contents"); + ai := ref Keyring->Authinfo; + if((ai.spk = keyring->strtopk((hd els).astext())) == nil) + return (nil, "bad signer public key"); + els = tl els; + if((ai.cert = keyring->strtocert((hd els).astext())) == nil) + return (nil, "bad certificate"); + els = tl els; + if((ai.mysk = keyring->strtosk((hd els).astext())) == nil) + return (nil, "bad secret/public key"); + if((ai.mypk = keyring->sktopk(ai.mysk)) == nil) + return (nil, "cannot make pk from sk"); + els = tl els; + if((ai.alpha = IPint.bytestoip((hd els).asdata())) == nil) + return (nil, "bad value for alpha"); + els = tl els; + if((ai.p = IPint.bytestoip((hd els).asdata())) == nil) + return (nil, "bad value for p"); + return (ai, nil); +} + +authinfotostr(ai: ref Keyring->Authinfo): string +{ + return (ref Sexp.List( + ss(keyring->pktostr(ai.spk)) :: + ss(keyring->certtostr(ai.cert)) :: + ss(keyring->sktostr(ai.mysk)) :: + sb(ai.alpha.iptobytes()) :: + sb(ai.p.iptobytes()) :: + nil + )).b64text(); +} + +ss(s: string): ref Sexp.String +{ + return ref Sexp.String(s, nil); +} + +sb(d: array of byte): ref Sexp.Binary +{ + return ref Sexp.Binary(d, nil); +} + +sl(l: list of ref Sexp): ref Sexp +{ + return ref Sexp.List(l); +} diff --git a/appl/cmd/auth/factotum/proto/keyreps.b b/appl/cmd/auth/factotum/proto/keyreps.b new file mode 100644 index 00000000..5fdac2c0 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/keyreps.b @@ -0,0 +1,173 @@ +implement Keyreps; +include "sys.m"; + sys: Sys; +include "keyring.m"; + kr: Keyring; + IPint: import kr; +include "sexprs.m"; +include "spki.m"; +include "encoding.m"; + base64: Encoding; +include "keyreps.m"; + +init() +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + base64 = load Encoding Encoding->BASE64PATH; +} + +keyextract(flds: list of string, names: list of (string, int)): list of (string, ref IPint) +{ + a := array[len flds] of ref IPint; + for(i := 0; i < len a; i++){ + a[i] = IPint.b64toip(hd flds); + flds = tl flds; + } + rl: list of (string, ref IPint); + for(; names != nil; names = tl names){ + (n, p) := hd names; + if(p < len a) + rl = (n, a[p]) :: rl; + } + return revt(rl); +} + +Keyrep.pk(pk: ref Keyring->PK): ref Keyrep.PK +{ + s := kr->pktostr(pk); + (nf, flds) := sys->tokenize(s, "\n"); + if((nf -= 2) < 0) + return nil; + case hd flds { + "rsa" => + return ref Keyrep.PK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("e",1), ("n",0)})); + "elgamal" or "dsa" => + return ref Keyrep.PK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2)})); + * => + return nil; + } +} + +Keyrep.sk(pk: ref Keyring->SK): ref Keyrep.SK +{ + s := kr->pktostr(pk); + (nf, flds) := sys->tokenize(s, "\n"); + if((nf -= 2) < 0) + return nil; + case hd flds { + "rsa" => + return ref Keyrep.SK(hd flds, hd tl flds, + keyextract(tl tl flds,list of {("e",1), ("n",0), ("!dk",2), ("!p",3), ("!q",4), ("!kp",5), ("!kq",6), ("!c2",7)})); + "elgamal" or "dsa" => + return ref Keyrep.SK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2), ("!secret",3)})); + * => + return nil; + } +} + +Keyrep.get(k: self ref Keyrep, n: string): ref IPint +{ + for(el := k.els; el != nil; el = tl el) + if((hd el).t0 == n) + return (hd el).t1; + return nil; +} + +Keyrep.getb(k: self ref Keyrep, n: string): array of byte +{ + v := k.get(n); + if(v == nil) + return nil; + return pre0(v.iptobebytes()); +} + +pre0(a: array of byte): array of byte +{ + for(i:=0; i 0) + a = a[i:]; + if(len a < 1 || (int a[0] & 16r80) == 0) + return a; + b := array[len a + 1] of byte; + b[0] = byte 0; + b[1:] = a; + return b; +} + +Keyrep.mkpk(k: self ref Keyrep): (ref Keyring->PK, int) +{ + case k.alg { + "rsa" => + e := k.get("e"); + n := k.get("n"); + return (kr->strtopk(sys->sprint("rsa\n%s\n%s\n%s\n", k.owner, n.iptob64(), e.iptob64())), n.bits()); + * => + raise "Keyrep: unknown algorithm" + k.alg; + } +} + +Keyrep.mksk(k: self ref Keyrep): ref Keyring->SK +{ + case k.alg { + "rsa" => + e := k.get("e"); + n := k.get("n"); + dk := k.get("!dk"); + p := k.get("!p"); + q := k.get("!q"); + kp := k.get("!kp"); + kq := k.get("!kq"); + c12 := k.get("!c2"); + return kr->strtosk(sys->sprint("rsa\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + k.owner, n.iptob64(), e.iptob64(), dk.iptob64(), p.iptob64(), q.iptob64(), + kp.iptob64(), kq.iptob64(), c12.iptob64())); + * => + raise "Keyrep: unknown algorithm"; + } +} + +Keyrep.eq(k1: self ref Keyrep, k2: ref Keyrep): int +{ + # n⁲ but n is small + for(l1 := k1.els; l1 != nil; l1 = tl l1){ + (n, v1) := hd l1; + v2 := k2.get(n); + if(v2 == nil || !v1.eq(v2)) + return 0; + } + for(l2 := k2.els; l2 != nil; l2 = tl l2) + if(k1.get((hd l2).t0) == nil) + return 0; + return 1; +} + +Keyrep.mkkey(kr: self ref Keyrep): ref SPKI->Key +{ + k := ref SPKI->Key; + (k.pk, k.nbits) = kr.mkpk(); + k.sk = kr.mksk(); + return k; +} + +sig2icert(sig: ref SPKI->Signature, signer: string, exp: int): ref Keyring->Certificate +{ + if(sig.sig == nil) + return nil; + s := sys->sprint("%s\n%s\n%s\n%d\n%s\n", "rsa", sig.hash.alg, signer, exp, base64->enc((hd sig.sig).t1)); +#sys->print("alg %s *** %s\n", sig.sa, base64->enc((hd sig.sig).t1)); + return kr->strtocert(s); +} + +revt[S,T](l: list of (S,T)): list of (S,T) +{ + rl: list of (S,T); + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} diff --git a/appl/cmd/auth/factotum/proto/keyreps.m b/appl/cmd/auth/factotum/proto/keyreps.m new file mode 100644 index 00000000..ddfd7f0d --- /dev/null +++ b/appl/cmd/auth/factotum/proto/keyreps.m @@ -0,0 +1,23 @@ +Keyreps: module +{ + PATH: con "/dis/lib/spki/keyreps.dis"; + init: fn(); + Keyrep: adt { + alg: string; + owner: string; + els: list of (string, ref Keyring->IPint); + pick{ # keeps a type distance between public and private keys + PK => + SK => + } + + pk: fn(pk: ref Keyring->PK): ref Keyrep.PK; + sk: fn(sk: ref Keyring->SK): ref Keyrep.SK; + mkpk: fn(k: self ref Keyrep): (ref Keyring->PK, int); + mksk: fn(k: self ref Keyrep): ref Keyring->SK; + get: fn(k: self ref Keyrep, n: string): ref Keyring->IPint; + getb: fn(k: self ref Keyrep, n: string): array of byte; + eq: fn(k1: self ref Keyrep, k2: ref Keyrep): int; + mkkey: fn(k: self ref Keyrep): ref SPKI->Key; + }; +}; diff --git a/appl/cmd/auth/factotum/proto/mkfile b/appl/cmd/auth/factotum/proto/mkfile new file mode 100644 index 00000000..efdd73da --- /dev/null +++ b/appl/cmd/auth/factotum/proto/mkfile @@ -0,0 +1,22 @@ +<../../../../../mkconfig + +TARG=\ + p9any.dis\ + pass.dis\ + +SYSMODULES=\ + factotum.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +MODULES=\ + ../authio.m\ + +DISBIN=$ROOT/dis/auth/proto + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/auth/factotum/proto/p9any.b b/appl/cmd/auth/factotum/proto/p9any.b new file mode 100644 index 00000000..1668a701 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/p9any.b @@ -0,0 +1,232 @@ +implement Authproto; + +# currently includes p9sk1 + +include "sys.m"; + sys: Sys; + Rread, Rwrite: import Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "auth9.m"; + auth9: Auth9; + ANAMELEN, AERRLEN, DOMLEN, DESKEYLEN, CHALLEN, SECRETLEN: import Auth9; + TICKREQLEN, TICKETLEN, AUTHENTLEN: import Auth9; + Ticketreq, Ticket, Authenticator: import auth9; + +include "../authio.m"; + authio: Authio; + Aattr, Aval, Aquery: import Authio; + Attr, IO, Key, Authinfo: import authio; + netmkaddr, eqbytes, memrandom: import authio; + +include "encoding.m"; + base16: Encoding; + +Debug: con 0; + +# init, addkey, closekey, write, read, close, keyprompt + +init(f: Authio): string +{ + authio = f; + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + auth9 = load Auth9 Auth9->PATH; + auth9->init(); + base16 = load Encoding Encoding->BASE16PATH; + return nil; +} + +version := 1; + +interaction(attrs: list of ref Attr, io: ref IO): string +{ + return p9any(io); +} + +p9any(io: ref IO): string +{ + while((buf := io.read()) == nil || (n := len buf) == 0 || buf[n-1] != byte 0) + io.toosmall(2048); + s := string buf[0:n-1]; + if(Debug) + sys->print("s: %q\n", s); + (nil, flds) := sys->tokenize(s, " \t"); + if(flds != nil && len hd flds >= 2 && (hd flds)[0:2] == "v."){ + if(hd flds == "v.2"){ + version = 2; + flds = tl flds; + if(Debug) + sys->print("version 2\n"); + }else + return "p9any: unknown version"; + } + doms: list of string; + for(; flds != nil; flds = tl flds){ + (nf, subf) := sys->tokenize(hd flds, "@"); + if(nf == 2 && hd subf == "p9sk1") + doms = hd tl subf :: doms; + } + if(doms == nil) + return "p9any: unsupported protocol"; + if(Debug){ + for(l := doms; l != nil; l = tl l) + sys->print("dom: %q\n", hd l); + } + r := array of byte ("p9sk1 "+hd doms); + buf[0:] = r; + buf[len r] = byte 0; + io.write(buf, len r + 1); + if(version == 2){ + b := io.readn(3); + if(b == nil || b[0] != byte 'O' || b[1] != byte 'K' || b[2] != byte 0) + return "p9any: AS protocol botch: not OK"; + if(Debug) + sys->print("OK\n"); + } + return p9sk1client(io, hd doms); +} + +#p9sk1: +# C->S: nonce-C +# S->C: nonce-S, uid-S, domain-S +# C->A: nonce-S, uid-S, domain-S, uid-C, factotum-C +# A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n} +# C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter} +# S->C: Kn{nonce-C, counter} + +#asserts that uid-S and uid-C share new secret Kn +#increment the counter to reuse the ticket. + +p9sk1client(io: ref IO, udom: string): string +{ + + # C->S: nonce-C + cchal := array[CHALLEN] of byte; + memrandom(cchal, CHALLEN); + if(io.write(cchal, len cchal) != len cchal) + return sys->sprint("p9sk1: can't write cchal: %r"); + + # S->C: nonce-S, uid-S, domain-S + trbuf := io.readn(TICKREQLEN); + if(trbuf == nil) + return sys->sprint("p9sk1: can't read ticketreq: %r"); + + (nil, tr) := Ticketreq.unpack(trbuf); + if(tr == nil) + return "p9sk1: can't unpack ticket request"; + if(Debug) + sys->print("ticketreq: type=%d authid=%q authdom=%q chal= hostid=%q uid=%q\n", + tr.rtype, tr.authid, tr.authdom, tr.hostid, tr.uid); + + (mykey, diag) := io.findkey(nil, sys->sprint("dom=%q proto=p9sk1 user? !password?", udom)); + if(mykey == nil) + return "can't find key: "+diag; + ukey: array of byte; + if((a := authio->lookattrval(mykey.secrets, "!hex")) != nil){ + ukey = base16->dec(a); + if(len ukey != DESKEYLEN) + return "p9sk1: invalid !hex key"; + }else if((a = authio->lookattrval(mykey.secrets, "!password")) != nil) + ukey = auth9->passtokey(a); + else + return "no !password (or !hex) in key"; + + # A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n} + user := authio->lookattrval(mykey.attrs, "user"); + if(user == nil) + user = authio->user(); # shouldn't happen + tr.rtype = Auth9->AuthTreq; + tr.hostid = user; + tr.uid = tr.hostid; # not speaking for anyone else + (tick, serverbits) := getastickets(tr, ukey); + if(tick == nil) + return sys->sprint("p9sk1: getasticket failed: %r"); + if(tick.num != Auth9->AuthTc) + return "p9sk1: getasticket: failed: wrong key?"; + if(Debug) + sys->print("ticket: num=%d chal= cuid=%q suid=%q key=\n", tick.num, tick.cuid, tick.suid); + + # C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter} + ar := ref Authenticator; + ar.num = Auth9->AuthAc; + ar.chal = tick.chal; + ar.id = 0; + obuf := array[TICKETLEN+AUTHENTLEN] of byte; + obuf[0:] = serverbits; + obuf[TICKETLEN:] = ar.pack(tick.key); + if(io.write(obuf, len obuf) != len obuf) + return "p9sk1: error writing authenticator: %r"; + + # S->C: Kn{nonce-C, counter} + sbuf := io.readn(AUTHENTLEN); + if(sbuf == nil) + return sys->sprint("p9sk1: can't read server's authenticator: %r"); + (nil, ar) = Authenticator.unpack(sbuf, tick.key); + if(ar.num != Auth9->AuthAs || !eqbytes(ar.chal, cchal) || ar.id != 0) + return "invalid authenticator from server"; + + ai := ref Authinfo(tick.cuid, tick.suid, nil, auth9->des56to64(tick.key)); + io.done(ai); + + return nil; +} + +getastickets(tr: ref Ticketreq, key: array of byte): (ref Ticket, array of byte) +{ + afd := authdial(nil, tr.authdom); + if(afd == nil) + return (nil, nil); + return auth9->_asgetticket(afd, tr, key); +} + +# +# where to put the following functions? +# + +csgetvalue(netroot: string, keytag: string, keyval: string, needtag: string): string +{ + cs := "/net/cs"; + if(netroot != nil) + cs = netroot+"/cs"; + fd := sys->open(cs, Sys->ORDWR); # TO DO: choice of root + if(fd == nil) + return nil; + if(sys->fprint(fd, "!%s=%s %s=*", keytag, keyval, needtag) < 0) + return nil; + sys->seek(fd, big 0, 0); + buf := array[1024] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + al := authio->parseline(string buf[0:n]); # assume the conventions match factotum's + for(; al != nil; al = tl al) + if((hd al).name == needtag) + return (hd al).val; + } + return nil; +} + +authdial(netroot: string, dom: string): ref Sys->FD +{ + p: string; + if(dom != nil){ + # look up an auth server in an authentication domain + p = csgetvalue(netroot, "authdom", dom, "auth"); + + # if that didn't work, just try the IP domain + if(p == nil) + p = csgetvalue(netroot, "dom", dom, "auth"); + if(p == nil) + p = "$auth"; # temporary ... + if(p == nil){ + sys->werrstr("no auth server found for "+dom); + return nil; + } + }else + p = "$auth"; # look for one relative to my machine + (nil, conn) := sys->dial(netmkaddr(p, netroot, "ticket"), nil); + return conn.dfd; +} diff --git a/appl/cmd/auth/factotum/proto/pass.b b/appl/cmd/auth/factotum/proto/pass.b new file mode 100644 index 00000000..9c4462b3 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/pass.b @@ -0,0 +1,29 @@ +implement Authproto; + +include "sys.m"; + sys: Sys; + +include "../authio.m"; + authio: Authio; + Attr, IO: import authio; + +init(f: Authio): string +{ + sys = load Sys Sys->PATH; + authio = f; + return nil; +} + +interaction(attrs: list of ref Attr, io: ref Authio->IO): string +{ + (key, err) := io.findkey(attrs, "user? !password?"); + if(key == nil) + return err; + user := authio->lookattrval(key.attrs, "user"); + if(user == nil) + return "unknown user"; + pass := authio->lookattrval(key.secrets, "!password"); + a := sys->aprint("%q %q", user, pass); + io.write(a, len a); + return nil; +} diff --git a/appl/cmd/auth/factotum/rpc.b b/appl/cmd/auth/factotum/rpc.b new file mode 100644 index 00000000..220980a8 --- /dev/null +++ b/appl/cmd/auth/factotum/rpc.b @@ -0,0 +1,68 @@ +implement Rpcio; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Rpcio: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: rpc\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + + file := "/mnt/factotum/rpc"; + if(len args > 1) + file = hd tl args; + rfd := sys->open(file, Sys->ORDWR); + if(rfd == nil){ + sys->fprint(sys->fildes(2), "rpc: can't open %s: %r\n", file); + raise "fail:load"; + } + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + rpc(rfd, s[0:len s-1]); + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +rpc(f: ref Sys->FD, addr: string) +{ + b := array of byte addr; + if(sys->write(f, b, len b) > 0){ + sys->seek(f, big 0, Sys->SEEKSTART); + buf := array[256] of byte; + if((n := sys->read(f, buf, len buf)) > 0) + sys->print("%s\n", string buf[0:n]); + if(n >= 0) + return; + } + sys->print("!%r\n"); +} diff --git a/appl/cmd/auth/getpk.b b/appl/cmd/auth/getpk.b new file mode 100644 index 00000000..24283340 --- /dev/null +++ b/appl/cmd/auth/getpk.b @@ -0,0 +1,83 @@ +implement Getpk; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; +include "keyring.m"; + keyring: Keyring; + +Getpk: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmodule(p: string) +{ + sys->fprint(sys->fildes(2), "getpk: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + if(keyring == nil) + badmodule(Keyring->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + arg->setusage("usage: getpk [-asu] file..."); + aflag := 0; + sflag := 0; + uflag := 0; + while((opt := arg->opt()) != 0){ + case opt { + 's' => + sflag++; + 'a' => + aflag++; + 'u' => + uflag++; + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + multi := len argv > 1; + for(; argv != nil; argv = tl argv){ + info := keyring->readauthinfo(hd argv); + if(info == nil){ + sys->fprint(sys->fildes(2), "getpk: cannot read %s: %r\n", hd argv); + continue; + } + pk := info.mypk; + if(sflag) + pk = info.spk; + s := keyring->pktostr(pk); + if(!aflag) + s = hex(hash(s)); + if(multi) + s = hd argv + ": " + s; + if(uflag) + s += " " + pk.owner; + sys->print("%s\n", s); + } +} + +hash(s: string): array of byte +{ + d := array of byte s; + digest := array[Keyring->SHA1dlen] of byte; + keyring->sha1(d, len d, digest, nil); + return digest; +} + +hex(a: array of byte): string +{ + s := ""; + for(i := 0; i < len a; i++) + s += sys->sprint("%2.2ux", int a[i]); + return s; +} diff --git a/appl/cmd/auth/keyfs.b b/appl/cmd/auth/keyfs.b new file mode 100644 index 00000000..f81c3ee7 --- /dev/null +++ b/appl/cmd/auth/keyfs.b @@ -0,0 +1,806 @@ +implement Keyfs; + +# +# Copyright © 2002,2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + Qid: import Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + AESbsize, AESstate: import kr; + +include "rand.m"; + rand: Rand; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "styxservers.m"; + styxservers: Styxservers; + Fid, Styxserver, Navigator, Navop: import styxservers; + Enotfound, Eperm, Ebadarg, Edot: import styxservers; + +include "arg.m"; + +Keyfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +User: adt +{ + x: int; # table index + name: string; + secret: array of byte; # eg, password hashed by SHA1 + expire: int; # expiration time (epoch seconds) + status: int; + failed: int; # count of failed attempts + path: big; +}; + +Qroot, Quser, Qsecret, Qlog, Qstatus, Qexpire: con iota; +files := array[] of { + (Qsecret, "secret"), + (Qlog, "log"), + (Qstatus, "status"), + (Qexpire, "expire") +}; + +Maxsecret: con 255; +Maxname: con 255; +Maxfail: con 50; +users: array of ref User; +Sok, Sdisabled: con iota; +status := array[] of {Sok => "ok", Sdisabled => "disabled" }; +Never: con 0; # expiry time + +Eremoved: con "user has been removed"; + +pathgen := 0; +keyversion := 0; +user: string; +now: int; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: keyfs [-D] [-m mountpoint] [keyfile]\n"); + raise "fail:usage"; +} + +nomod(s: string) +{ + sys->fprint(sys->fildes(2), "keyfs: can't load %s: %r\n", s); + raise "fail:load"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->NEWPGRP, nil); + kr = load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + styx = load Styx Styx->PATH; + if(styx == nil) + nomod(Styx->PATH); + styxservers = load Styxservers Styxservers->PATH; + if(styxservers == nil) + nomod(Styxservers->PATH); + rand = load Rand Rand->PATH; + if(rand == nil) + nomod(Rand->PATH); + + styx->init(); + styxservers->init(styx); + rand->init(sys->millisec()); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("keyfs [-m mntpt] [-D] [-n nvramfile] [keyfile]"); + mountpt := "/mnt/keys"; + keyfile := "/keydb/keys"; + nvram: string; + while((o := arg->opt()) != 0) + case o { + 'm' => + mountpt = arg->earg(); + 'D' => + styxservers->traceset(1); + 'n' => + nvram = arg->earg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(args != nil) + keyfile = hd args; + + pwd, err: string; + if(nvram != nil){ + pwd = rf(nvram); + if(pwd == nil) + error(sys->sprint("can't read %s: %r", nvram)); + } + if(pwd == nil){ + (pwd, err) = readconsline("Key: ", 1); + if(pwd == nil || err == "exit") + exit; + if(err != nil) + error(sys->sprint("couldn't get key: %s", err)); + (rc, d) := sys->stat(keyfile); + if(rc == -1 || d.length == big 0){ + pwd0 := pwd; + (pwd, err) = readconsline("Confirm key: ", 1); + if(pwd == nil || err == "exit") + exit; + if(pwd != pwd0) + error("key mismatch"); + for(i := 0; i < len pwd0; i++) + pwd0[i] = ' '; # clear it out + } + } + + thekey = hashkey(pwd); + for(i:=0; ipctl(Sys->NEWPGRP|Sys->FORKFD, nil); # immediately avoid sharing keyfd + + readkeys(keyfile); + + user = rf("/dev/user"); + if(user == nil) + user = "keyfs"; + + fds := array[2] of ref Sys->FD; + if(sys->pipe(fds) < 0) + error(sys->sprint("can't create pipe: %r")); + + navops := chan of ref Navop; + spawn navigator(navops); + + (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qroot); + fds[0] = nil; + + pidc := chan of int; + spawn serveloop(tchan, srv, pidc, navops, keyfile); + <-pidc; + + if(sys->mount(fds[1], nil, mountpt, Sys->MREPL|Sys->MCREATE, nil) < 0) + error(sys->sprint("mount on %s failed: %r", mountpt)); +} + +rf(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return nil; + b := array[256] of byte; + n := sys->read(fd, b, len b); + if(n < 0) + return nil; + return string b[0:n]; +} + +quit(err: string) +{ + fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + if(err != nil) + raise "fail:"+err; + exit; +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "keyfs: %s\n", s); + quit("error"); +} + +thekey: array of byte; + +hashkey(s: string): array of byte +{ + key := array of byte s; + skey := array[Keyring->SHA1dlen] of byte; + sha := kr->sha1(array of byte "aescbc file", 11, nil, nil); + kr->sha1(key, len key, skey, sha); + for(i:=0; iprint("HEX="); for(i:=0;iprint("%.2ux", int skey[i]);sys->print("\n");} + return skey[0:AESbsize]; +} + +readconsline(prompt: string, raw: int): (string, string) +{ + fd := sys->open("/dev/cons", Sys->ORDWR); + if(fd == nil) + return (nil, sys->sprint("can't open cons: %r")); + sys->fprint(fd, "%s", prompt); + fdctl: ref Sys->FD; + if(raw){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->fprint(fdctl, "rawon") < 0) + return (nil, sys->sprint("can't open consctl: %r")); + } + line := array[256] of byte; + o := 0; + err: string; + buf := array[1] of byte; + Read: + while((r := sys->read(fd, buf, len buf)) > 0){ + c := int buf[0]; + case c { + 16r7F => + err = "interrupt"; + break Read; + '\b' => + if(o > 0) + o--; + '\n' or '\r' or 16r4 => + break Read; + * => + if(o > len line){ + err = "line too long"; + break Read; + } + line[o++] = byte c; + } + } + sys->fprint(fd, "\n"); + if(r < 0) + err = sys->sprint("can't read cons: %r"); + if(raw) + sys->fprint(fdctl, "rawoff"); + if(err != nil) + return (nil, err); + return (string line[0:o], err); +} + +serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop, keyfile: string) +{ + pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil); + while((gm := <-tchan) != nil){ + now = time(); + pick m := gm { + Readerror => + error(sys->sprint("mount read error: %s", m.error)); + Create => + (c, mode, nil, err) := srv.cancreate(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + case TYPE(c.path) { # parent + Qroot => + if((m.perm & Sys->DMDIR) == 0){ + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + u := findusername(m.name); + if(u != nil){ + srv.reply(ref Rmsg.Error(m.tag, "user already exists")); + continue; + } + if(len m.name > Maxname){ + srv.reply(ref Rmsg.Error(m.tag, "user name too long")); + continue; + } + u = newuser(m.name, nil); + qid := Qid((u.path | big Quser), 0, Sys->QTDIR); + c.open(mode, qid); + writekeys(keyfile); + srv.reply(ref Rmsg.Create(m.tag, qid, srv.iounit())); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + Read => + (c, err) := srv.canread(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + if(c.qtype & Sys->QTDIR){ + srv.read(m); # does readdir + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + case TYPE(c.path) { + Qsecret => + if(u.status != Sok){ + srv.reply(ref Rmsg.Error(m.tag, "user disabled")); + break; + } + if(u.expire < now && u.expire != Never){ + srv.reply(ref Rmsg.Error(m.tag, "user expired")); + break; + } + srv.reply(styxservers->readbytes(m, u.secret)); + Qlog => + srv.reply(styxservers->readstr(m, sys->sprint("%d", u.failed))); + Qstatus => + s := status[u.status]; + if(u.status == Sok && u.expire != Never && u.expire < now) + s = "expired"; + srv.reply(styxservers->readstr(m, s)); + Qexpire => + s: string; + if(u.expire != Never) + s = sys->sprint("%ud", u.expire); + else + s = "never"; + srv.reply(styxservers->readstr(m, s)); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + } + Write => + (c, merr) := srv.canwrite(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, merr)); + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + Case: + case TYPE(c.path) { + Qsecret => + if(m.offset != big 0 || len m.data > Maxsecret){ + srv.reply(ref Rmsg.Error(m.tag, "illegal write")); + break; + } + u.secret = m.data; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Qexpire => + s := trim(string m.data); + if(s != "never"){ + if(!isnumeric(s)){ + srv.reply(ref Rmsg.Error(m.tag, "illegal expiry time")); + break; + } + u.expire = int s; + }else + u.expire = Never; + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Qstatus => + s := trim(string m.data); + for(i := 0; i < len status; i++) + if(s == status[i]){ + u.status = i; + if(i == Sok) + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + break Case; + } + srv.reply(ref Rmsg.Error(m.tag, "unknown status")); + Qlog => + s := trim(string m.data); + if(s != "good" && s != "ok"){ + if(++u.failed >= Maxfail) + u.status = Sdisabled; + }else + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + } + Remove => + c := srv.getfid(m.fid); + if(c == nil){ + srv.remove(m); # let it diagnose the errors + break; + } + case TYPE(c.path) { + Quser => + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + removeuser(u); + writekeys(keyfile); + srv.delfid(c); + srv.reply(ref Rmsg.Remove(m.tag)); + Qsecret => + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + u.secret = nil; + writekeys(keyfile); + srv.delfid(c); + srv.reply(ref Rmsg.Remove(m.tag)); + * => + srv.remove(m); # let it reject it + } + Wstat => + # rename user + c := srv.getfid(m.fid); + if(c == nil || TYPE(c.path) != Quser){ + srv.default(gm); # let it reject it + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + if((new := m.stat.name) == nil){ + srv.default(gm); + break; + } + if(new == "." || new == ".."){ + srv.reply(ref Rmsg.Error(m.tag, Edot)); + break; + } + if(findusername(new) != nil){ + srv.reply(ref Rmsg.Error(m.tag, "user already exists")); + break; + } + # unhashuser(u); + u.name = new; + # hashuser(u); + writekeys(keyfile); + srv.reply(ref Rmsg.Wstat(m.tag)); + * => + srv.default(gm); + } + } + navops <-= nil; # shut down navigator +} + +trim(s: string): string +{ + (nf, flds) := sys->tokenize(s, " \t\n"); + if(nf == 0) + return nil; + return hd flds; +} + +isnumeric(s: string): int +{ + for(i:=0; i='0' && s[i]<='9')) + return 0; + return i>0; +} + +TYPE(path: big): int +{ + return int path & 16rF; +} + +INDEX(path: big): int +{ + return (int path & 16rFFFF) >> 4; +} + +finduserpath(path: big): ref User +{ + i := INDEX(path); + if(i >= len users || (u := users[i]) == nil || u.path != (path & ~big 16rF)) + return nil; + return u; +} + +findusername(name: string): ref User +{ + for(i := 0; i < len users; i++) + if((u := users[i]) != nil && u.name == name) + return u; + return nil; +} + +newuser(name: string, u: ref User): ref User +{ + for(i := 0; i < len users; i++) + if(users[i] == nil) + break; + if(i >= len users) + users = (array[i+16] of ref User)[0:] = users; + path := big ((pathgen++ << 16) | (i<<4)); + if(u == nil) + u = ref User(i, name, nil, Never, Sok, 0, path); + else{ + u.x = i; + u.path = path; + } + users[i] = u; + return u; +} + +removeuser(u: ref User) +{ + if(u != nil) + users[u.x] = nil; +} + +dirslot(n: int): int +{ + for(i := 0; i < len users; i++){ + u := users[i]; + if(u != nil){ + if(n == 0) + break; + n--; + } + } + return i; +} + +dir(qid: Sys->Qid, name: string, length: big, perm: int): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid = qid; + if(qid.qtype & Sys->QTDIR) + perm |= Sys->DMDIR; + d.mode = perm; + d.name = name; + d.uid = user; + d.gid = user; + d.length = length; + d.atime = now; + d.mtime = now; + return d; +} + +dirgen(p: big, name: string, u: ref User): (ref Sys->Dir, string) +{ + case t := TYPE(p) { + Qroot => + return (dir(Qid(big Qroot, keyversion,Sys->QTDIR), "/", big 0, 8r755), nil); + Quser => + if(name == nil){ + if(u == nil){ + u = finduserpath(p); + if(u == nil) + return (nil, Enotfound); + } + name = u.name; + } + return (dir(Qid(p,0,Sys->QTDIR), name, big 0, 8r500), nil); # note: unwritable + * => + l := 0; + if(t == Qsecret){ + if(u == nil) + u = finduserpath(p); + if(u != nil) + l = len u.secret; + } + return (dir(Qid(p,0,Sys->QTFILE), name, big l, 8r600), nil); + } +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + Pick: + pick n := m { + Stat => + n.reply <-= dirgen(n.path, nil, nil); + Walk => + case TYPE(n.path) { + Qroot => + if(n.name == ".."){ + n.reply <-= dirgen(n.path, nil, nil); + break; + } + u := findusername(n.name); + if(u == nil){ + n.reply <-= (nil, Enotfound); + break; + } + n.reply <-= dirgen(u.path | big Quser, u.name, u); + Quser => + if(n.name == ".."){ + n.reply <-= dirgen(big Qroot, nil, nil); + break; + } + for(j := 0; j < len files; j++){ + (ftype, name) := files[j]; + if(n.name == name){ + n.reply <-= dirgen((n.path & ~big 16rF) | big ftype, name, nil); + break Pick; + } + } + n.reply <-= (nil, Enotfound); + * => + if(n.name != ".."){ + n.reply <-= (nil, Enotfound); + break; + } + n.reply <-= dirgen((n.path & ~big 16rF) | big Quser, nil, nil); # parent directory + } + Readdir => + case TYPE(n.path) { + Qroot => + for(j := dirslot(n.offset); --n.count >= 0 && j < len users; j++) + if((u := users[j]) != nil) + n.reply <-= dirgen(u.path | big Quser, u.name, u); + n.reply <-= (nil, nil); + Quser => + u := finduserpath(n.path); + if(u == nil){ + n.reply <-= (nil, Eremoved); + break; + } + for(j := n.offset; --n.count >= 0 && j < len files; j++){ + (ftype, name) := files[j]; + n.reply <-= dirgen((n.path & ~big 16rF)|big ftype, name, u); + } + n.reply <-= (nil, nil); + } + } + } +} + +timefd: ref Sys->FD; + +time(): int +{ + if(timefd == nil){ + timefd = sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + return 0; + } + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + t := (big string buf[0:n]) / big 1000000; + return int t; +} + +Checkpat: con "XXXXXXXXXXXXXXXX"; # it's what Plan 9's aescbc uses +Checklen: con len Checkpat; + +Hdrlen: con 1+1+4; + +packedsize(u: ref User): int +{ + return Hdrlen+(1+len array of byte u.name)+(1+len u.secret); +} + +pack(u: ref User): array of byte +{ + a := array[packedsize(u)] of byte; + a[0] = byte u.status; + a[1] = byte u.failed; + a[2] = byte u.expire; + a[3] = byte (u.expire>>8); + a[4] = byte (u.expire>>16); + a[5] = byte (u.expire>>24); + bn := array of byte u.name; + n := len bn; + if(n > 255) + error(sys->sprint("overlong user name: %s", u.name)); # shouldn't happen + a[6] = byte n; + a[7:] = bn; + n += 7; + a[n] = byte len u.secret; + a[n+1:] = u.secret; + return a; +} + +unpack(a: array of byte): (ref User, int) +{ + if(len a < Hdrlen+2) + return (nil, 0); + u := ref User; + u.status = int a[0]; + u.failed = int a[1]; + u.expire = (int a[5] << 24) | (int a[4] << 16) | (int a[3] << 8) | int a[2]; + n := int a[6]; + j := 7+n; + if(j > len a) + return (nil, 0); + u.name = string a[7:j]; + if(j >= len a) + return (nil, 0); + n = int a[j++]; + if(j+n > len a) + return (nil, 0); + if(n > 0){ + u.secret = array[n] of byte; + u.secret[0:] = a[j:j+n]; + } + return (u, j+n); +} + +corrupt(keyfile: string) +{ + error(sys->sprint("%s: incorrect key or corrupt/damaged keyfile", keyfile)); +} + +readkeys(keyfile: string) +{ + fd := sys->open(keyfile, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %s: %r", keyfile)); + (rc, d) := sys->fstat(fd); + if(rc < 0) + error(sys->sprint("can't get status of %s: %r", keyfile)); + length := int d.length; + if(length == 0) + return; + if(length < AESbsize+Checklen) + corrupt(keyfile); + buf := array[length] of byte; + if(sys->read(fd, buf, len buf) != len buf) + error(sys->sprint("can't read %s: %r", keyfile)); + state := kr->aessetup(thekey, buf[0:AESbsize]); + if(state == nil) + error("can't initialise AES"); + kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Decrypt); + if(string buf[length-Checklen:] != Checkpat) + corrupt(keyfile); + length -= Checklen; + for(i := AESbsize; i < length;){ + (u, n) := unpack(buf[i:]); + if(u == nil) + corrupt(keyfile); + newuser(u.name, u); + i += n; + } +} + +writekeys(keyfile: string) +{ + length := 0; + for(i := 0; i < len users; i++) + if((u := users[i]) != nil) + length += packedsize(u); + if(length == 0){ + # leave it empty for clarity + fd := sys->create(keyfile, Sys->OWRITE, 8r600); + if(fd == nil) + error(sys->sprint("can't create %s: %r", keyfile)); + return; + } + length += AESbsize+Checklen; + buf := array[length] of byte; + for(i=0; irand(256); + j := AESbsize; + for(i = 0; i < len users; i++) + if((u = users[i]) != nil){ + a := pack(u); + buf[j:] = a; + j += len a; + } + buf[length-Checklen:] = array of byte Checkpat; + state := kr->aessetup(thekey, buf[0:AESbsize]); + if(state == nil) + error("can't initialise AES"); + kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Encrypt); + fd := sys->create(keyfile, Sys->OWRITE, 8r600); + if(fd == nil) + error(sys->sprint("can't create %s: %r", keyfile)); + if(sys->write(fd, buf, len buf) != len buf) + error(sys->sprint("error writing to %s: %r", keyfile)); +} diff --git a/appl/cmd/auth/keysrv.b b/appl/cmd/auth/keysrv.b new file mode 100644 index 00000000..c7144256 --- /dev/null +++ b/appl/cmd/auth/keysrv.b @@ -0,0 +1,199 @@ +implement Keysrv; + +# +# remote access to keys (currently only to change secret) +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + auth: Auth; + +include "arg.m"; + +keydb := "/mnt/keys"; + +Keysrv: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: keysrv\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + if(sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil) < 0) + err(sys->sprint("can't fork name space: %r")); + + keyfile := "/usr/"+user()+"/keyring/default"; + + arg := load Arg Arg->PATH; + if(arg == nil) + err("can't load Arg"); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'k' => + keyfile = arg->arg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + kr = load Keyring Keyring->PATH; + if(kr == nil) + err("can't load Keyring"); + + auth = load Auth Auth->PATH; + if(auth == nil) + err("can't load Auth"); + auth->init(); + + ai := kr->readauthinfo(keyfile); + if(ai == nil) + err(sys->sprint("can't read server key file %s: %r", keyfile)); + + (fd, id_or_err) := auth->server("sha1" :: "rc4_256" :: nil, ai, sys->fildes(0), 0); + if(fd == nil) + err(sys->sprint("can't authenticate: %s", id_or_err)); + + if(sys->bind("#s", "/mnt/keysrv", Sys->MREPL) < 0) + err(sys->sprint("can't bind #s on /mnt/keysrv: %r")); + srv := sys->file2chan("/mnt/keysrv", "secret"); + if(srv == nil) + err(sys->sprint("can't create file2chan on /mnt/keysrv: %r")); + exitc := chan of int; + spawn worker(srv, id_or_err, exitc); + if(sys->export(fd, "/mnt/keysrv", Sys->EXPWAIT) < 0){ + exitc <-= 1; + err(sys->sprint("can't export %s: %r", "/mnt/keysrv")); + } + exitc <-= 1; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "keysrv: %s\n", s); + raise "fail:error"; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /dev/user: %r")); + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + err(sys->sprint("error reading /dev/user: %r")); + + return string buf[0:n]; +} + +worker(file: ref Sys->FileIO, user: string, exitc: chan of int) +{ + (keydir, secret, err) := getuser(user); + if(keydir == nil || secret == nil){ + if(err == nil) + err = "no existing secret"; # can't change it remotely until set + } + (nil, hash) := hashkey(secret); + for(;;)alt{ + <-exitc => + exit; + (nil, nbytes, fid, rc) := <-file.read => + if(rc == nil) + break; + if(err != nil){ + rc <-= (nil, err); + break; + } + rc <-= (nil, nil); + (nil, data, fid, wc) := <-file.write => + if(wc == nil) + break; + if(err != nil){ + wc <-= (0, err); + break; + } + for(i := 0; i < len data; i++) + if(data[i] == byte ' ') + break; + if(string data[0:i] != hash){ + wc <-= (0, "wrong secret"); + break; + } + if(++i >= len data){ + wc <-= (0, nil); + break; + } + if(len data - i < 8){ + wc <-= (0, "unacceptable secret"); + break; + } + if(putsecret(keydir, data[i:]) < 0){ + wc <-= (0, sys->sprint("can't update secret: %r")); + break; + } + wc <-= (len data, nil); + } +} + +hashkey(a: array of byte): (array of byte, string) +{ + hash := array[Keyring->SHA1dlen] of byte; + kr->sha1(a, len a, hash, nil); + s := ""; + for(i := 0; i < len hash; i++) + s += sys->sprint("%2.2ux", int hash[i]); + return (hash, s); +} + +getuser(id: string): (string, array of byte, string) +{ + (ok, nil) := sys->stat(keydb); + if(ok < 0) + return (nil, nil, sys->sprint("can't stat %s: %r", id)); + dbdir := keydb+"/"+id; + (ok, nil) = sys->stat(dbdir); + if(ok < 0) + return (nil, nil, sys->sprint("user not registered: %s", id)); + fd := sys->open(dbdir+"/secret", Sys->OREAD); + if(fd == nil) + return (nil, nil, sys->sprint("can't open %s/secret: %r", id)); + d: Sys->Dir; + (ok, d) = sys->fstat(fd); + if(ok < 0) + return (nil, nil, sys->sprint("can't stat %s/secret: %r", id)); + l := int d.length; + secret: array of byte; + if(l > 0){ + secret = array[l] of byte; + if(sys->read(fd, secret, len secret) != len secret) + return (nil, nil, sys->sprint("error reading %s/secret: %r", id)); + } + return (dbdir, secret, nil); +} + +putsecret(dir: string, secret: array of byte): int +{ + fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return -1; + return sys->write(fd, secret, len secret); +} diff --git a/appl/cmd/auth/logind.b b/appl/cmd/auth/logind.b new file mode 100644 index 00000000..f9d14616 --- /dev/null +++ b/appl/cmd/auth/logind.b @@ -0,0 +1,244 @@ +implement Logind; + +# +# certification service (signer) +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + ssl: SSL; + +include "daytime.m"; + daytime: Daytime; + +Logind: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +TimeLimit: con 5*60*1000; # five minutes +keydb := "/mnt/keys"; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->open("/dev/cons", sys->OWRITE); + + kr = load Keyring Keyring->PATH; + + ssl = load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + + (err, c) := ssl->connect(sys->fildes(0)); + if(c == nil) + fatal("pushing ssl: " + err); + + # impose time out to ensure dead network connections recovered well before TCP/IP's long time out + + grpid := sys->pctl(Sys->NEWPGRP,nil); + pidc := chan of int; + spawn stalker(pidc, grpid); + tpid := <-pidc; + err = dologin(c); + if(err != nil){ + sys->fprint(stderr, "logind: %s\n", err); + kr->puterror(c.dfd, err); + } + kill(tpid, "kill"); +} + +dologin(c: ref Sys->Connection): string +{ + ivec: array of byte; + + (info, err) := signerkey("/keydb/signerkey"); + if(info == nil) + return "can't read signer's own key: "+err; + + # get user name; ack + s: string; + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return err; + name := s; + kr->putstring(c.dfd, name); + + # get initialization vector + (ivec, err) = kr->getbytearray(c.dfd); + if(err != nil) + return "can't get initialization vector: "+err; + + # lookup password + pw := getsecret(s); + if(pw == nil) + return sys->sprint("no password entry for %s: %r", s); + if(len pw < Keyring->SHA1dlen) + return "bad password for "+s+": not SHA1 hashed?"; + userexp := getexpiry(s); + if(userexp < 0) + return sys->sprint("expiry time for %s: %r", s); + + # generate our random diffie hellman part + bits := info.p.bits(); + r0 := kr->IPint.random(bits/4, bits); + + # generate alpha0 = alpha**r0 mod p + alphar0 := info.alpha.expmod(r0, info.p); + + # start encrypting + pwbuf := array[8] of byte; + for(i := 0; i < 8; i++) + pwbuf[i] = pw[i] ^ pw[8+i]; + for(i = 0; i < 4; i++) + pwbuf[i] ^= pw[16+i]; + for(i = 0; i < 8; i++) + pwbuf[i] ^= ivec[i]; + err = ssl->secret(c, pwbuf, pwbuf); + if(err != nil) + return "can't set ssl secret: "+err; + + if(sys->fprint(c.cfd, "alg rc4") < 0) + return sys->sprint("can't push alg rc4: %r"); + + # send P(alpha**r0 mod p) + if(kr->putstring(c.dfd, alphar0.iptob64()) < 0) + return sys->sprint("can't send (alpha**r0 mod p): %r"); + + # stop encrypting + if(sys->fprint(c.cfd, "alg clear") < 0) + return sys->sprint("can't clear alg: %r"); + + # send alpha, p + if(kr->putstring(c.dfd, info.alpha.iptob64()) < 0 || + kr->putstring(c.dfd, info.p.iptob64()) < 0) + return sys->sprint("can't send alpha, p: %r"); + + # get alpha**r1 mod p + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return "can't get alpha**r1 mod p:"+err; + alphar1 := kr->IPint.b64toip(s); + + # compute alpha**(r0*r1) mod p + alphar0r1 := alphar1.expmod(r0, info.p); + + # turn on digesting + secret := alphar0r1.iptobytes(); + err = ssl->secret(c, secret, secret); + if(err != nil) + return "can't set digest secret: "+err; + if(sys->fprint(c.cfd, "alg sha1") < 0) + return sys->sprint("can't push alg sha1: %r"); + + # send our public key + if(kr->putstring(c.dfd, kr->pktostr(kr->sktopk(info.mysk))) < 0) + return sys->sprint("can't send signer's public key: %r"); + + # get his public key + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return "client public key: "+err; + hisPKbuf := array of byte s; + hisPK := kr->strtopk(s); + if(hisPK.owner != name) + return "pk name doesn't match user name"; + + # sign and return + state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + cert := kr->sign(info.mysk, userexp, state, "sha1"); + + if(kr->putstring(c.dfd, kr->certtostr(cert)) < 0) + return sys->sprint("can't send certificate: %r"); + + return nil; +} + +nomod(mod: string) +{ + fatal(sys->sprint("can't load %s: %r",mod)); +} + +fatal(msg: string) +{ + sys->fprint(stderr, "logind: %s\n", msg); + exit; +} + +signerkey(filename: string): (ref Keyring->Authinfo, string) +{ + + info := kr->readauthinfo(filename); + if(info == nil) + return (nil, sys->sprint("readauthinfo %r")); + + # validate signer key + now := daytime->now(); + if(info.cert.exp != 0 && info.cert.exp < now) + return (nil, sys->sprint("signer key expired")); + + return (info, nil); +} + +getsecret(id: string): array of byte +{ + fd := sys->open(sys->sprint("%s/%s/secret", keydb, id), Sys->OREAD); + if(fd == nil) + return nil; + (ok, d) := sys->fstat(fd); + if(ok < 0) + return nil; + a := array[int d.length] of byte; + n := sys->read(fd, a, len a); + if(n < 0) + return nil; + return a[0:n]; +} + +getexpiry(id: string): int +{ + fd := sys->open(sys->sprint("%s/%s/expire", keydb, id), Sys->OREAD); + if(fd == nil) + return -1; + a := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, a, len a); + if(n < 0) + return -1; + s := string a[0:n]; + if(s == "never") + return 0; + if(s == "expired"){ + sys->werrstr(sys->sprint("entry for %s expired", id)); + return -1; + } + return int s; +} + +stalker(pidc: chan of int, killpid: int) +{ + pidc <-= sys->pctl(0, nil); + sys->sleep(TimeLimit); + sys->fprint(stderr, "logind: login timed out\n"); + kill(killpid, "killgrp"); +} + +kill(pid: int, how: string) +{ + fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", how) < 0) + sys->fprint(stderr, "logind: can't %s %d: %r\n", how, pid); +} diff --git a/appl/cmd/auth/mkauthinfo.b b/appl/cmd/auth/mkauthinfo.b new file mode 100644 index 00000000..33feffbb --- /dev/null +++ b/appl/cmd/auth/mkauthinfo.b @@ -0,0 +1,125 @@ +implement Mkauthinfo; + +# +# sign a new key to produce a certificate +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + auth: Auth; + +include "daytime.m"; + daytime: Daytime; + +include "arg.m"; + +Mkauthinfo: module{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->open("/dev/cons", sys->OWRITE); + + kr = load Keyring Keyring->PATH; + + auth = load Auth Auth->PATH; + if(auth == nil) + nomod(Auth->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("auth/mkauthinfo [-k keyspec] [-e ddmmyyyy] user [keyfile]"); + keyspec := "key=default"; + expiry := 0; + while((o := arg->opt()) != 0) + case o { + 'k' => + keyspec = arg->earg(); + 'e' => + expiry = parsedate(arg->earg()); + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + user := hd args; + args = tl args; + dstfile := "/fd/1"; + if(args != nil) + dstfile = hd args; + arg = nil; + + sai := auth->key(keyspec); + if(sai == nil){ + sys->fprint(stderr, "sign: can't find key matching %q: %r\n", keyspec); + raise "fail:no key"; + } + + info := ref Keyring->Authinfo; + info.alpha = sai.alpha; + info.p = sai.p; + info.mysk = kr->genSKfromPK(sai.spk, user); + info.mypk = kr->sktopk(info.mysk); + info.spk = sai.mypk; + pkbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(pkbuf, len pkbuf, nil, nil); + info.cert = kr->sign(sai.mysk, expiry, state, "sha1"); + if(kr->writeauthinfo("/fd/1", info) < 0){ + sys->fprint(stderr, "sign: error writing certificate: %r\n"); + raise "fail:write error"; + } +} + +parsedate(s: string): int +{ + now := daytime->now(); + tm := daytime->local(now); + if(s == "permanent") + return 0; + if(len s != 8) + fatal("bad date format "+s+" (expected DDMMYYYY)"); + tm.mday = int s[0:2]; + if(tm.mday > 31 || tm.mday < 1) + fatal(sys->sprint("bad day of month %d", tm.mday)); + tm.mon = int s[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0) + fatal(sys->sprint("bad month %d\n", tm.mon + 1)); + tm.year = int s[4:8] - 1900; + if(tm.year < 70) + fatal(sys->sprint("bad year %d (year may be no earlier than 1970)", tm.year + 1900)); + expiry := daytime->tm2epoch(tm); + expiry += 60; + if(expiry <= now) + fatal("expiry date has already passed"); + return expiry; +} + +nomod(mod: string) +{ + fatal(sys->sprint("can't load %s: %r",mod)); +} + +fatal(msg: string) +{ + sys->fprint(stderr, "mkauthinfo: %s\n", msg); + raise "fail:error"; +} diff --git a/appl/cmd/auth/mkfile b/appl/cmd/auth/mkfile new file mode 100644 index 00000000..112ba66a --- /dev/null +++ b/appl/cmd/auth/mkfile @@ -0,0 +1,38 @@ +<../../../mkconfig + +DIRS=\ + factotum\ + +TARG=\ + aescbc.dis\ + changelogin.dis\ + countersigner.dis\ + convpasswd.dis\ + createsignerkey.dis\ + keyfs.dis\ + keysrv.dis\ + getpk.dis\ + logind.dis\ + mkauthinfo.dis\ + passwd.dis\ + secstore.dis\ + signer.dis\ + verify.dis\ + +SYSMODULES=\ + arg.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + secstore.m\ + string.m\ + styx.m\ + styxservers.m\ + +DISBIN=$ROOT/dis/auth + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/auth/passwd.b b/appl/cmd/auth/passwd.b new file mode 100644 index 00000000..d10b5c95 --- /dev/null +++ b/appl/cmd/auth/passwd.b @@ -0,0 +1,290 @@ +implement Passwd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + auth: Auth; + +include "arg.m"; + +Passwd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; +keysrv := "/mnt/keysrv"; +signer := "$SIGNER"; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: passwd [-u user] [-s signer] [keyfile]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + kr = load Keyring Keyring->PATH; + if(kr == nil) + noload(Keyring->PATH); + auth = load Auth Auth->PATH; + if(auth == nil) + noload(Auth->PATH); + auth->init(); + + keyfile, id: string; + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 's' => + signer = arg->arg(); + 'u' => + id = arg->arg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(args == nil) + args = "default" :: nil; + + if(id == nil) + id= user(); + + if(args != nil) + keyfile = hd args; + else + keyfile = "default"; + if(len keyfile > 0 && keyfile[0] != '/') + keyfile = "/usr/" + id + "/keyring/" + keyfile; + + ai := kr->readauthinfo(keyfile); + if(ai == nil) + err(sys->sprint("can't read certificate from %s: %r", keyfile)); +sys->print("key owner: %s\n", ai.mypk.owner); + + sys->pctl(Sys->FORKNS|Sys->FORKFD, nil); + remid := mountsrv(ai); + + # get password + ok: int; + secret: array of byte; + oldhash: array of byte; + word: string; + for(;;){ + sys->print("Inferno secret: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word == nil) + exit; + secret = array of byte word; + (nil, s) := hashkey(secret); + for(i := 0; i < len word; i++) + word[i] = ' '; + oldhash = array of byte s; + e := putsecret(oldhash, nil); + if(e != "wrong secret"){ + if(e == nil) + break; + err(e); + } + sys->fprint(stderr, "!wrong secret\n"); + } + newsecret: array of byte; + for(;;){ + for(;;){ + sys->print("new secret [default = don't change]: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok) + exit; + if(word == "" && secret != nil) + break; + if(len word >= 8) + break; + sys->print("!secret must be at least 8 characters\n"); + } + if(word != ""){ + # confirm password change + word1 := word; + sys->print("confirm: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word != word1){ + sys->fprint(stderr, "!entries didn't match\n"); + continue; + } + # TO DO... + #pwbuf := array of byte word; + #newsecret = array[Keyring->SHA1dlen] of byte; + #kr->sha1(pwbuf, len pwbuf, newsecret, nil); + newsecret = array of byte word; + } + if(!eq(newsecret, secret)){ + if((e := putsecret(oldhash, newsecret)) != nil){ + sys->fprint(stderr, "passwd: can't update secret for %s: %s\n", id, e); + continue; + } + } + break; + } +} + +noload(s: string) +{ + err(sys->sprint("can't load %s: %r", s)); +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "passwd: %s\n", s); + raise "fail:error"; +} + +mountsrv(ai: ref Keyring->Authinfo): string +{ + (rc, c) := sys->dial(netmkaddr(signer, "net", "infkey"), nil); + if(rc < 0) + err(sys->sprint("can't dial %s: %r", signer)); + (fd, id_or_err) := auth->client("sha1/rc4_256", ai, c.dfd); + if(fd == nil) + err(sys->sprint("can't authenticate with %s: %r", signer)); + if(sys->mount(fd, nil, keysrv, Sys->MREPL, nil) < 0) + err(sys->sprint("can't mount %s on %s: %r", signer, keysrv)); + return id_or_err; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /dev/user: %r")); + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + err(sys->sprint("error reading /dev/user: %r")); + + return string buf[0:n]; +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +hashkey(a: array of byte): (array of byte, string) +{ + hash := array[Keyring->SHA1dlen] of byte; + kr->sha1(a, len a, hash, nil); + s := ""; + for(i := 0; i < len hash; i++) + s += sys->sprint("%2.2ux", int hash[i]); + return (hash, s); +} + +putsecret(oldhash: array of byte, secret: array of byte): string +{ + fd := sys->create(keysrv+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return sys->sprint("%r"); + n := len oldhash; + if(secret != nil) + n += 1 + len secret; + buf := array[n] of byte; + buf[0:] = oldhash; + if(secret != nil){ + buf[len oldhash] = byte ' '; + buf[len oldhash+1:] = secret; + } + if(sys->write(fd, buf, len buf) < 0) + return sys->sprint("%r"); + return nil; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +readline(io: ref Sys->FD, mode: string): (int, string) +{ + r : int; + line : string; + buf := array[8192] of byte; + fdctl : ref Sys->FD; + rawoff := array of byte "rawoff"; + + if(mode == "rawon"){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){ + sys->fprint(stderr, "unable to change console mode"); + return (0,nil); + } + } + + line = ""; + for(;;) { + r = sys->read(io, buf, len buf); + if(r <= 0){ + sys->fprint(stderr, "error read from console mode"); + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + return (0, nil); + } + + line += string buf[0:r]; + if ((len line >= 1) && (line[(len line)-1] == '\n')){ + if(mode == "rawon"){ + r = sys->write(stdout,array of byte "\n",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + break; + } + else { + if(mode == "rawon"){ + #r = sys->write(stdout, array of byte "*",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + } + } + + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + + return (1, line[0:len line - 1]); +} diff --git a/appl/cmd/auth/secstore.b b/appl/cmd/auth/secstore.b new file mode 100644 index 00000000..5a63b78d --- /dev/null +++ b/appl/cmd/auth/secstore.b @@ -0,0 +1,317 @@ +implement Secstorec; + +# +# interact with the Plan 9 secstore +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "secstore.m"; + secstore: Secstore; + +include "arg.m"; + +Secstorec: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Maxfilesize: con 128*1024; + +stderr: ref Sys->FD; +conn: ref Sys->Connection; +seckey: array of byte; +filekey: array of byte; +file: array of byte; +verbose := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + secstore = load Secstore Secstore->PATH; + + sys->pctl(Sys->FORKFD, nil); + stderr = sys->fildes(2); + secstore->init(); + secstore->privacy(); + + addr := "net!$auth!secstore"; + user := readfile("/dev/user"); + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("auth/secstore [-iv] [-k key] [-p pin] [-s net!server!secstore] [-u user] [{drptx} file ...]"); + iflag := 0; + pass, pin: string; + while((o := arg->opt()) != 0) + case o { + 'i' => iflag = 1; + 'k' => pass = arg->earg(); + 'v' => verbose = 1; + 's' => addr = arg->earg(); + 'u' => user = arg->earg(); + 'p' => pin = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + op := -1; + if(args != nil){ + if(len hd args != 1) + arg->usage(); + op = (hd args)[0]; + args = tl args; + case op { + 'd' or 'r' or 'p' or 'x' => + if(args == nil) + arg->usage(); + 't' => + ; + * => + arg->usage(); + } + } + arg = nil; + + if(iflag){ + buf := array[Secstore->Maxmsg] of byte; + stdin := sys->fildes(0); + for(nr := 0; nr < len buf && (n := sys->read(stdin, buf, len buf-nr)) > 0;) + nr += n; + s := string buf[0:nr]; + secstore->erasekey(buf[0:nr]); + (nf, flds) := sys->tokenize(s, "\n"); + for(i := 0; i < len s; i++) + s[i] = 0; + if(nf < 1) + error("no password on standard input"); + pass = hd flds; + if(nf > 1) + pin = hd tl flds; + } + conn: ref Sys->Connection; +Auth: + for(;;){ + if(!iflag) + pass = readpassword("secstore password"); + if(pass == nil) + exit; + erase(); + seckey = secstore->mkseckey(pass); + filekey = secstore->mkfilekey(pass); + for(i := 0; i < len pass; i++) + pass[i] = 0; # clear it + conn = secstore->dial(netmkaddr(addr, "net", "secstore")); + if(conn == nil) + error(sys->sprint("can't connect to secstore: %r")); + (srvname, diag) := secstore->auth(conn, user, seckey); + if(srvname == nil){ + secstore->bye(conn); + sys->fprint(stderr, "secstore: authentication failed: %s\n", diag); + if(iflag) + raise "fail:auth"; + continue; + } + case diag { + "" => + if(verbose) + sys->fprint(stderr, "server: %s\n", srvname); + secstore->erasekey(seckey); + seckey = nil; + break Auth; + "need pin" => + if(!iflag){ + pin = readpassword("STA PIN+SecureID"); + if(len pin == 0){ + sys->fprint(stderr, "cancelled"); + exit; + } + }else if(pin == nil) + raise "fail:no pin"; + if(secstore->sendpin(conn, pin) < 0){ + sys->fprint(stderr, "secstore: pin rejected: %r\n"); + if(iflag) + raise "fail:bad pin"; + continue; + } + } + } + if(op == 't'){ + erase(); # no longer need the keys + entries := secstore->files(conn); + for(; entries != nil; entries = tl entries){ + (name, size, date, hash, nil) := hd entries; + if(args != nil){ + for(l := args; l != nil; l = tl l) + if((hd args) == name) + break; + if(args == nil) + continue; + } + if(verbose) + sys->print("%-14q %10d %s %s\n", name, size, date, hash); + else + sys->print("%q\n", name); + } + exit; + } + for(; args != nil; args = tl args){ + fname := hd args; + case op { + 'd' => + checkname(fname, 1); + if(secstore->remove(conn, fname) < 0) + error(sys->sprint("can't remove %q: %r", fname)); + verb('d', fname); + 'p' => + checkname(fname, 1); + file = getfile(conn, fname, filekey); + lines := secstore->lines(file); + lno := 1; + for(; lines != nil; lines = tl lines){ + l := hd lines; + if(sys->write(sys->fildes(1), l, len l) != len l) + sys->fprint(sys->fildes(2), "secstore (%s:%d): %r\n", fname, lno); + lno++; + } + secstore->erasekey(file); + file = nil; + verb('p', fname); + 'x' => + checkname(fname, 1); + file = getfile(conn, fname, filekey); + ofd := sys->create(fname, Sys->OWRITE, 8r600); + if(ofd == nil) + error(sys->sprint("can't create %q: %r", fname)); + if(sys->write(ofd, file, len file) != len file) + error(sys->sprint("error writing to %q: %r", fname)); + secstore->erasekey(file); + file = nil; + verb('x', fname); + 'r' or * => + error(sys->sprint("op %c not implemented", op)); + } + } + erase(); +} + +checkname(s: string, noslash: int): string +{ + tail := s; + for(i := 0; i < len s; i++){ + if(s[i] == '/'){ + if(noslash) + break; + tail = s[i+1:]; + } + if(s[i] == '\n' || s[i] <= ' ') + break; + } + if(s == nil || tail == nil || i < len s || s == "..") + error(sys->sprint("can't use %q as a secstore file name", s)); # server checks as well, of course + return tail; +} + +verb(op: int, n: string) +{ + if(verbose) + sys->fprint(stderr, "%c %q\n", op, n); +} + +getfile(conn: ref Sys->Connection, fname: string, key: array of byte): array of byte +{ + f := secstore->getfile(conn, fname, 0); + if(f == nil) + error(sys->sprint("can't fetch %q: %r", fname)); + if(fname != "."){ + f = secstore->decrypt(f, key); + if(f == nil) + error(sys->sprint("can't decrypt %q: %r", fname)); + } + return f; +} + +erase() +{ + if(secstore != nil){ + secstore->erasekey(seckey); + secstore->erasekey(filekey); + secstore->erasekey(file); + } +} + +error(s: string) +{ + erase(); + sys->fprint(stderr, "secstore: %s\n", s); + raise "fail:error"; +} + +readpassword(prompt: string): string +{ + cons := sys->open("/dev/cons", Sys->ORDWR); + if(cons == nil) + return nil; + stdin := bufio->fopen(cons, Sys->OREAD); + if(stdin == nil) + return nil; + cfd := sys->open("/dev/consctl", Sys->OWRITE); + if (cfd == nil || sys->fprint(cfd, "rawon") <= 0) + sys->fprint(stderr, "secstore: warning: cannot hide typed password\n"); +L: + for(;;){ + sys->fprint(cons, "%s: ", prompt); + s := ""; + while ((c := stdin.getc()) >= 0){ + case c { + '\n' or ('d'&8r037) => + sys->fprint(cons, "\n"); + return s; + '\b' or 8r177 => + if(len s > 0) + s = s[0:len s - 1]; + 'u' & 8r037 => + sys->fprint(cons, "\n"); + continue L; + * => + s[len s] = c; + } + } + break; + } + return nil; +} + +readfile(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return ""; + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return ""; + return string buf[0:n]; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auth/signer.b b/appl/cmd/auth/signer.b new file mode 100644 index 00000000..b3f4669d --- /dev/null +++ b/appl/cmd/auth/signer.b @@ -0,0 +1,132 @@ +implement Signer; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + random: Random; + +Signer: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +# size in bits of modulus for public keys +PKmodlen: con 512; + +# size in bits of modulus for diffie hellman +DHmodlen: con 512; + +stderr, stdin, stdout: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + random = load Random Random->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "signer: no key database\n"); + raise "fail:no keydb"; + } + + err := sign(); + if(err != nil){ + sys->fprint(stderr, "signer: %s\n", err); + raise "fail:error"; + } +} + +sign(): string +{ + info := signerkey("signerkey"); + if(info == nil) + return "can't read key"; + + # send public part to client + mypkbuf := array of byte kr->pktostr(kr->sktopk(info.mysk)); + kr->sendmsg(stdout, mypkbuf, len mypkbuf); + alphabuf := array of byte info.alpha.iptob64(); + kr->sendmsg(stdout, alphabuf, len alphabuf); + pbuf := array of byte info.p.iptob64(); + kr->sendmsg(stdout, pbuf, len pbuf); + + # get client's public key + hisPKbuf := kr->getmsg(stdin); + if(hisPKbuf == nil) + return "caller hung up"; + hisPK := kr->strtopk(string hisPKbuf); + if(hisPK == nil) + return "illegal caller PK"; + + # hash, sign, and blind + state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + cert := kr->sign(info.mysk, 0, state, "sha1"); + + # sanity clause + state = kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + if(kr->verify(info.mypk, cert, state) == 0) + return "bad signer certificate"; + + certbuf := array of byte kr->certtostr(cert); + blind := random->randombuf(random->ReallyRandom, len certbuf); + for(i := 0; i < len blind; i++) + certbuf[i] = certbuf[i] ^ blind[i]; + + # sum PKs and blinded certificate + state = kr->md5(mypkbuf, len mypkbuf, nil, nil); + kr->md5(hisPKbuf, len hisPKbuf, nil, state); + digest := array[Keyring->MD5dlen] of byte; + kr->md5(certbuf, len certbuf, digest, state); + + # save sum and blinded cert in a file + file := "signed/"+hisPK.owner; + fd := sys->create(file, Sys->OWRITE, 8r600); + if(fd == nil) + return "can't create "+file+sys->sprint(": %r"); + if(kr->sendmsg(fd, blind, len blind) < 0 || + kr->sendmsg(fd, digest, len digest) < 0){ + sys->remove(file); + return "can't write "+file+sys->sprint(": %r"); + } + + # send blinded cert to client + kr->sendmsg(stdout, certbuf, len certbuf); + + return nil; +} + +signerkey(filename: string): ref Keyring->Authinfo +{ + info := kr->readauthinfo(filename); + if(info != nil) + return info; + + # generate a local key + info = ref Keyring->Authinfo; + info.mysk = kr->genSK("elgamal", "*", PKmodlen); + info.mypk = kr->sktopk(info.mysk); + info.spk = kr->sktopk(info.mysk); + myPKbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(myPKbuf, len myPKbuf, nil, nil); + info.cert = kr->sign(info.mysk, 0, state, "sha1"); + (info.alpha, info.p) = kr->dhparams(DHmodlen); + + if(kr->writeauthinfo(filename, info) < 0){ + sys->fprint(stderr, "can't write signerkey file: %r\n"); + return nil; + } + + return info; +} diff --git a/appl/cmd/auth/verify.b b/appl/cmd/auth/verify.b new file mode 100644 index 00000000..d829a76c --- /dev/null +++ b/appl/cmd/auth/verify.b @@ -0,0 +1,85 @@ +implement Verify; + +include "sys.m"; + sys: Sys; + +include "keyring.m"; + kr: Keyring; + +include "draw.m"; + +Verify: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin: ref Sys->FD; + +pro := array[] of { + "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", + "hotel", "india", "juliet", "kilo", "lima", "mike", "nancy", "oscar", + "papa", "quebec", "romeo", "sierra", "tango", "uniform", + "victor", "whisky", "xray", "yankee", "zulu" +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stderr = sys->fildes(2); + + if(args != nil) + args = tl args; + if(args == nil){ + sys->fprint(stderr, "usage: verify boxid\n"); + raise "fail:usage"; + } + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "signer: no key database\n"); + raise "fail:no keydb"; + } + + boxid := hd args; + file := "signed/"+boxid; + fd := sys->open(file, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "signer: can't open %s: %r\n", file); + raise "fail:no certificate"; + } + certbuf := kr->getmsg(fd); + digest := kr->getmsg(fd); + if(digest == nil || certbuf == nil){ + sys->fprint(stderr, "signer: can't read %s: %r\n", file); + raise "fail:bad certificate"; + } + + s: string; + for(i := 0; i < len digest; i++){ + s = s + (string (2*i)) + ": " + pro[((int digest[i])>>4)%len pro] + "\t"; + s = s + (string (2*i+1)) + ": " + pro[(int digest[i])%len pro] + "\n"; + } + + sys->print("%s\naccept (y or n)? ", s); + buf := array[5] of byte; + n := sys->read(stdin, buf, len buf); + if(n < 1 || buf[0] != byte 'y'){ + sys->print("\nrejected\n"); + raise "fail:rejected"; + } + sys->print("\naccepted\n"); + + nfile := "countersigned/"+boxid; + fd = sys->create(nfile, Sys->OWRITE, 8r600); + if(fd == nil){ + sys->fprint(stderr, "signer: can't create %s: %r\n", nfile); + raise "fail:create"; + } + if(kr->sendmsg(fd, certbuf, len certbuf) < 0){ + sys->fprint(stderr, "signer: can't write %s: %r\n", nfile); + raise "fail:write"; + } +} diff --git a/appl/cmd/auxi/cpuslave.b b/appl/cmd/auxi/cpuslave.b new file mode 100644 index 00000000..66b409ac --- /dev/null +++ b/appl/cmd/auxi/cpuslave.b @@ -0,0 +1,79 @@ +implement CPUslave; + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Context, Display, Screen: import draw; +include "arg.m"; + +include "sh.m"; + +stderr: ref Sys->FD; + +CPUslave: module +{ + init: fn(ctxt: ref Context, args: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: cpuslave [-s screenid] command args\n"); + raise "fail:usage"; +} + +init(nil: ref Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + draw = load Draw Draw->PATH; + + arg := load Arg Arg->PATH; + if (arg == nil) { + sys->fprint(stderr, "cpuslave: cannot load %s: %r\n", Arg->PATH); + raise "fail:bad module"; + } + screenid := -1; + arg->init(args); + while ((opt := arg->opt()) != 0) { + if (opt != 's' || (a := arg->arg()) == nil) + usage(); + screenid = int a; + } + args = arg->argv(); + if(args == nil) + usage(); + + file := hd args + ".dis"; + cmd := load Command file; + if(cmd == nil) + cmd = load Command "/dis/"+file; + if(cmd == nil){ + sys->fprint(stderr, "cpuslave: can't load %s: %r\n", hd args); + raise "fail:bad command"; + } + + ctxt: ref Context; + if (screenid >= 0) { + display := Display.allocate(nil); + if(display == nil){ + sys->fprint(stderr, "cpuslave: can't initialize display: %r\n"); + raise "fail:no display"; + } + + screen: ref Screen; + if(screenid >= 0){ + screen = display.publicscreen(screenid); + if(screen == nil){ + sys->fprint(stderr, "cpuslave: cannot access screen %d: %r\n", screenid); + raise "fail:bad screen"; + } + } + + ctxt = ref Context; + ctxt.screen = screen; + ctxt.display = display; + } + + spawn cmd->init(ctxt, args); +} diff --git a/appl/cmd/auxi/digest.b b/appl/cmd/auxi/digest.b new file mode 100644 index 00000000..108de205 --- /dev/null +++ b/appl/cmd/auxi/digest.b @@ -0,0 +1,91 @@ +implement Digest; + +# +# read a classifier example file and write its digest +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "strokes.m"; + strokes: Strokes; + Classifier, Penpoint, Stroke: import strokes; + readstrokes: Readstrokes; + writestrokes: Writestrokes; + +include "arg.m"; + +Digest: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: digest [file.cl ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + strokes = load Strokes Strokes->PATH; + if(strokes == nil) + nomod(Strokes->PATH); + strokes->init(); + readstrokes = load Readstrokes Readstrokes->PATH; + if(readstrokes == nil) + nomod(Readstrokes->PATH); + readstrokes->init(strokes); + writestrokes = load Writestrokes Writestrokes->PATH; + if(writestrokes == nil) + nomod(Writestrokes->PATH); + writestrokes->init(strokes); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + while((opt := arg->opt()) != 0) + case opt { + * => + usage(); + } + args = arg->argv(); + arg = nil; + + for(; args != nil; args = tl args){ + ofile := file := hd args; + n := len file; + if(n >= 3 && ofile[n-3:] == ".cl") + ofile = ofile[0:n-3]; + ofile += ".clx"; + (err, rec) := readstrokes->read_classifier(hd args, 1, 0); + if(err != nil) + error(sys->sprint("error reading classifier from %s: %s", file, err)); + fd := sys->create(ofile, Sys->OWRITE, 8r666); + if(fd == nil) + error(sys->sprint("can't create %s: %r", file)); + err = writestrokes->write_digest(fd, rec.cnames, rec.dompts); + if(err != nil) + error(sys->sprint("error writing digest to %s: %s", file, err)); + } +} + +nomod(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "digest: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/auxi/fpgaload.b b/appl/cmd/auxi/fpgaload.b new file mode 100644 index 00000000..5c37b80b --- /dev/null +++ b/appl/cmd/auxi/fpgaload.b @@ -0,0 +1,67 @@ +implement Fpgaload; + +include"sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +Fpgaload: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + arg := load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + arg->setusage("fpgaload [-c clock] file.rbf"); + clock := -1; + while((c := arg->opt()) != 0) + case c { + 'c' => + clock = int arg->earg(); + if(clock <= 0) + error("invalid clock value"); + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + fd := sys->open(hd args, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %s: %r", hd args)); + ofd := sys->open("#G/fpgaprog", Sys->OWRITE); + if(ofd == nil) + error(sys->sprint("can't open %s: %r", "#G/fpgaprog")); + a := array[128*1024] of byte; + while((n := sys->read(fd, a, len a)) > 0) + if(sys->write(ofd, a, n) != n) + error(sys->sprint("write error: %r")); + if(n < 0) + error(sys->sprint("read error: %r")); + if(clock >= 0) + setclock(clock); +} + +setclock(n: int) +{ + fd := sys->open("#G/fpgactl", Sys->OWRITE); + if(fd == nil) + error(sys->sprint("can't open %s: %r", "#G/fpgactl")); + if(sys->fprint(fd, "bclk %d", n) < 0) + error(sys->sprint("can't set clock to %d: %r", n)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "fpgaload: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/auxi/mangaload.b b/appl/cmd/auxi/mangaload.b new file mode 100644 index 00000000..380dd22e --- /dev/null +++ b/appl/cmd/auxi/mangaload.b @@ -0,0 +1,362 @@ +implement Mangaload; + +# to do: +# - set arp entry based on /lib/ndb if necessary + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "ip.m"; + ip: IP; + IPaddr: import ip; + +include "timers.m"; + timers: Timers; + Timer: import timers; + +include "arg.m"; + +Mangaload: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# manga parameters +FlashBlocksize: con 16r10000; +FlashSize: con 16r400000; # 4meg for now +FlashUserArea: con 16r3C0000; + +# magic values +FooterOffset: con 16rFFEC; +FooterSig: con 16rA0FFFF9F; # ARM flash library +FileInfosize: con 64; +FileNamesize: con FileInfosize - 3*4; # x, y, z +Packetdatasize: con 1500-28; # ether data less IP + ICMP header +RequestTimeout: con 500; +Probecount: con 10; # query unit every so many packets + +# manga uses extended TFTP ops in ICMP InfoRequest packets +Tftp_Req: con 0; +Tftp_Read: con 1; +Tftp_Write: con 2; +Tftp_Data: con 3; +Tftp_Ack: con 4; +Tftp_Error: con 5; +Tftp_Last: con 6; + +Icmp: adt +{ + ttl: int; # time to live + src: IPaddr; + dst: IPaddr; + ptype: int; + code: int; + id: int; + seq: int; + data: array of byte; + munged: int; # packet received but corrupt + + unpack: fn(b: array of byte): ref Icmp; +}; + +# ICMP packet types +EchoReply: con 0; +Unreachable: con 3; +SrcQuench: con 4; +EchoRequest: con 8; +TimeExceed: con 11; +Timestamp: con 13; +TimestampReply: con 14; +InfoRequest: con 15; +InfoReply: con 16; + +Nmsg: con 32; +Interval: con 1000; # ms + +debug := 0; +flashblock := 1; # never 0, that's the boot firmware +maxfilesize := 8*FlashBlocksize; +flashlim := FlashSize/FlashBlocksize; +loadinitrd := 0; +maxlen := 512*1024; +mypid := 0; +Datablocksize: con 4096; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + timers = load Timers Timers->PATH; + ip = load IP IP->PATH; + ip->init(); + + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("mangaload [-48dr] destination file"); + while((o := arg->opt()) != 0) + case o { + '4' => + flashlim = 4*1024*1024/FlashBlocksize; + '8' => + flashlim = 8*1024*1024/FlashBlocksize; + 'r' => + loadinitrd = 1; + flashblock = 9; + if(flashlim > 4*1024*1024/FlashBlocksize) + maxfilesize = 113*FlashBlocksize; + else + maxfilesize = 50*FlashBlocksize; + 'd' => + debug++; + } + args = arg->argv(); + if(len args != 2) + arg->usage(); + arg = nil; + + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); + + filename := hd tl args; + fd := sys->open(filename, Sys->OREAD); + if(fd == nil){ + sys->fprint(sys->fildes(2), "mangaload: can't open %s: %r\n", filename); + raise "fail:open"; + } + (ok, d) := sys->fstat(fd); + if(ok < 0){ + sys->fprint(sys->fildes(2), "mangaload: can't stat %s: %r\n", filename); + raise "fail:stat"; + } + if(d.length > big maxfilesize){ + sys->fprint(sys->fildes(2), "mangaload: file %s too long (must not exceed %d bytes)\n", + filename, maxfilesize); + raise "fail:size"; + } + filesize := int d.length; + + port := sys->sprint("%d", 16r8695); + addr := netmkaddr(hd args, "icmp", port); + (rok, c) := sys->dial(addr, port); + if(rok < 0){ + sys->fprint(sys->fildes(2), "mangaload: can't dial %s: %r\n", addr); + raise "fail:dial"; + } + + tpid := timers->init(20); + + pids := chan of int; + replies := chan [2] of ref Icmp; + spawn reader(c.dfd, replies, pids); + rpid := <-pids; + + flashoffset := flashblock * FlashBlocksize; + + # file name first + bname := array of byte filename; + l := len bname; + buf := array[Packetdatasize] of byte; + ip->put4(buf, 0, filesize); + ip->put4(buf, 4, l); + buf[8:] = bname; + l += 2*4; + buf[l++] = byte 0; + ip->put4(buf, l, flashoffset); + l += 4; + { + if(send(c.dfd, buf[0:l], Tftp_Write, 0) < 0) + senderr(); + (op, iseq, data) := recv(replies, 400); + sys->print("initial reply: %d %d\n", op, iseq); + if(op != Tftp_Ack){ + why := "no response"; + if(op == Tftp_Error) + why = "manga cannot receive file"; + sys->fprint(sys->fildes(2), "mangaload: %s\n", why); + raise "fail:error"; + } + sys->print("sending %s size %d at address %d (0x%x)\n", filename, filesize, flashoffset, flashoffset); + seq := 1; + nsent := 0; + last := 0; + while((n := sys->read(fd, buf, len buf)) >= 0 && !last){ + last = n != len buf; + nretry := 0; + Retry: + for(;;){ + if(++nsent%10 == 0){ # probe + o = Tftp_Req; + send(c.dfd, array[0] of byte, Tftp_Req, seq); + (op, iseq, data) = recv(replies, 500); + if(debug || op != Tftp_Ack) + sys->print("ack reply: %d %d\n", op, iseq); + if(op == Tftp_Last || op == Tftp_Error){ + if(op == Tftp_Last) + sys->print("timed out\n"); + else + sys->print("error reply\n"); + raise "disaster"; + } + if(debug) + sys->print("ok\n"); + continue Retry; + } + send(c.dfd, buf[0:n], Tftp_Data, seq); + (op, iseq, data) = recv(replies, 40); + case op { + Tftp_Error => + sys->fprint(sys->fildes(2), "mangaload: manga refused data\n"); + raise "disaster"; + Tftp_Ack => + if(seq == iseq){ + seq++; + break Retry; + } + sys->print("sequence error: rcvd %d expected %d\n", iseq, seq); + if(iseq > seq){ + sys->print("unrecoverable sequence error\n"); + send(c.dfd, array[0] of byte, Tftp_Data, ++seq); # stop manga + raise "disaster"; + } + # resend + sys->seek(fd, -big ((seq-iseq)*len buf), 1); + seq = iseq; + Tftp_Last => + seq++; + break Retry; # timeout ok: manga doesn't usually reply unless packet lost + } + } + } + }exception{ + * => + ; + } + kill(rpid); + kill(tpid); + sys->print("ok?\n"); +} + +kill(pid: int) +{ + if(pid) + sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill"); +} + +senderr() +{ + sys->fprint(sys->fildes(2), "mangaload: icmp write failed: %r\n"); + raise "disaster"; +} + +send(fd: ref Sys->FD, data: array of byte, op: int, seq: int): int +{ + buf := array[64*1024+512] of {* => byte 0}; + buf[Odata:] = data; + ip->put2(buf, Oseq, seq); + buf[Otype] = byte InfoRequest; + buf[Ocode] = byte op; + if(sys->write(fd, buf, Odata+len data) < Odata+len data) + return -1; + if(debug) + sys->print("sent op=%d seq=%d ld=%d\n", op, seq, len data); + return 0; +} + +flush(input: chan of ref Icmp) +{ + for(;;)alt{ + <-input => + ; + * => + return; + } +} + +recv(input: chan of ref Icmp, msec: int): (int, int, array of byte) +{ + t := Timer.start(msec); + alt{ + <-t.timeout => + return (Tftp_Last, 0, nil); + ic := <-input => + t.stop(); + if(ic.ptype == InfoReply) + return (ic.code, ic.seq, ic.data); + return (Tftp_Last, 0, nil); + } +} + +reader(fd: ref Sys->FD, out: chan of ref Icmp, pid: chan of int) +{ + pid <-= sys->pctl(0, nil); + for(;;){ + buf := array[64*1024+512] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0){ + if(n == 0) + sys->werrstr("unexpected eof"); + break; + } + ic := Icmp.unpack(buf[0:n]); + if(ic != nil){ + if(debug) + sys->print("recv type=%d op=%d seq=%d id=%d\n", ic.ptype, ic.code, ic.seq, ic.id); + out <-= ic; + }else + sys->fprint(sys->fildes(2), "mangaload: corrupt icmp packet rcvd\n"); + } + sys->print("read: %r\n"); + out <-= nil; +} + +# IP and ICMP packet header +Ovihl: con 0; +Otos: con 1; +Olength: con 2; +Oid: con Olength+2; +Ofrag: con Oid+2; +Ottl: con Ofrag+2; +Oproto: con Ottl+1; +Oipcksum: con Oproto+1; +Osrc: con Oipcksum+2; +Odst: con Osrc+4; +Otype: con Odst+4; +Ocode: con Otype+1; +Ocksum: con Ocode+1; +Oicmpid: con Ocksum+2; +Oseq: con Oicmpid+2; +Odata: con Oseq+2; + +Icmp.unpack(b: array of byte): ref Icmp +{ + if(len b < Odata) + return nil; + ic := ref Icmp; + ic.ttl = int b[Ottl]; + ic.src = IPaddr.newv4(b[Osrc:]); + ic.dst = IPaddr.newv4(b[Odst:]); + ic.ptype = int b[Otype]; + ic.code = int b[Ocode]; + ic.seq = ip->get2(b, Oseq); + ic.id = ip->get2(b, Oicmpid); + ic.munged = 0; + if(len b > Odata) + ic.data = b[Odata:]; + return ic; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auxi/mkfile b/appl/cmd/auxi/mkfile new file mode 100644 index 00000000..6d8dfc88 --- /dev/null +++ b/appl/cmd/auxi/mkfile @@ -0,0 +1,24 @@ +<../../../mkconfig + +TARG=\ + cpuslave.dis\ + digest.dis\ + fpgaload.dis\ + mangaload.dis\ + pcmcia.dis\ + rdbgsrv.dis\ + rstyxd.dis\ + +SYSMODULES=\ + arg.m\ + bufio.m\ + draw.m\ + sh.m\ + string.m\ + strokes.m\ + styx.m\ + sys.m\ + +DISBIN=$ROOT/dis/auxi + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/auxi/pcmcia.b b/appl/cmd/auxi/pcmcia.b new file mode 100644 index 00000000..d5d998b0 --- /dev/null +++ b/appl/cmd/auxi/pcmcia.b @@ -0,0 +1,491 @@ +implement Pcmcia; + +# +# Copyright © 1995-2001 Lucent Technologies Inc. All rights reserved. +# Revisions Copyright © 2001-2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + print, fprint: import sys; + +include "draw.m"; + +Pcmcia: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +End: con 16rFF; + +fd: ref Sys->FD; +stderr: ref Sys->FD; +pos := 0; + +hex := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + if(args != nil) + args = tl args; + if(args != nil && hd args == "-x"){ + hex = 1; + args = tl args; + } + + file := "#y/pcm0attr"; + if(args != nil) + file = hd args; + + fd = sys->open(file, Sys->OREAD); + if(fd == nil) + fatal(sys->sprint("can't open %s: %r", file)); + + for(next := 0; next >= 0;) + next = dtuple(next); +} + +fatal(s: string) +{ + fprint(stderr, "pcmcia: %s\n", s); + raise "fail:error"; +} + +readc(): int +{ + x := array[1] of byte; + sys->seek(fd, big(2*pos), 0); + pos++; + rv := sys->read(fd, x, 1); + if(rv != 1){ + if(rv < 0) + sys->print("readc err: %r\n"); + return -1; + } + v := int x[0]; + if(hex) + print("%2.2ux ", v); + return v; +} + +dtuple(next: int): int +{ + pos = next; + if((ttype := readc()) < 0) + return -1; + if(ttype == End) + return -1; + if((link := readc()) < 0) + return -1; + case ttype { + * => print("unknown tuple type #%2.2ux\n", ttype); + 16r01 => tdevice(ttype, link); + 16r15 => tvers1(ttype, link); + 16r17 => tdevice(ttype, link); + 16r1A => tcfig(ttype, link); + 16r1B => tentry(ttype, link); + } + if(link == End) + next = -1; + else + next = next+2+link; + return next; +} + +speedtab := array[16] of { +0 => 0, +1 => 250, +2 => 200, +3 => 150, +4 => 100, +}; + +mantissa := array[16] of { +1 => 10, +2 => 12, +3 => 13, +4 => 15, +5 => 20, +6 => 25, +7 => 30, +8 => 35, +9 => 40, +10=> 45, +11=> 50, +12=> 55, +13=> 60, +14=> 70, +15=> 80, +}; + +exponent := array[] of { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, +}; + +typetab := array [256] of { +1=> "Masked ROM", +2=> "PROM", +3=> "EPROM", +4=> "EEPROM", +5=> "FLASH", +6=> "SRAM", +7=> "DRAM", +16rD=> "IO+MEM", +* => "Unknown", +}; + +getlong(size: int): int +{ + x := 0; + for(i := 0; i < size; i++){ + if((c := readc()) < 0) + break; + x |= c<<(i*8); + } + return x; +} + +tdevice(dtype: int, tlen: int) +{ + while(tlen > 0){ + if((id := readc()) < 0) + return; + tlen--; + if(id == End) + return; + + speed := id & 16r7; + ns := 0; + if(speed == 16r7){ + if((speed = readc()) < 0) + return; + tlen--; + if(speed & 16r80){ + if((aespeed := readc()) < 0) + return; + ns = 0; + } else + ns = (mantissa[(speed>>3)&16rF]*exponent[speed&7])/10; + } else + ns = speedtab[speed]; + + ttype := id>>4; + if(ttype == 16rE){ + if((ttype = readc()) < 0) + return; + tlen--; + } + tname := typetab[ttype]; + if(tname == nil) + tname = "unknown"; + + if((size := readc()) < 0) + return; + tlen--; + bytes := ((size>>3)+1) * 512 * (1<<(2*(size&16r7))); + + ttname := "attr device"; + if(dtype == 1) + ttname = "device"; + print("%s %d bytes of %dns %s\n", ttname, bytes, ns, tname); + } +} + +tvers1(nil: int, tlen: int) +{ + if((major := readc()) < 0) + return; + tlen--; + if((minor := readc()) < 0) + return; + tlen--; + print("version %d.%d\n", major, minor); + while(tlen > 0){ + s := ""; + while(tlen > 0){ + if((c := readc()) < 0) + return; + tlen--; + if(c == 0) + break; + if(c == End){ + if(s != "") + print("\t%s\n", s); + return; + } + s[len s] = c; + } + print("\t%s\n", s); + } +} + +tcfig(nil: int, nil: int) +{ + if((size := readc()) < 0) + return; + rasize := (size&16r3) + 1; + rmsize := ((size>>2)&16rf) + 1; + if((last := readc()) < 0) + return; + caddr := getlong(rasize); + cregs := getlong(rmsize); + + print("configuration registers at"); + for(i := 0; i < 16; i++) + if((1< "memory", +1 => "I/O", +4 => "Custom 0", +5 => "Custom 1", +6 => "Custom 2", +7 => "Custom 3", +* => "unknown" +}; + +vexp := array[8] of { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; +vmant := array[16] of { + 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90, +}; + +volt(name: string) +{ + if((c := readc()) < 0) + return; + exp := vexp[c&16r7]; + microv := vmant[(c>>3)&16rf]*exp; + while(c & 16r80){ + if((c = readc()) < 0) + return; + case c { + 16r7d => + break; # high impedence when sleeping + 16r7e or 16r7f => + microv = 0; # no connection + * => + exp /= 10; + microv += exp*(c&16r7f); + } + } + print(" V%s %duV", name, microv); +} + +amps(name: string) +{ + if((c := readc()) < 0) + return; + amps := vexp[c&16r7]*vmant[(c>>3)&16rf]; + while(c & 16r80){ + if((c = readc()) < 0) + return; + if(c == 16r7d || c == 16r7e || c == 16r7f) + amps = 0; + } + if(amps >= 1000000) + print(" I%s %dmA", name, amps/100000); + else if(amps >= 1000) + print(" I%s %duA", name, amps/100); + else + print(" I%s %dnA", name, amps*10); +} + +power(name: string) +{ + print("\t%s: ", name); + if((feature := readc()) < 0) + return; + if(feature & 1) + volt("nominal"); + if(feature & 2) + volt("min"); + if(feature & 4) + volt("max"); + if(feature & 8) + amps("static"); + if(feature & 16r10) + amps("avg"); + if(feature & 16r20) + amps("peak"); + if(feature & 16r40) + amps("powerdown"); + print("\n"); +} + +ttiming(name: string, scale: int) +{ + if((unscaled := readc()) < 0) + return; + scaled := (mantissa[(unscaled>>3)&16rf]*exponent[unscaled&7])/10; + scaled = scaled * vexp[scale]; + print("\t%s %dns\n", name, scaled); +} + +timing() +{ + if((c := readc()) < 0) + return; + i := c&16r3; + if(i != 3) + ttiming("max wait", i); + i = (c>>2)&16r7; + if(i != 7) + ttiming("max ready/busy wait", i); + i = (c>>5)&16r7; + if(i != 7) + ttiming("reserved wait", i); +} + +range(asize: int, lsize: int) +{ + address := getlong(asize); + alen := getlong(lsize); + print("\t\t%ux - %ux\n", address, address+alen); +} + +ioaccess := array[] of { + 0 => " no access", + 1 => " 8bit access only", + 2 => " 8bit or 16bit access", + 3 => " selectable 8bit or 8&16bit access", +}; + +iospace(c: int): int +{ + print("\tIO space %d address lines%s\n", c&16r1f, ioaccess[(c>>5)&3]); + if((c & 16r80) == 0) + return -1; + + if((c = readc()) < 0) + return -1; + + for(i := (c&16rf)+1; i; i--) + range((c>>4)&16r3, (c>>6)&16r3); + return 0; +} + +iospaces() +{ + if((c := readc()) < 0) + return; + iospace(c); +} + +irq() +{ + if((c := readc()) < 0) + return; + irqs: int; + if(c & 16r10){ + if((irq1 := readc()) < 0) + return; + if((irq2 := readc()) < 0) + return; + irqs = irq1|(irq2<<8); + } else + irqs = 1<<(c&16rf); + level := ""; + if(c & 16r20) + level = " level"; + pulse := ""; + if(c & 16r40) + pulse = " pulse"; + shared := ""; + if(c & 16r80) + shared = " shared"; + print("\tinterrupts%s%s%s", level, pulse, shared); + for(i := 0; i < 16; i++) + if(irqs & (1<sprint("type %d", i & 16rf); + attrib := ""; + if(i & 16r10) + attrib += " Battery status active"; + if(i & 16r20) + attrib += " Write Protect active"; + if(i & 16r40) + attrib += " Ready/Busy active"; + if(i & 16r80) + attrib += " Memory Wait required"; + print("\t%s device, %s\n", tname, attrib); + } + if((feature := readc()) < 0) + return; + case feature&16r3 { + 1 => + power("Vcc"); + 2 => + power("Vcc"); + power("Vpp"); + 3 => + power("Vcc"); + power("Vpp1"); + power("Vpp2"); + } + if(feature&16r4) + timing(); + if(feature&16r8) + iospaces(); + if(feature&16r10) + irq(); + case (feature>>5)&16r3 { + 1 => + memspace(0, 2, 0); + 2 => + memspace(2, 2, 0); + 3 => + if((c = readc()) < 0) + return; + for(i := 0; i <= (c&16r7); i++) + memspace((c>>5)&16r3, (c>>3)&16r3, c&16r80); + break; + } + if(feature&16r80) + misc(); +} diff --git a/appl/cmd/auxi/rdbgsrv.b b/appl/cmd/auxi/rdbgsrv.b new file mode 100644 index 00000000..2a958eee --- /dev/null +++ b/appl/cmd/auxi/rdbgsrv.b @@ -0,0 +1,222 @@ +implement RDbgSrv; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; + +include "arg.m"; + arg: Arg; + +RDbgSrv: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +debug:= 0; +dev:= "/dev/eia0"; +speed:= 38400; +progname: string; +rpid := 0; +wpid := 0; + +usage() +{ + sys->fprint(stderr(), "Usage: rdbgsrv [-d n] [-s speed] [-f dev] mountpoint\n"); + raise "fail: usage"; +} + +init(nil: ref Draw->Context, av: list of string) +{ + sys = load Sys Sys->PATH; + if(sys == nil) + return; + styx = load Styx Styx->PATH; + if(styx == nil){ + sys->fprint(stderr(), "rdbgsrv: can't load %s; %r\n", Styx->PATH); + raise "fail:load"; + } + arg = load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(stderr(), "rdbgsrv: can't load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + + arg->init(av); + progname = arg->progname(); + while(o := arg->opt()) + case o { + 'd' => + d := arg->arg(); + if(d == nil) + usage(); + debug = int d; + 's' => + s := arg->arg(); + if(s == nil) + usage(); + speed = int s; + 'f' => + s := arg->arg(); + if(s == nil) + usage(); + dev = s; + 'h' => + usage(); + } + + mtpt := arg->arg(); + if(mtpt == nil) + usage(); + + ctl := dev + "ctl"; + cfd := sys->open(ctl, Sys->OWRITE); + if(cfd == nil){ + sys->fprint(stderr(), "%s: can't open %s: %r\n", progname, ctl); + raise "fail: open eia\n"; + } + + sys->fprint(cfd, "b%d", speed); + sys->fprint(cfd, "l8"); + sys->fprint(cfd, "pn"); + sys->fprint(cfd, "s1"); + + (rfd, wfd) := start(dev); + if(rfd == nil){ + sys->fprint(stderr(), "%s: failed to start protocol\n", progname); + raise "fail:proto start"; + } + + fds := array[2] of ref Sys->FD; + + if(sys->pipe(fds) == -1){ + sys->fprint(stderr(), "%s: pipe: %r\n", progname); + raise "fail:no pipe"; + } + + if(debug) + sys->fprint(stderr(), "%s: starting server\n", progname); + + rc := chan of int; + spawn copymsg(fds[1], wfd, "->", rc); + rpid = <-rc; + spawn copymsg(rfd, fds[1], "<-", rc); + wpid = <-rc; + + if(sys->mount(fds[0], nil, mtpt, Sys->MREPL, nil) == -1) { + fds[1] = nil; + sys->fprint(stderr(), "%s: can't mount on %s: %r\n", progname, mtpt); + quit("mount"); + } +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +killpid(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +quit(err: string) +{ + killpid(rpid); + killpid(wpid); + if(err != nil) + raise "fail:"+err; + exit; +} + +start(name:string): (ref Sys->FD, ref Sys->FD) +{ + rfd := sys->open(name, Sys->OREAD); + wfd := sys->open(name, Sys->OWRITE); + if(rfd == nil || wfd == nil) + return (nil, nil); + if(sys->fprint(wfd, "go") < 0) + return (nil, nil); + c := array[1] of byte; + state := 0; + for(;;) { + if(sys->read(rfd, c, 1) != 1) + return (nil, nil); + if(state == 0 && c[0] == byte 'o') + state = 1; + else if(state == 1 && c[0] == byte 'k') + break; + else + state = 0; + } + return (rfd, wfd); +} + +copymsg(f: ref Sys->FD, t: ref Sys->FD, dir: string, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + + { + for(;;) { + (msg, err) := styx->readmsg(f, 0); + if(msg == nil){ + sys->fprint(stderr(), "%s: %s: read error: %s\n", progname, dir, err); + quit("error"); + } + if(debug &1) + trace(dir, msg); + if(debug & 2) + dump(dir, msg, len msg); + if(sys->write(t, msg, len msg) != len msg){ + sys->fprint(stderr(), "%s: %s: write error: %r\n", progname, dir); + quit("error"); + } + } + }exception e{ + "*" => + sys->print("%s: %s: %s: exiting\n", progname, dir, e); + quit("exception"); + } +} + +trace(sourcept: string, op: array of byte ) +{ + if(styx->istmsg(op)){ + (nil, m) := Tmsg.unpack(op); + if(m != nil) + sys->print("%s: %s\n", sourcept, m.text()); + else + sys->print("%s: unknown\n", sourcept); + }else{ + (nil, m) := Rmsg.unpack(op); + if(m != nil) + sys->print("%s: %s\n", sourcept, m.text()); + else + sys->print("%s: unknown\n", sourcept); + } +} + +dump(msg: string, buf: array of byte, n: int) +{ + sys->print("%s: [%d bytes]: ", msg, n); + s := ""; + for(i:=0;iprint(" %s\n", s); + s = ""; + } + sys->print("%2.2x ", int buf[i]); + if(int buf[i] >= 32 && int buf[i] < 127) + s[len s] = int buf[i]; + else + s += "."; + } + for(i %= 20; i < 20; i++) + sys->print(" "); + sys->print(" %s\n\n", s); +} diff --git a/appl/cmd/auxi/rstyxd.b b/appl/cmd/auxi/rstyxd.b new file mode 100644 index 00000000..2f853ad5 --- /dev/null +++ b/appl/cmd/auxi/rstyxd.b @@ -0,0 +1,114 @@ +implement Rstyxd; + +include "sys.m"; +include "draw.m"; +include "sh.m"; +include "string.m"; + +sys: Sys; +str: String; +stderr: ref Sys->FD; + +Rstyxd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +# +# argv is a list of Inferno supported algorithms from Security->Auth +# +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + if (str == nil) + badmod(String->PATH); + + fd := sys->fildes(0); + stderr = sys->fildes(2); + sys->pctl(sys->FORKFD, fd.fd :: nil); + + args := readargs(fd); + if(args == nil) + err(sys->sprint("error reading arguments: %r")); + + cmd := hd args; + s := ""; + for (a := args; a != nil; a = tl a) + s += hd a + " "; + sys->fprint(stderr, "rstyxd: cmd: %s\n", s); + s = nil; + file: string; + if(cmd == "sh") + file = "/dis/sh.dis"; + else + file = cmd + ".dis"; + mod := load Command file; + if(mod == nil){ + mod = load Command "/dis/"+file; + if(mod == nil) + badmod("/dis/"+file); + } + + sys->pctl(Sys->FORKNS|Sys->FORKENV, nil); + + if(sys->mount(fd, nil, "/n/client", Sys->MREPL, "") < 0) + err(sys->sprint("cannot mount connection on /n/client: %r")); + + if(sys->bind("/n/client/dev", "/dev", Sys->MBEFORE) < 0) + err(sys->sprint("cannot bind /n/client/dev to /dev: %r")); + + fd = sys->open("/dev/cons", sys->OREAD); + sys->dup(fd.fd, 0); + fd = sys->open("/dev/cons", sys->OWRITE); + sys->dup(fd.fd, 1); + sys->dup(fd.fd, 2); + fd = nil; + + mod->init(nil, args); +} + +readargs(fd: ref Sys->FD): list of string +{ + buf := array[1024] of byte; + c := array[1] of byte; + for(i:=0; ; i++){ + if(i>=len buf || sys->read(fd, c, 1)!=1) + return nil; + buf[i] = c[0]; + if(c[0] == byte '\n') + break; + } + nb := int string buf[0:i]; + if(nb <= 0) + return nil; + args := readn(fd, nb); + if (args == nil) + return nil; + return str->unquoted(string args[0:nb]); +} + +readn(fd: ref Sys->FD, nb: int): array of byte +{ + buf:= array[nb] of byte; + for(n:=0; nread(fd, buf[n:], nb-n); + if(m <= 0) + return nil; + n += m; + } + return buf; +} + + +err(s: string) +{ + sys->fprint(stderr, "rstyxd: %s\n", s); + raise "fail:error"; +} + +badmod(s: string) +{ + sys->fprint(stderr, "rstyxd: can't load %s: %r\n", s); + raise "fail:load"; +} diff --git a/appl/cmd/avr/burn.b b/appl/cmd/avr/burn.b new file mode 100644 index 00000000..d1004cd1 --- /dev/null +++ b/appl/cmd/avr/burn.b @@ -0,0 +1,859 @@ +implement Burn; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "timers.m"; + timers: Timers; + Timer: import timers; + +include "string.m"; + str: String; + +include "arg.m"; + +Burn: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Avr: adt { + id: int; + rev: int; + flashsize: int; + eepromsize: int; + fusebytes: int; + lockbytes: int; + serfprog: int; # serial fuse programming support + serlprog: int; # serial lockbit programming support + serflread: int; # serial fuse/lockbit reading support + commonlfr: int; # lockbits and fuses are combined + sermemprog: int; # serial memory programming support + pagesize: int; + eeprompagesize: int; + selftimed: int; # all instructions are self-timed + fullpar: int; # part has full parallel interface + polling: int; # polling can be used during SPI access + fpoll: int; # flash poll value + epoll1: int; # eeprom poll value 1 + epoll2: int; # eeprom poll value 2 + name: string; + signalpagel: int; # posn of PAGEL signal (16rD7 by default) + signalbs2: int; # posn of BS2 signal (16rA0 by default) +}; + +F, T: con iota; +ATMEGA128: con 16rB2; # 128k devices + +avrs: array of Avr = array[] of { + (ATMEGA128, 1, 131072, 4096, 3, 1, T, T, T, F, T, 256, 8, T, T, T, 16rFF, 16rFF, 16rFF, "ATmega128", 16rD7, 16rA0), +}; + +sfd: ref Sys->FD; +cfd: ref Sys->FD; +rd: ref Rd; +mib510 := 1; + +Rd: adt { + c: chan of array of byte; + pid: int; + fd: ref Sys->FD; + buf: array of byte; + new: fn(fd: ref Sys->FD): ref Rd; + read: fn(r: self ref Rd, ms: int): array of byte; + readn: fn(r: self ref Rd, n: int, ms: int): array of byte; + flush: fn(r: self ref Rd); + stop: fn(r: self ref Rd); + reader: fn(r: self ref Rd, c: chan of int); +}; + +debug := 0; +verify := 0; +erase := 1; +ignore := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = ckl(load Bufio Bufio->PATH, Bufio->PATH); + str = ckl(load String String->PATH, String->PATH); + timers = ckl(load Timers Timers->PATH, Timers->PATH); + + serial := "/dev/eia0"; + fuseext := -1; + fuselow := -1; + fusehigh := -1; + arg := ckl(load Arg Arg->PATH, Arg->PATH); + arg->init(args); + arg->setusage("burn [-rD] [-d serialdev] file.out"); + while((o := arg->opt()) != 0) + case o { + 'D' => debug++; + 'e' => erase = 0; + 'r' => verify = 1; + 'd' => serial = arg->earg(); + 'i' => ignore = 1; + 'E' => fuseext = fuseval(arg->earg()); + 'L' => fuselow = fuseval(arg->earg()); + 'H' => fusehigh = fuseval(arg->earg()); + * => arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + sfile := hd args; + fd := bufio->open(sfile, Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open %s: %r", sfile)); + + timers->init(2); + sfd = sys->open(serial, Sys->ORDWR); + if(sfd == nil) + err(sys->sprint("can't open %s: %r", "/dev/eia0")); + cfd = sys->open(serial+"ctl", Sys->ORDWR); + sys->fprint(cfd, "f"); + sys->fprint(cfd, "b115200"); + sys->fprint(cfd, "i8"); +# sys->fprint(cfd, "f\nb115200\ni8"); + rd = Rd.new(sfd); + + initialise(); + if(fuseext >= 0 || fuselow >= 0 || fusehigh >= 0){ + if(fuselow >= 0 && (fuselow & 16rF) == 0) + err("don't program external clock"); + if(fuseext >= 0 && (fuseext & (1<<0)) == 0) + err("don't program ATmega103 compatibility"); + if(fusehigh >= 0 && (fusehigh & (1<<7)) == 0) + err("don't program OCDEN=0"); + if(fusehigh >= 0 && writefusehigh(fusehigh) >= 0) + sys->print("set fuse high=%.2ux\n", fusehigh); + if(fuselow >= 0 && writefuselow(fuselow) >= 0) + sys->print("set fuse low=%.2ux\n", fuselow); + if(fuseext >= 0 && writefuseext(fuseext) >= 0) + sys->print("set fuse ext=%.2ux\n", fuseext); + shutdown(); + exit; + } + + if(!verify && erase){ + chiperase(); + sys->print("Erased flash\n"); + } + + totbytes := 0; + while((l := fd.gets('\n')) != nil){ + (c, addr, data) := sdecode(l); + if(c >= '1' && c <= '3'){ + if(verify){ + fdata := readflashdata(addr, len data); + if(!eq(fdata, data)) + sys->print("mismatch: %d::%d at %4.4ux\n", len data, len fdata, addr); + }else if(writeflashdata(addr, data) != len data) + err("failed to program device"); + totbytes += len data; + } else if(c == '0') + sys->print("title: %q\n", string data); + } + if(!verify){ + flushpage(); + sys->print("Programmed %ud (0x%4.4ux) bytes\n", totbytes, totbytes); + } + + shutdown(); +} + +ckl[T](m: T, s: string): T +{ + if(m == nil) + err(sys->sprint("can't load %s: %r", s)); + return m; +} + +fuseval(s: string): int +{ + (n, t) := str->toint(s, 16); + if(t != nil || n < 0 || n > 255) + err("illegal fuse value"); + return n; +} + +cache: (int, array of byte); + +readflashdata(addr: int, nbytes: int): array of byte +{ + data := array[nbytes] of byte; + ia := addr; + ea := addr+nbytes; + while(addr < ea){ + (ca, cd) := cache; + if(addr >= ca && addr < ca+len cd){ + n := nbytes; + o := addr-ca; + if(o+n > len cd) + n = len cd - o; + if(addr-ia+n > len data) + n = len data - (addr-ia); + data[addr-ia:] = cd[o:o+n]; + addr += n; + }else{ + ca = addr & ~16rFF; + cd = readflashpage(ca, 16r100); + cache = (ca, cd); + } + } + return data; +} + +writeflashdata(addr: int, data: array of byte): int +{ + pagesize := avrs[0].pagesize; + ia := addr; + ea := addr+len data; + while(addr < ea){ + (ca, cd) := cache; + if(addr >= ca && addr < ca+len cd){ + n := len data; + o := addr-ca; + if(o+n > len cd) + n = len cd - o; + cd[o:] = data[0:n]; + addr += n; + data = data[n:]; + }else{ + if(flushpage() < 0) + break; + cache = (addr & ~16rFF, array[pagesize] of {* => byte 16rFF}); + } + } + return addr-ia; +} + +flushpage(): int +{ + (ca, cd) := cache; + if(len cd == 0) + return 0; + cache = (0, nil); + if(writeflashpage(ca, cd) != len cd) + return -1; + return len cd; +} + +shutdown() +{ +# setisp(0); + if(rd != nil){ + rd.stop(); + rd = nil; + } + if(timers != nil) + timers->shutdown(); +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "burn: %s\n", s); + shutdown(); + raise "fail:error"; +} + +dump(a: array of byte): string +{ + s := sys->sprint("[%d]", len a); + for(i := 0; i < len a; i++) + s += sys->sprint(" %.2ux", int a[i]); + return s; +} + +initialise() +{ + if(mib510){ + # MIB510-specific: switch rs232 to STK500 + for(i:=0; i<8; i++){ + setisp0(1); + sys->sleep(10); + rd.flush(); + if(setisp(1)) + break; + } + if(!setisp(1)) + err("no response from programmer"); + } + resync(); + resync(); + if(!mib510){ + r := rpc(array[] of {Cmd_STK_GET_SIGN_ON}, 7); + if(r != nil) + sys->print("got: %q\n", string r); + } + r := readsig(); + if(len r > 0 && r[0] != byte 16rFF) + sys->print("sig: %s\n", dump(r)); + (min, maj) := version(); + sys->print("Firmware version: %s.%s\n", min, maj); + setdevice(avrs[0]); + pgmon(); + r = readsig(); + sys->print("sig: %s\n", dump(r)); + pgmoff(); + if(len r < 3 || r[0] != byte 16r1e || r[1] != byte 16r97 || r[2] != byte 16r02) + if(!ignore) + err("unlikely response: check connections"); + + # could set voltages here... + sys->print("fuses: h=%.2ux l=%.2ux e=%.2ux\n", readfusehigh(), readfuselow(), readfuseext()); +} + +resync() +{ + for(i := 0; i < 8; i++){ + rd.flush(); + r := rpc(array[] of {Cmd_STK_GET_SYNC}, 0); + if(r != nil) + return; + } + err("lost sync with programmer"); +} + +getparam(p: byte): int +{ + r := rpc(array[] of {Cmd_STK_GET_PARAMETER, p}, 1); + if(len r > 0) + return int r[0]; + return -1; +} + +version(): (string, string) +{ + maj := getparam(Parm_STK_SW_MAJOR); + min := getparam(Parm_STK_SW_MINOR); + if(mib510) + return (sys->sprint("%c", maj), sys->sprint("%c", min)); + return (sys->sprint("%d", maj), sys->sprint("%d", min)); +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +# +# Motorola S records +# + +badsrec(s: string) +{ + err("bad S record: "+s); +} + +hexc(c: int): int +{ + if(c >= '0' && c <= '9') + return c-'0'; + if(c >= 'a' && c <= 'f') + return c-'a'+10; + if(c >= 'A' && c <= 'F') + return c-'A'+10; + return -1; +} + +g8(s: string): int +{ + if(len s >= 2){ + c0 := hexc(s[0]); + c1 := hexc(s[1]); + if(c0 >= 0 && c1 >= 0) + return (c0<<4) | c1; + } + return -1; +} + +# S d len +sdecode(s: string): (int, int, array of byte) +{ + while(len s > 0 && (s[len s-1] == '\r' || s[len s-1] == '\n')) + s = s[0:len s-1]; + if(len s < 4 || s[0] != 'S') + badsrec(s); + l := g8(s[2:4]); + if(l < 0) + badsrec("length: "+s); + if(2*l != len s - 4) + badsrec("length: "+s); + csum := l; + na := 2; + if(s[1] >= '1' && s[1] <= '3') + na = s[1]-'1'+2; + addr := 0; + for(i:=0; i # end/start address + ; + * => + badsrec("type: "+s); + } + data := array[l-na-1] of byte; + for(i = 0; i < len data; i++){ + c := g8(s[4+(na+i)*2:]); + csum += c; + data[i] = byte c; + } + v := g8(s[4+l*2-2:]); + csum += v; + if((csum & 16rFF) != 16rFF) + badsrec("checksum: "+s); + return (s[1], addr, data); +} + +# +# serial port +# + +Rd.new(fd: ref Sys->FD): ref Rd +{ + r := ref Rd(chan[4] of array of byte, 0, fd, nil); + c := chan of int; + spawn r.reader(c); + <-c; + return r; +} + +Rd.reader(r: self ref Rd, c: chan of int) +{ + r.pid = sys->pctl(0, nil); + c <-= 1; + for(;;){ + buf := array[258] of byte; + n := sys->read(r.fd, buf, len buf); + if(n <= 0){ + r.pid = 0; + err(sys->sprint("read error: %r")); + } + if(debug) + sys->print("<- %s\n", dump(buf[0:n])); + r.c <-= buf[0:n]; + } +} + +Rd.read(r: self ref Rd, ms: int): array of byte +{ + if((a := r.buf) != nil){ + r.buf = nil; + return a; + } + t := Timer.start(ms); + alt{ + a = <-r.c => + t.stop(); + Acc: + for(;;){ + sys->sleep(5); + alt{ + b := <-r.c => + if(b == nil) + break Acc; + a = cat(a, b); + * => + break Acc; + } + } + return a; + <-t.timeout => + return nil; + } +} + +Rd.readn(r: self ref Rd, n: int, ms: int): array of byte +{ + a: array of byte; + + while((need := n - len a) > 0){ + b := r.read(ms); + if(b == nil) + break; + if(len b > need){ + r.buf = b[need:]; + b = b[0:need]; + } + a = cat(a, b); + } + return a; +} + +Rd.flush(r: self ref Rd) +{ + r.buf = nil; + sys->sleep(5); + for(;;){ + alt{ + <-r.c => + ; + * => + return; + } + } +} + +Rd.stop(r: self ref Rd) +{ + pid := r.pid; + if(pid){ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); + } +} + +cat(a, b: array of byte): array of byte +{ + if(len b == 0) + return a; + if(len a == 0) + return b; + c := array[len a + len b] of byte; + c[0:] = a; + c[len a:] = b; + return c; +} + +# +# STK500 communication protocol +# + +STK_SIGN_ON_MESSAGE: con "AVR STK"; # Sign on string for Cmd_STK_GET_SIGN_ON + +# Responses + +Resp_STK_OK: con byte 16r10; +Resp_STK_FAILED: con byte 16r11; +Resp_STK_UNKNOWN: con byte 16r12; +Resp_STK_NODEVICE: con byte 16r13; +Resp_STK_INSYNC: con byte 16r14; +Resp_STK_NOSYNC: con byte 16r15; + +Resp_ADC_CHANNEL_ERROR: con byte 16r16; +Resp_ADC_MEASURE_OK: con byte 16r17; +Resp_PWM_CHANNEL_ERROR: con byte 16r18; +Resp_PWM_ADJUST_OK: con byte 16r19; + +# Special constants + +Sync_CRC_EOP: con byte 16r20; + +# Commands + +Cmd_STK_GET_SYNC: con byte 16r30; +Cmd_STK_GET_SIGN_ON: con byte 16r31; + +Cmd_STK_SET_PARAMETER: con byte 16r40; +Cmd_STK_GET_PARAMETER: con byte 16r41; +Cmd_STK_SET_DEVICE: con byte 16r42; +Cmd_STK_SET_DEVICE_EXT: con byte 16r45; + +Cmd_STK_ENTER_PROGMODE: con byte 16r50; +Cmd_STK_LEAVE_PROGMODE: con byte 16r51; +Cmd_STK_CHIP_ERASE: con byte 16r52; +Cmd_STK_CHECK_AUTOINC: con byte 16r53; +Cmd_STK_LOAD_ADDRESS: con byte 16r55; +Cmd_STK_UNIVERSAL: con byte 16r56; +Cmd_STK_UNIVERSAL_MULTI: con byte 16r57; + +Cmd_STK_PROG_FLASH: con byte 16r60; +Cmd_STK_PROG_DATA: con byte 16r61; +Cmd_STK_PROG_FUSE: con byte 16r62; +Cmd_STK_PROG_LOCK: con byte 16r63; +Cmd_STK_PROG_PAGE: con byte 16r64; +Cmd_STK_PROG_FUSE_EXT: con byte 16r65; + +Cmd_STK_READ_FLASH: con byte 16r70; +Cmd_STK_READ_DATA: con byte 16r71; +Cmd_STK_READ_FUSE: con byte 16r72; +Cmd_STK_READ_LOCK: con byte 16r73; +Cmd_STK_READ_PAGE: con byte 16r74; +Cmd_STK_READ_SIGN: con byte 16r75; +Cmd_STK_READ_OSCCAL: con byte 16r76; +Cmd_STK_READ_FUSE_EXT: con byte 16r77; +Cmd_STK_READ_OSCCAL_EXT: con byte 16r78; + +# Parameter constants + +Parm_STK_HW_VER: con byte 16r80; # ' ' - R +Parm_STK_SW_MAJOR: con byte 16r81; # ' ' - R +Parm_STK_SW_MINOR: con byte 16r82; # ' ' - R +Parm_STK_LEDS: con byte 16r83; # ' ' - R/W +Parm_STK_VTARGET: con byte 16r84; # ' ' - R/W +Parm_STK_VADJUST: con byte 16r85; # ' ' - R/W +Parm_STK_OSC_PSCALE: con byte 16r86; # ' ' - R/W +Parm_STK_OSC_CMATCH: con byte 16r87; # ' ' - R/W +Parm_STK_RESET_DURATION: con byte 16r88; # ' ' - R/W +Parm_STK_SCK_DURATION: con byte 16r89; # ' ' - R/W + +Parm_STK_BUFSIZEL: con byte 16r90; # ' ' - R/W, Range {0..255} +Parm_STK_BUFSIZEH: con byte 16r91; # ' ' - R/W, Range {0..255} +Parm_STK_DEVICE: con byte 16r92; # ' ' - R/W, Range {0..255} +Parm_STK_PROGMODE: con byte 16r93; # ' ' - 'P' or 'S' +Parm_STK_PARAMODE: con byte 16r94; # ' ' - TRUE or FALSE +Parm_STK_POLLING: con byte 16r95; # ' ' - TRUE or FALSE +Parm_STK_SELFTIMED: con byte 16r96; # ' ' - TRUE or FALSE + +# status bits + +Stat_STK_INSYNC: con byte 16r01; # INSYNC status bit, '1' - INSYNC +Stat_STK_PROGMODE: con byte 16r02; # Programming mode, '1' - PROGMODE +Stat_STK_STANDALONE: con byte 16r04; # Standalone mode, '1' - SM mode +Stat_STK_RESET: con byte 16r08; # RESET button, '1' - Pushed +Stat_STK_PROGRAM: con byte 16r10; # Program button, ' 1' - Pushed +Stat_STK_LEDG: con byte 16r20; # Green LED status, '1' - Lit +Stat_STK_LEDR: con byte 16r40; # Red LED status, '1' - Lit +Stat_STK_LEDBLINK: con byte 16r80; # LED blink ON/OFF, '1' - Blink + +ispmode := array[] of {byte 16rAA, byte 16r55, byte 16r55, byte 16rAA, byte 16r17, byte 16r51, byte 16r31, byte 16r13, byte 0}; # last byte is 1 to switch isp on 0 to switch off + +ck(r: array of byte) +{ + if(r == nil) + err("programming failed"); +} + +pgmon() +{ + ck(rpc(array[] of {Cmd_STK_ENTER_PROGMODE}, 0)); +} + +pgmoff() +{ + ck(rpc(array[] of {Cmd_STK_LEAVE_PROGMODE}, 0)); +} + +setisp0(on: int) +{ + rd.flush(); + buf := array[len ispmode] of byte; + buf[0:] = ispmode; + buf[8] = byte on; + sys->write(sfd, buf, len buf); +} + +setisp(on: int): int +{ + rd.flush(); + buf := array[len ispmode] of byte; + buf[0:] = ispmode; + buf[8] = byte on; + r := send(buf, 2); + return len r == 2 && ok(r); +} + +readsig(): array of byte +{ + r := send(array[] of {Cmd_STK_READ_SIGN, Sync_CRC_EOP}, 5); + # doesn't behave as documented in AVR061: it repeats the command bytes instead + if(len r != 5 || r[0] != Cmd_STK_READ_SIGN || r[4] != Sync_CRC_EOP){ + sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r)); + return nil; + } + return r[1:len r-1]; # trim proto bytes +} + +pgrpc(a: array of byte, repn: int): array of byte +{ + pgmon(); + r := rpc(a, repn); + pgmoff(); + return r; +} + +eop := array[] of {Sync_CRC_EOP}; + +rpc(a: array of byte, repn: int): array of byte +{ + r := send(cat(a, eop), repn+2); + if(!ok(r)){ + if(len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r-1] == Resp_STK_NODEVICE) + err("internal error: programming parameters not correctly set"); + if(len r >= 1 && r[0] == Resp_STK_NOSYNC) + err("lost synchronisation"); + sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r)); + return nil; + } + return r[1:len r-1]; # trim sync bytes +} + +send(a: array of byte, repn: int): array of byte +{ + if(debug) + sys->print("-> %s\n", dump(a)); + if(sys->write(sfd, a, len a) != len a) + err(sys->sprint("write error: %r")); + return rd.readn(repn, 2000); +} + +ok(r: array of byte): int +{ + return len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r -1] == Resp_STK_OK; +} + +universal(req: array of byte): int +{ + r := pgrpc(cat(array[] of {Cmd_STK_UNIVERSAL}, req), 1); + if(r == nil) + return -1; + return int r[0]; +} + +setdevice(d: Avr) +{ + b := array[] of { + Cmd_STK_SET_DEVICE, + byte d.id, + byte d.rev, + byte 0, # prog type (CHECK) + byte d.fullpar, + byte d.polling, + byte d.selftimed, + byte d.lockbytes, + byte d.fusebytes, + byte d.fpoll, + byte d.fpoll, + byte d.epoll1, + byte d.epoll2, + byte (d.pagesize >> 8), byte d.pagesize, + byte (d.eepromsize>>8), byte d.eepromsize, + byte (d.flashsize>>24), byte (d.flashsize>>16), byte (d.flashsize>>8), byte d.flashsize + }; + ck(rpc(b, 0)); + if(mib510) + return; + b = array[] of { + Cmd_STK_SET_DEVICE_EXT, + byte 4, + byte d.eeprompagesize, + byte d.signalpagel, + byte d.signalbs2, + byte 0 # ResetDisable + }; + ck(rpc(b, 0)); +} + +chiperase() +{ + ck(pgrpc(array[] of {Cmd_STK_CHIP_ERASE}, 0)); +} + +readfuselow(): int +{ + return universal(array[] of {byte 16r50, byte 0, byte 0, byte 0}); +} + +readfusehigh(): int +{ + return universal(array[] of {byte 16r58, byte 8, byte 0, byte 0}); +} + +readfuseext(): int +{ + return universal(array[] of {byte 16r50, byte 8, byte 0, byte 0}); +} + +readlockfuse(): int +{ + return universal(array[] of {byte 16r58, byte 0, byte 0, byte 0}); +} + +readflashpage(addr: int, nb: int): array of byte +{ + return readmem('F', addr/2, nb); +} + +readeeprompage(addr: int, nb: int): array of byte +{ + return readmem('E', addr, nb); +} + +readmem(memtype: int, addr: int, nb: int): array of byte +{ + if(nb > 256) + nb = 256; + pgmon(); + r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0); + if(r != nil){ + r = send(array[] of {Cmd_STK_READ_PAGE, byte (nb>>8), byte nb, byte memtype, Sync_CRC_EOP}, nb+2); + l := len r; + # AVR601 says last byte should be Resp_STK_OK but it's not, at least on MIB; check for both + if(l >= 2 && r[0] == Resp_STK_INSYNC && (r[l-1] == Resp_STK_INSYNC || r[l-1] == Resp_STK_OK)) + r = r[1:l-1]; # trim framing bytes + else{ + sys->print("bad reply: %s\n", dump(r)); + r = nil; + } + if(len r < nb) + sys->print("short [%d@%4.4ux]\n", nb, addr); + } + pgmoff(); + return r; +} + +writeflashpage(addr: int, data: array of byte): int +{ + return writemem('F', addr/2, data); +} + +writeeeprompage(addr: int, data: array of byte): int +{ + return writemem('E', addr, data); +} + +writemem(memtype: int, addr: int, data: array of byte): int +{ + nb := len data; + if(nb > 256){ + nb = 256; + data = data[0:nb]; + } + pgmon(); + r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0); + if(r != nil){ + r = rpc(cat(array[] of {Cmd_STK_PROG_PAGE, byte (nb>>8), byte nb, byte memtype},data), 0); + if(r == nil) + nb = -1; + } + pgmoff(); + return nb; +} + +writefuseext(v: int): int +{ + return universal(array[] of {byte 16rAC, byte 16rA4, byte 16rFF, byte v}); +} + +writefuselow(v: int): int +{ + return universal(array[] of {byte 16rAC, byte 16rA0, byte 16rFF, byte v}); +} + +writefusehigh(v: int): int +{ + return universal(array[] of {byte 16rAC, byte 16rA8, byte 16rFF, byte v}); +} diff --git a/appl/cmd/avr/mkfile b/appl/cmd/avr/mkfile new file mode 100644 index 00000000..2c6a5a33 --- /dev/null +++ b/appl/cmd/avr/mkfile @@ -0,0 +1,10 @@ +<../../../mkconfig + +TARG=\ + burn.dis\ + +SYSMODULES=\ + +DISBIN=$ROOT/dis/avr + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/basename.b b/appl/cmd/basename.b new file mode 100644 index 00000000..8d0ad5a8 --- /dev/null +++ b/appl/cmd/basename.b @@ -0,0 +1,50 @@ +implement Basename; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "names.m"; + names: Names; + +include "arg.m"; + +Basename: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + names = load Names Names->PATH; + arg := load Arg Arg->PATH; + + dirname := 0; + arg->init(args); + arg->setusage("basename [-d] string [suffix]"); + while((o := arg->opt()) != 0) + case o { + 'd' => + dirname = 1; + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil || tl args != nil && (dirname || tl tl args != nil)) + arg->usage(); + arg = nil; + + if(dirname){ + s := names->dirname(hd args); + if(s == nil) + s = "."; + sys->print("%s\n", s); + exit; + } + suffix: string; + if(tl args != nil) + suffix = hd tl args; + sys->print("%s\n", names->basename(hd args, suffix)); +} diff --git a/appl/cmd/bind.b b/appl/cmd/bind.b new file mode 100644 index 00000000..fa6c734b --- /dev/null +++ b/appl/cmd/bind.b @@ -0,0 +1,66 @@ +implement Bind; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +Bind: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +stderr: ref Sys->FD; + +usage() +{ + sys->fprint(stderr, "usage: bind [-a|-b|-c|-ac|-bc] [-q] source target\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + flags := 0; + qflag := 0; + if(args != nil) + args = tl args; + while(args != nil && (a := hd args) != "" && a[0] == '-'){ + args = tl args; + if(a == "--") + break; + for(o := 1; o < len a; o++) + case a[o] { + 'a' => + flags |= Sys->MAFTER; + 'b' => + flags |= Sys->MBEFORE; + 'c' => + flags |= Sys->MCREATE; + 'q' => + qflag = 1; + * => + usage(); + } + } + if(len args != 2 || flags&Sys->MAFTER && flags&Sys->MBEFORE) + usage(); + + f1 := hd args; + f2 := hd tl args; + if(sys->bind(f1, f2, flags) < 0){ + if(qflag) + exit; + # try to improve the error message + err := sys->sprint("%r"); + if(sys->stat(f1).t0 < 0) + sys->fprint(stderr, "bind: %s: %r\n", f1); + else if(sys->stat(f2).t0 < 0) + sys->fprint(stderr, "bind: %s: %r\n", f2); + else + sys->fprint(stderr, "bind: cannot bind %s onto %s: %s\n", f1, f2, err); + raise "fail:bind"; + } +} diff --git a/appl/cmd/bit2gif.b b/appl/cmd/bit2gif.b new file mode 100644 index 00000000..52788e76 --- /dev/null +++ b/appl/cmd/bit2gif.b @@ -0,0 +1,86 @@ +# +# bit2gif - +# +# A simple command line utility for converting inferno bitmaps +# to gif images. +# +# Craig Newell, Jan. 1999 CraigN@cheque.uq.edu.au +# +implement bit2gif; + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Display: import draw; +include "string.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "imagefile.m"; + +bit2gif : module +{ + init: fn(ctx: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->print("usage: bit2gif \n"); + exit; +} + +init(ctx: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + # check arguments + if (argv == nil) + usage(); + argv = tl argv; + if (argv == nil) + usage(); + s := hd argv; + if (len s && s[0] == '-') + usage(); + + # load the modules + str := load String String->PATH; + draw = load Draw Draw->PATH; + bufio = load Bufio Bufio->PATH; + imgfile := load WImagefile WImagefile->WRITEGIFPATH; + imgfile->init(bufio); + + # open the display + display: ref Draw->Display; + if (ctx == nil) { + display = Display.allocate(nil); + } else { + display = ctx.display; + } + + # process all the files + while (argv != nil) { + + # get the filenames + bit_name := hd argv; + (gif_name, nil) := str->splitstrl(bit_name, ".bit"); + gif_name = gif_name + ".gif"; + + # load inferno bitmap + img := display.open(bit_name); + if (img == nil) { + sys->print("bit2gif: unable to read <%s>\n", bit_name); + } else { + # save as gif + o := bufio->create(gif_name, Bufio->OWRITE, 8r644); + if (o != nil) { + imgfile->writeimage(o, img); + o.close(); + } + } + + # next argument + argv = tl argv; + } +} diff --git a/appl/cmd/broke.b b/appl/cmd/broke.b new file mode 100644 index 00000000..41f2dd89 --- /dev/null +++ b/appl/cmd/broke.b @@ -0,0 +1,84 @@ +implement Broke; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +Broke: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + fd := sys->open("/prog", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /prog: %r")); + killed := ""; + for(;;){ + (n, dir) := sys->dirread(fd); + if(n <= 0){ + if(n < 0) + err(sys->sprint("error reading /prog: %r")); + break; + } + for(i := 0; i < n; i++) + if(isbroken(dir[i].name) && kill(dir[i].name)) + killed += sys->sprint(" %s", dir[i].name); + } + if(killed != nil) + sys->print("%s\n", killed); +} + +isbroken(pid: string): int +{ + statf := "/prog/" + pid + "/status"; + fd := sys->open(statf, Sys->OREAD); + if (fd == nil) + return 0; + buf := array[256] of byte; + n := sys->read(fd, buf, len buf); + if (n < 0) { # process died or is exiting + # sys->fprint(stderr(), "broke: can't read %s: %r\n", statf); + return 0; + } + (nf, l) := sys->tokenize(string buf[0:n], " "); + return nf >= 5 && hd tl tl tl tl l == "broken"; +} + +kill(pid: string): int +{ + ctl := "/prog/" + pid + "/ctl"; + fd := sys->open(ctl, sys->OWRITE); + if(fd == nil || sys->fprint(fd, "kill") < 0){ + sys->fprint(stderr(), "broke: can't kill %s: %r\n", pid); # but press on + return 0; + } + return 1; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "broke: %s\n", s); + raise "fail:error"; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +user(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil) + return "inferno"; + + buf := array[64] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return "inferno"; + + return string buf[0:n]; +} diff --git a/appl/cmd/bytes.b b/appl/cmd/bytes.b new file mode 100644 index 00000000..e45c4fe4 --- /dev/null +++ b/appl/cmd/bytes.b @@ -0,0 +1,212 @@ +implement Bytes; +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +stdin, stdout: ref Iobuf; + +Bytes: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: bytes start end [bytes]\n"); + raise "fail:usage"; +} + +END: con 16r7fffffff; +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) { + sys->fprint(stderr, "bytes: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + stdin = bufio->fopen(sys->fildes(0), Sys->OREAD); + stdout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + start := end := END; + if (len argv < 3) + usage(); + argv = tl argv; + if (hd argv != "end") + start = int hd argv; + argv = tl argv; + if (hd argv != "end") + end = int hd argv; + if (end < start) { + sys->fprint(stderr, "bytes: out of order range\n"); + raise "fail:bad range"; + } + argv = tl argv; + if (argv == nil) + showbytes(start, end); + else { + if (tl argv != nil) + usage(); + b := s2bytes(hd argv); + setbytes(start, end, b); + } + stdout.close(); +} + +showbytes(start, end: int) +{ + buf := array[Sys->ATOMICIO] of byte; + hold := array[Sys->UTFmax] of byte; + tot := 0; + nhold := 0; + while (tot < end && (n := stdin.read(buf[nhold:], len buf - nhold)) > 0) { + sys->fprint(stderr, "bytes: read %d bytes\n", n); + if (tot + n < start) + continue; + sb := 0; + eb := n; + if (start > tot) + sb = start - tot; + if (tot + n > end) + eb = end - tot; + nhold = putbytes(buf[sb:eb], hold); + buf[0:] = hold[0:nhold]; + tot += n - nhold; + } + sys->fprint(stderr, "out of loop\n"); + flushbytes(hold[0:nhold]); +} + +setbytes(start, end: int, d: array of byte) +{ + buf := array[Sys->ATOMICIO] of byte; + tot := 0; + while ((n := stdin.read(buf, len buf)) > 0) { + if (tot + n < start || tot >= end) { + stdout.write(buf, n); + continue; + } + if (tot <= start) { + stdout.write(buf[0:start-tot], start-tot); + stdout.write(d, len d); + if (end == END) + return; + } + if (tot + n >= end) + stdout.write(buf[end - tot:], n - (end - tot)); + tot += n; + } + if (tot == start || start == END) + stdout.write(d, len d); +} + +putbytes(d: array of byte, hold: array of byte): int +{ + i := 0; + while (i < len d) { + (c, n, ok) := sys->byte2char(d, i); + if (ok && n > 0) { + if (c == '\\') + stdout.putc('\\'); + stdout.putc(c); + } else { + if (n == 0) { + hold[0:] = d[i:]; + return len d - i; + } else { + putbyte(d[i]); + n = 1; + } + } + i += n; + } + return 0; +} + +flushbytes(hold: array of byte) +{ + for (i := 0; i < len hold; i++) + putbyte(hold[i]); +} + +putbyte(b: byte) +{ + stdout.puts(sys->sprint("\\%2.2X", int b)); +} + +isbschar(c: int): int +{ + case c { + 'n' or 'r' or 't' or 'v' => + return 1; + } + return 0; +} + +s2bytes(s: string): array of byte +{ + d := array[len s + 2] of byte; + j := 0; + for (i := 0; i < len s; i++) { + if (s[i] == '\\') { + if (i >= len s - 1 || (!isbschar(s[i+1]) && i >= len s - 2)) { + sys->fprint(stderr, "bytes: invalid backslash sequence\n"); + raise "fail:bad args"; + } + d = assure(d, j + 1); + if (isbschar(s[i+1])) { + case s[i+1] { + 'n' => d[j++] = byte '\n'; + 'r' => d[j++] = byte '\r'; + 't' => d[j++] = byte '\t'; + 'v' => d[j++] = byte '\v'; + '\\' => d[j++] = byte '\\'; + * => + sys->fprint(stderr, "bytes: invalid backslash sequence\n"); + raise "fail:bad args"; + } + i++; + } else if (!ishex(s[i+1]) || !ishex(s[i+2])) { + sys->fprint(stderr, "bytes: invalid backslash sequence\n"); + raise "fail:bad args"; + } else { + d[j++] = byte ((hex(s[i+1]) << 4) + hex(s[i+2])); + i += 2; + } + } else { + d = assure(d, j + 3); + j += sys->char2byte(s[i], d, j); + } + } + return d[0:j]; +} + +assure(d: array of byte, n: int): array of byte +{ + if (len d >= n) + return d; + nd := array[n] of byte; + nd[0:] = d; + return nd; +} + +ishex(c: int): int +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +hex(c: int): int +{ + case c { + '0' to '9' => + return c - '0'; + 'a' to 'f' => + return c - 'a' + 10; + 'A' to 'F' => + return c- 'A' + 10; + } + return 0; +} diff --git a/appl/cmd/cal.b b/appl/cmd/cal.b new file mode 100644 index 00000000..90c4f777 --- /dev/null +++ b/appl/cmd/cal.b @@ -0,0 +1,295 @@ +implement Cal; + +# +# Copyright © 1995-2002 Lucent Technologies Inc. All rights reserved. +# Limbo transliteration 2003 by Vita Nuova +# This software is subject to the Plan 9 Open Source Licence. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "daytime.m"; + daytime: Daytime; + Tm: import daytime; + +Cal: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +dayw := " S M Tu W Th F S"; +smon := array[] of { + "January", "February", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December", +}; + +mon := array[] of { + 0, + 31, 29, 31, 30, + 31, 30, 31, 31, + 30, 31, 30, 31, +}; + +bout: ref Iobuf; + +init(nil: ref Draw->Context, args: list of string) +{ + y, m: int; + + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + daytime = load Daytime Daytime->PATH; + + argc := len args; + if(argc > 3){ + sys->fprint(sys->fildes(2), "usage: cal [month] [year]\n"); + raise "fail:usage"; + } + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + +# +# no arg, print current month +# + if(argc <= 1) { + m = curmo(); + y = curyr(); + return xshort(m, y); + } + args = tl args; + +# +# one arg +# if looks like a month, print month +# else print year +# + if(argc == 2) { + y = number(hd args); + if(y < 0) + y = -y; + if(y >= 1 && y <= 12) + return xshort(y, curyr()); + return xlong(y); + } + +# +# two arg, month and year +# + m = number(hd args); + if(m < 0) + m = -m; + y = number(hd tl args); + return xshort(m, y); +} + +# +# print out just month +# +xshort(m: int, y: int) +{ + if(m < 1 || m > 12) + badarg(); + if(y < 1 || y > 9999) + badarg(); + bout.puts(sys->sprint(" %s %ud\n", smon[m-1], y)); + bout.puts(sys->sprint("%s\n", dayw)); + lines := cal(m, y); + for(i := 0; i < len lines; i++){ + bout.puts(lines[i]); + bout.putc('\n'); + } + bout.flush(); +} + +# +# print out complete year +# +xlong(y: int) +{ + if(y<1 || y>9999) + badarg(); + bout.puts("\n\n\n"); + bout.puts(sys->sprint(" %ud\n", y)); + bout.putc('\n'); + months := array[3] of array of string; + for(i:=0; i<12; i+=3) { + bout.puts(sys->sprint(" %.3s", smon[i])); + bout.puts(sys->sprint(" %.3s", smon[i+1])); + bout.puts(sys->sprint(" %.3s\n", smon[i+2])); + bout.puts(sys->sprint("%s %s %s\n", dayw, dayw, dayw)); + for(j := 0; j < 3; j++) + months[j] = cal(i+j+1, y); + for(l := 0; l < 6; l++){ + s := ""; + for(j = 0; j < 3; j++) + s += sys->sprint("%-20.20s ", months[j][l]); + for(j = len s; j > 0 && s[j-1] == ' ';) + j--; + bout.puts(s[0:j]); + bout.putc('\n'); + } + } + bout.flush(); +} + +badarg() +{ + sys->fprint(sys->fildes(2), "cal: bad argument\n"); + raise "fail:bad argument"; +} + +dict := array[] of { + ("january", 1), + ("february", 2), + ("march", 3), + ("april", 4), + ("may", 5), + ("june", 6), + ("july", 7), + ("august", 8), + ("sept", 9), + ("september", 9), + ("october", 10), + ("november", 11), + ("december", 12), +}; + +# +# convert to a number. +# if its a dictionary word, +# return negative number +# +number(s: string): int +{ + if(len s >= 3){ + for(n:=0; n < len dict; n++){ + (word, val) := dict[n]; + if(s == word || s == word[0:3]) + return -val; + } + } + n := 0; + for(i := 0; i < len s; i++){ + c := s[i]; + if(c<'0' || c>'9') + badarg(); + n = n*10 + c-'0'; + } + return n; +} + +pstr(str: string, n: int) +{ + bout.puts(sys->sprint("%-*.*s\n", n, n, str)); +} + +cal(m: int, y: int): array of string +{ + d := jan1(y); + mon[9] = 30; + + case (jan1(y+1)+7-d)%7 { + + # + # non-leap year + # + 1 => + mon[2] = 28; + + # + # leap year + # + 2 => + mon[2] = 29; + + # + # 1752 + # + * => + mon[2] = 29; + mon[9] = 19; + } + for(i:=1; isprint("%2d", i); + if(++d == 7) { + d = 0; + lines[l++] = s; + s = ""; + }else + s[len s] = ' '; + } + if(s != nil){ + while(s[len s-1] == ' ') + s = s[:len s-1]; + lines[l] = s; + } + return lines; +} + +# +# return day of the week +# of jan 1 of given year +# +jan1(y: int): int +{ +# +# normal gregorian calendar +# one extra day per four years +# + + d := 4+y+(y+3)/4; + +# +# julian calendar +# regular gregorian +# less three days per 400 +# + + if(y > 1800) { + d -= (y-1701)/100; + d += (y-1601)/400; + } + +# +# great calendar changeover instant +# + + if(y > 1752) + d += 3; + + return d%7; +} + +# +# get current month and year +# +curmo(): int +{ + tm := daytime->local(daytime->now()); + return tm.mon+1; +} + +curyr(): int +{ + tm := daytime->local(daytime->now()); + return tm.year+1900; +} diff --git a/appl/cmd/cat.b b/appl/cmd/cat.b new file mode 100644 index 00000000..24d62372 --- /dev/null +++ b/appl/cmd/cat.b @@ -0,0 +1,57 @@ +implement Cat; + +include "sys.m"; +include "draw.m"; + +Cat: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +sys: Sys; +stdout: ref Sys->FD; + +init(nil: ref Draw->Context, argl: list of string) +{ + sys = load Sys Sys->PATH; + + stdout = sys->fildes(1); + + argl = tl argl; + if(argl == nil) + argl = "-" :: nil; + while(argl != nil) { + cat(hd argl); + argl = tl argl; + } +} + +cat(file: string) +{ + n: int; + fd: ref Sys->FD; + buf := array[8192] of byte; + + if(file == "-") + fd = sys->fildes(0); + else { + fd = sys->open(file, sys->OREAD); + if(fd == nil) { + sys->fprint(sys->fildes(2), "cat: cannot open %s: %r\n", file); + raise "fail:bad open"; + } + } + for(;;) { + n = sys->read(fd, buf, len buf); + if(n <= 0) + break; + if(sys->write(stdout, buf, n) < n) { + sys->fprint(sys->fildes(2), "cat: write error: %r\n"); + raise "fail:write error"; + } + } + if(n < 0) { + sys->fprint(sys->fildes(2), "cat: read error: %r\n"); + raise "fail:read error"; + } +} diff --git a/appl/cmd/cd.b b/appl/cmd/cd.b new file mode 100644 index 00000000..57c94aba --- /dev/null +++ b/appl/cmd/cd.b @@ -0,0 +1,48 @@ +implement Cd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +Cd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + argv = tl argv; + if(argv == nil) + argv = "/usr/"+user() :: nil; + + if(tl argv != nil) { + sys->fprint(stderr, "Usage: cd [directory]\n"); + raise "fail:usage"; + } + + if(sys->chdir(hd argv) < 0) { + sys->fprint(stderr, "cd: %s: %r\n", hd argv); + raise "fail:failed"; + } +} + +user(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil) + return "inferno"; + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return "inferno"; + + return string buf[0:n]; +} diff --git a/appl/cmd/chgrp.b b/appl/cmd/chgrp.b new file mode 100644 index 00000000..ec473759 --- /dev/null +++ b/appl/cmd/chgrp.b @@ -0,0 +1,58 @@ +implement Chgrp; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +Chgrp: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: chgrp [-uo] group file ...\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + arg := load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(sys->fildes(2), "chgrp: can't load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + setuser := 0; + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'o' or 'u' => + setuser = 1; + * => + usage(); + } + args = arg->argv(); + arg = nil; + if(args == nil) + usage(); + id := hd args; + err := 0; + while((args = tl args) != nil){ + d := sys->nulldir; + if(setuser) + d.uid = id; + else + d.gid = id; + if(sys->wstat(hd args, d) < 0){ + sys->fprint(sys->fildes(2), "chgrp: can't change %s: %r\n", hd args); + err = 1; + } + } + if(err) + raise "fail:error"; +} diff --git a/appl/cmd/chmod.b b/appl/cmd/chmod.b new file mode 100644 index 00000000..de7ecf2c --- /dev/null +++ b/appl/cmd/chmod.b @@ -0,0 +1,125 @@ +implement Chmod; + +include "sys.m"; +include "draw.m"; +include "string.m"; + +Chmod: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +sys: Sys; +stderr: ref Sys->FD; + +str: String; + +User: con 8r700; +Group: con 8r070; +Other: con 8r007; +All: con User | Group | Other; + +Read: con 8r444; +Write: con 8r222; +Exec: con 8r111; + +usage() +{ + sys->fprint(stderr, "usage: chmod [8r]777 file ... or chmod [augo][+-=][rwxal] file ...\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + str = load String String->PATH; + if(str == nil){ + sys->fprint(stderr, "chmod: cannot load %s: %r\n", String->PATH); + raise "fail:bad module"; + } + + if(len argv < 3) + usage(); + argv = tl argv; + m := hd argv; + argv = tl argv; + + mask := All; + if (str->prefix("8r", m)) + m = m[2:]; + (mode, s) := str->toint(m, 8); + if(s != "" || m == ""){ + ok := 0; + (ok, mask, mode) = parsemode(m); + if(!ok){ + sys->fprint(stderr, "chmod: bad mode '%s'\n", m); + usage(); + } + } + ndir := sys->nulldir; + for(; argv != nil; argv = tl argv){ + f := hd argv; + (ok, dir) := sys->stat(f); + if(ok < 0){ + sys->fprint(stderr, "chmod: cannot stat %s: %r\n", f); + continue; + } + ndir.mode = (dir.mode & ~mask) | (mode & mask); + if(sys->wstat(f, ndir) < 0) + sys->fprint(stderr, "chmod: cannot wstat %s: %r\n", f); + } +} + +parsemode(spec: string): (int, int, int) +{ + mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMTMP; +loop: for(i := 0; i < len spec; i++){ + case spec[i] { + 'u' => + mask |= User; + 'g' => + mask |= Group; + 'o' => + mask |= Other; + 'a' => + mask |= All; + * => + break loop; + } + } + if(i == len spec) + return (0, 0, 0); + if(i == 0) + mask |= All; + + op := spec[i++]; + if(op != '+' && op != '-' && op != '=') + return (0, 0, 0); + + mode := 0; + for(; i < len spec; i++){ + case spec[i]{ + 'r' => + mode |= Read; + 'w' => + mode |= Write; + 'x' => + mode |= Exec; + 'a' => + mode |= Sys->DMAPPEND; + 'l' => + mode |= Sys->DMEXCL; + 't' => + mode |= Sys->DMTMP; + * => + return (0, 0, 0); + } + } + if(op == '+' || op == '-') + mask &= mode; + if(op == '-') + mode = ~mode; + return (1, mask, mode); +} diff --git a/appl/cmd/cleanname.b b/appl/cmd/cleanname.b new file mode 100644 index 00000000..0883e600 --- /dev/null +++ b/appl/cmd/cleanname.b @@ -0,0 +1,45 @@ +implement Cleanname; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "names.m"; + names: Names; + +include "arg.m"; + +Cleanname: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + names = load Names Names->PATH; + arg := load Arg Arg->PATH; + + dir: string; + arg->init(args); + arg->setusage("cleanname [-d pwd] name ..."); + while((o := arg->opt()) != 0) + case o { + 'd' => + dir = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + for(; args != nil; args = tl args){ + n := hd args; + if(dir != nil && n != nil && n[0] != '/' && n[0] != '#') + n = dir+"/"+n; + sys->print("%s\n", names->cleanname(n)); # %q? + } +} diff --git a/appl/cmd/cmp.b b/appl/cmd/cmp.b new file mode 100644 index 00000000..ce631b93 --- /dev/null +++ b/appl/cmd/cmp.b @@ -0,0 +1,151 @@ +implement Cmp; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + +include "arg.m"; + +BUF: con 65536; +stderr: ref Sys->FD; + +Cmp: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + lflag := Lflag := sflag := 0; + buf1 := array[BUF] of byte; + buf2 := array[BUF] of byte; + + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(stderr, "cmp: cannot load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + arg->init(args); + while((op := arg->opt()) != 0) + case op { + 'l' => lflag = 1; + 'L' => Lflag = 1; + 's' => sflag = 1; + * => usage(); + } + args = arg->argv(); + arg = nil; + if(args == nil) + usage(); + + if(len args < 2) + usage(); + name1 := hd args; + args = tl args; + + if((f1 := sys->open(name1, Sys->OREAD)) == nil){ + sys->fprint(stderr, "cmp: can't open %s: %r\n",name1); + raise "fail:open"; + } + name2 := hd args; + args = tl args; + + if((f2 := sys->open(name2, Sys->OREAD)) == nil){ + sys->fprint(stderr, "cmp: can't open %s: %r\n",name2); + raise "fail:open"; + } + + if(args != nil){ + o := big hd args; + if(sys->seek(f1, o, 0) < big 0){ + sys->fprint(stderr, "cmp: seek by offset1 failed: %r\n"); + raise "fail:seek 1"; + } + args = tl args; + } + + if(args != nil){ + o := big hd args; + if(sys->seek(f2, o, 0) < big 0){ + sys->fprint(stderr, "cmp: seek by offset2 failed: %r"); + raise "fail:seek 2"; + } + args = tl args; + } + if(args != nil) + usage(); + nc := big 1; + l := big 1; + diff := 0; + b1, b2: array of byte; + for(;;){ + if(len b1 == 0){ + nr := sys->read(f1, buf1, BUF); + if(nr < 0){ + if(!sflag) + sys->print("error on %s after %bd bytes\n", name1, nc-big 1); + raise "fail:read error"; + } + b1 = buf1[0: nr]; + } + if(len b2 == 0){ + nr := sys->read(f2, buf2, BUF); + if(nr < 0){ + if(!sflag) + sys->print("error on %s after %bd bytes\n", name2, nc-big 1); + raise "fail:read error"; + } + b2 = buf2[0: nr]; + } + n := len b2; + if(n > len b1) + n = len b1; + if(n == 0) + break; + for(i:=0; iprint("%s %s differ: char %bd", name1, name2, nc+big i); + if(Lflag) + sys->print(" line %bd\n", l); + else + sys->print("\n"); + } + raise "fail:differ"; + } + sys->print("%6bd 0x%.2x 0x%.2x\n", nc+big i, int b1[i], int b2[i]); + diff = 1; + } + } + nc += big n; + b1 = b1[n:]; + b2 = b2[n:]; + } + if(len b1 != len b2) { + nc--; + if(len b1 > len b2) + sys->print("EOF on %s after %bd bytes\n", name2, nc); + else + sys->print("EOF on %s after %bd bytes\n", name1, nc); + raise "fail:EOF"; + } + if(diff) + raise "fail:differ"; + exit; +} + + +usage() +{ + sys->fprint(stderr, "Usage: cmp [-lsL] file1 file2 [offset1 [offset2] ]\n"); + raise "fail:usage"; +} diff --git a/appl/cmd/comm.b b/appl/cmd/comm.b new file mode 100755 index 00000000..1e56310e --- /dev/null +++ b/appl/cmd/comm.b @@ -0,0 +1,124 @@ +implement Comm; + +# Copyright © 2002 Lucent Technologies Inc. +# Subject to the Lucent Public Licence 1.02 +# Limbo translation by Vita Nuova 2004; bug fixed. + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Comm: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +One, Two, Three: con 1<Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("comm [-123] file1 file2"); + while((c := arg->opt()) != 0){ + case c { + '1' to '3' => + cols &= ~(1 << (c-'1')); + * => + arg->usage(); + } + } + args = arg->argv(); + if(len args != 2) + arg->usage(); + arg = nil; + + if((cols & One) == 0){ + ldr[1] = ""; + ldr[2] = ldr[2][1:]; + } + if((cols & Two) == 0) + ldr[2] = ldr[2][1:]; + + ib1 := openfil(hd args); + ib2 := openfil(hd tl args); + if((lb1 := ib1.gets('\n')) == nil){ + if((lb2 := ib2.gets('\n')) == nil) + exit; + copy(ib2, lb2, 2); + } + if((lb2 := ib2.gets('\n')) == nil) + copy(ib1, lb1, 1); + for(;;) + case compare(lb1, lb2) { + 0 => + wr(lb1, 3); + if((lb1 = ib1.gets('\n')) == nil){ + if((lb2 = ib2.gets('\n')) == nil) + exit; + copy(ib2, lb2, 2); + } + if((lb2 = ib2.gets('\n')) == nil) + copy(ib1, lb1, 1); + 1 => + wr(lb1, 1); + if((lb1 = ib1.gets('\n')) == nil) + copy(ib2, lb2, 2); + 2 => + wr(lb2, 2); + if((lb2 = ib2.gets('\n')) == nil) + copy(ib1, lb1, 1); + } +} + +wr(str: string, n: int) +{ + if(cols & (1<<(n-1))) + sys->print("%s%s", ldr[n-1], str); +} + +copy(ibuf: ref Iobuf, lbuf: string, n: int) +{ + do + wr(lbuf, n); + while((lbuf = ibuf.gets('\n')) != nil); + exit; +} + +compare(a: string, b: string): int +{ + for(i := 0; i < len a; i++){ + if(i >= len b || a[i] < b[i]) + return 1; + if(a[i] != b[i]) + return 2; + } + if(i == len b) + return 0; + return 2; +} + +openfil(s: string): ref Iobuf +{ + if(s == "-") + b := bufio->fopen(sys->fildes(0), Bufio->OREAD); + else + b = bufio->open(s, Bufio->OREAD); + if(b != nil) + return b; + sys->fprint(sys->fildes(2), "comm: cannot open %s: %r\n", s); + raise "fail:open"; +} + diff --git a/appl/cmd/cook.b b/appl/cmd/cook.b new file mode 100644 index 00000000..0d333a4d --- /dev/null +++ b/appl/cmd/cook.b @@ -0,0 +1,1924 @@ +implement Cook; + +include "sys.m"; + sys: Sys; + FD: import Sys; + +include "draw.m"; + draw: Draw; + +include "bufio.m"; + B: Bufio; + Iobuf: import B; + +include "string.m"; + S: String; + splitl, splitr, splitstrl, drop, take, in, prefix, tolower : import S; + +include "brutus.m"; + Size6, Size8, Size10, Size12, Size16, NSIZE, + Roman, Italic, Bold, Type, NFONT, NFONTTAG, + Example, Caption, List, Listelem, Label, Labelref, + Exercise, Heading, Nofill, Author, Title, + Index, Indextopic, + DefFont, DefSize, TitleFont, TitleSize, HeadingFont, HeadingSize: import Brutus; + +# following are needed for types in brutusext.m +include "tk.m"; + tk: Tk; +include "tkclient.m"; + +include "brutusext.m"; + SGML, Text, Par, Extension, Float, Special, Celem, + FLatex, FLatexProc, FLatexBook, FLatexPart, FLatexSlides, FHtml: import Brutusext; + +include "strinttab.m"; + T: StringIntTab; + +Cook: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +# keep this sorted by name +tagstringtab := array[] of { T->StringInt + ("Author", Author), + ("Bold.10", Bold*NSIZE + Size10), + ("Bold.12", Bold*NSIZE + Size12), + ("Bold.16", Bold*NSIZE + Size16), + ("Bold.6", Bold*NSIZE + Size6), + ("Bold.8", Bold*NSIZE + Size8), + ("Caption", Caption), + ("Example", Example), + ("Exercise", Exercise), + ("Extension", Extension), + ("Float", Float), + ("Heading", Heading), + ("Index", Index), + ("Index-topic", Indextopic), + ("Italic.10", Italic*NSIZE + Size10), + ("Italic.12", Italic*NSIZE + Size12), + ("Italic.16", Italic*NSIZE + Size16), + ("Italic.6", Italic*NSIZE + Size6), + ("Italic.8", Italic*NSIZE + Size8), + ("Label", Label), + ("Label-ref", Labelref), + ("List", List), + ("List-elem", Listelem), + ("No-fill", Nofill), + ("Par", Par), + ("Roman.10", Roman*NSIZE + Size10), + ("Roman.12", Roman*NSIZE + Size12), + ("Roman.16", Roman*NSIZE + Size16), + ("Roman.6", Roman*NSIZE + Size6), + ("Roman.8", Roman*NSIZE + Size8), + ("SGML", SGML), + ("Title", Title), + ("Type.10", Type*NSIZE + Size10), + ("Type.12", Type*NSIZE + Size12), + ("Type.16", Type*NSIZE + Size16), + ("Type.6", Type*NSIZE + Size6), + ("Type.8", Type*NSIZE + Size8), +}; + +# This table must be sorted +fmtstringtab := array[] of { T->StringInt + ("html", FHtml), + ("latex", FLatex), + ("latexbook", FLatexBook), + ("latexpart", FLatexPart), + ("latexproc", FLatexProc), + ("latexslides", FLatexSlides), +}; + +Transtab: adt +{ + ch: int; + trans: string; +}; + +# Order doesn't matter for these table + +ltranstab := array[] of { Transtab + ('$', "\\textdollar{}"), + ('&', "\\&"), + ('%', "\\%"), + ('#', "\\#"), + ('_', "\\textunderscore{}"), + ('{', "\\{"), + ('}', "\\}"), + ('~', "\\textasciitilde{}"), + ('^', "\\textasciicircum{}"), + ('\\', "\\textbackslash{}"), + ('+', "\\textplus{}"), + ('=', "\\textequals{}"), + ('|', "\\textbar{}"), + ('<', "\\textless{}"), + ('>', "\\textgreater{}"), + (' ', "~"), + ('-', "-"), # needs special case ligature treatment + ('\t', " "), # needs special case treatment +}; + +htranstab := array[] of { Transtab + ('α', "α"), + ('Æ', "Æ"), + ('Á', "Á"), + ('Â', "Â"), + ('À', "À"), + ('Å', "Å"), + ('Ã', "Ã"), + ('Ä', "Ä"), + ('Ç', "Ç"), + ('Ð', "Ð"), + ('É', "É"), + ('Ê', "Ê"), + ('È', "È"), + ('Ë', "Ë"), + ('Í', "Í"), + ('Î', "Î"), + ('Ì', "Ì"), + ('Ï', "Ï"), + ('Ñ', "Ñ"), + ('Ó', "Ó"), + ('Ô', "Ô"), + ('Ò', "Ò"), + ('Ø', "Ø"), + ('Õ', "Õ"), + ('Ö', "Ö"), + ('Þ', "Þ"), + ('Ú', "Ú"), + ('Û', "Û"), + ('Ù', "Ù"), + ('Ü', "Ü"), + ('Ý', "Ý"), + ('æ', "&aElig;"), + ('á', "á"), + ('â', "â"), + ('à', "à"), + ('α', "α"), + ('&', "&"), + ('å', "å"), + ('ã', "ã"), + ('ä', "ä"), + ('β', "β"), + ('ç', "ç"), + ('⋯', "&cdots;"), + ('χ', "χ"), + ('©', "©"), + ('⋱', "&ddots;"), + ('δ', "δ"), + ('é', "é"), + ('ê', "ê"), + ('è', "è"), + ('—', "&emdash;"), + (' ', " "), + ('–', "&endash;"), + ('ε', "ε"), + ('η', "η"), + ('ð', "ð"), + ('ë', "ë"), + ('γ', "γ"), + ('>', ">"), + ('í', "í"), + ('î', "î"), + ('ì', "ì"), + ('ι', "ι"), + ('ï', "ï"), + ('κ', "κ"), + ('λ', "λ"), + ('…', "&ldots;"), + ('<', "<"), + ('μ', "μ"), + (' ', " "), + ('ñ', "ñ"), + ('ν', "ν"), + ('ó', "ó"), + ('ô', "ô"), + ('ò', "ò"), + ('ω', "ω"), + ('ο', "ο"), + ('ø', "ø"), + ('õ', "õ"), + ('ö', "ö"), + ('φ', "φ"), + ('π', "π"), + ('ψ', "ψ"), + (' ', "&quad;"), + ('"', """), + ('®', "®"), + ('ρ', "ρ"), + ('­', "­"), + ('σ', "σ"), + ('ß', "ß"), + ('τ', "τ"), + ('θ', "θ"), + (' ', " "), + ('þ', "þ"), + ('™', "™"), + ('ú', "ú"), + ('û', "û"), + ('ù', "ù"), + ('υ', "υ"), + ('ü', "ü"), + ('∈', "ϵ"), + ('ϕ', "ϕ"), + ('ϖ', "ϖ"), + ('ϱ', "ϱ"), + ('⋮', "&vdots;"), + ('ς', "&vsigma;"), + ('ϑ', "&vtheta;"), + ('ξ', "ξ"), + ('ý', "ý"), + ('ÿ', "ÿ"), + ('ζ', "ζ"), + ('−', "-"), +}; + +# For speedy lookups of ascii char translation, use asciitrans. +# It should be initialized by ascii elements from one of above tables +asciitrans := array[128] of string; + +stderr: ref FD; +infilename := ""; +outfilename := ""; +linenum := 0; +fin : ref Iobuf = nil; +fout : ref Iobuf = nil; +debug := 0; +fmt := FLatex; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + S = load String String->PATH; + B = load Bufio Bufio->PATH; + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + T = load StringIntTab StringIntTab->PATH; + stderr = sys->fildes(2); + + for(argv = tl argv; argv != nil; ) { + s := hd argv; + tlargv := tl argv; + case s { + "-f" => + if(tlargv == nil) + usage(); + fnd: int; + (fnd, fmt) = T->lookup(fmtstringtab, hd(tlargv)); + if(!fnd) { + sys->fprint(stderr, "unknown format: %s\n", hd(tlargv)); + exit; + } + argv = tlargv; + "-o" => + if(tlargv == nil) + usage(); + outfilename = hd(tlargv); + argv = tlargv; + "-d" => + debug = 1; + "-dd" => + debug = 2; + * => + if(tlargv == nil) + infilename = s; + else + usage(); + } + argv = tl argv; + } + if(infilename == "") { + fin = B->fopen(sys->fildes(0), sys->OREAD); + infilename = ""; + } + else + fin = B->open(infilename, sys->OREAD); + if(fin == nil) { + sys->fprint(stderr, "cook: error opening %s: %r\n", infilename); + exit; + } + if(outfilename == "") { + fout = B->fopen(sys->fildes(1), sys->OWRITE); + outfilename = ""; + } + else + fout = B->create(outfilename, sys->OWRITE, 8r664); + if(fout == nil) { + sys->fprint(stderr, "cook: error creating %s: %r\n", outfilename); + exit; + } + line0 := fin.gets('\n'); + if(line0 != "\n") { + parse_err("not an SGML file\n"); + exit; + } + linenum = 1; + e := parse(SGML); + findpars(e, 1, nil); + e = delemptystrs(e); + (e, nil) = canonfonts(e, DefFont*NSIZE+DefSize, DefFont*NSIZE+DefSize); + mergeadjs(e); + findfloats(e); + cleanexts(e); + cleanpars(e); + if(debug) { + fout.puts("After Initial transformations:\n"); + printelem(e, "", 1); + fout.flush(); + } + case fmt { + FLatex or FLatexProc or FLatexBook or FLatexPart or FLatexSlides => + latexconv(e); + FHtml => + htmlconv(e); + } + fin.close(); + fout.close(); +} + +usage() +{ + sys->fprint(stderr, "Usage: cook [-f (latex|html)] [-o outfile] [infile]\n"); + exit; +} + +parse_err(msg: string) +{ + sys->fprint(stderr, "%s:%d: %s\n", infilename, linenum, msg); +} + +# Parse into elements. +# Assumes tags are balanced. +# String elements are split so that there is never an internal newline. +parse(id: int) : ref Celem +{ + els : ref Celem = nil; + elstail : ref Celem = nil; + for(;;) { + c := fin.getc(); + if(c == Bufio->EOF) { + if(id == SGML) + break; + else { + parse_err(sys->sprint("EOF while parsing %s", tagname(id))); + return nil; + } + } + if(c == '<') { + tag := ""; + start := 1; + i := 0; + for(;;) { + c = fin.getc(); + if(c == Bufio->EOF) { + parse_err("EOF in middle of tag"); + return nil; + } + if(c == '\n') { + linenum++; + parse_err("newline in middle of tag"); + break; + } + if(c == '>') + break; + if(i == 0 && c == '/') + start = 0; + else + tag[i++] = c; + } + (fnd, tid) := T->lookup(tagstringtab, tag); + if(!fnd) { + if(prefix("Extension ", tag)) { + el := ref Celem(Extension, tag[10:], nil, nil, nil, nil); + if(els == nil) { + els = el; + elstail = el; + } + else { + el.prev = elstail; + elstail.next = el; + elstail = el; + } + } + else + parse_err(sys->sprint("unknown tag <%s>\n", tag)); + continue; + } + if(start) { + el := parse(tid); + if(el == nil) + return nil; + if(els == nil) { + els = el; + elstail = el; + } + else { + el.prev = elstail; + elstail.next = el; + elstail = el; + } + } + else { + if(tid != id) { + parse_err(sys->sprint("<%s> ended by ", + tagname(id), tag)); + continue; + } + break; + } + } + else { + s := ""; + i := 0; + for(;;) { + if(c == Bufio->EOF) + break; + if(c == '<') { + fin.ungetc(); + break; + } + if(c == ';' && i >=3 && s[i-1] == 't' && s[i-2] == 'l' && s[i-3] == '&') { + i -= 2; + s[i-1] = '<'; + s = s[0:i]; + } + else + s[i++] = c; + if(c == '\n') { + linenum++; + break; + } + else + c = fin.getc(); + } + if(s != "") { + el := ref Celem(Text, s, nil, nil, nil, nil); + if(els == nil) { + els = el; + elstail = el; + } + else { + el.prev = elstail; + elstail.next = el; + elstail = el; + } + } + } + } + ans := ref Celem(id, "", els, nil, nil, nil); + if(els != nil) + els.parent = ans; + return ans; +} + +# Modify tree e so that blank lines become Par elements. +# Only do it if parize is set, and unset parize when descending into TExample's. +# Pass in most recent TString or TPar element, and return updated most-recent-TString/TPar. +# This function may set some TString strings to "" +findpars(e: ref Celem, parize: int, prevspe: ref Celem) : ref Celem +{ + while(e != nil) { + prevnl := 0; + prevpar := 0; + if(prevspe != nil) { + if(prevspe.tag == Text && len prevspe.s != 0 + && prevspe.s[(len prevspe.s)-1] == '\n') + prevnl = 1; + else if(prevspe.tag == Par) + prevpar = 1; + } + if(e.tag == Text) { + if(parize && (prevnl || prevpar) && e.s[0] == '\n') { + if(prevnl) + prevspe.s = prevspe.s[0 : (len prevspe.s)-1]; + e.tag = Par; + e.s = nil; + } + prevspe = e; + } + else { + nparize := parize; + if(e.tag == Example) + nparize = 0; + prevspe = findpars(e.contents, nparize, prevspe); + } + e = e.next; + } + return prevspe; +} + +# Delete any empty strings from e's tree and return modified e. +# Also, delete any entity that has empty contents, except the +# Par ones +delemptystrs(e: ref Celem) : ref Celem +{ + if(e.tag == Text) { + if(e.s == "") + return nil; + else + return e; + } + if(e.tag == Par || e.tag == Extension || e.tag == Special) + return e; + h := e.contents; + while(h != nil) { + hnext := h.next; + hh := delemptystrs(h); + if(hh == nil) + delete(h); + h = hnext; + } + if(e.contents == nil) + return nil; + return e; +} + +# Change tree under e so that any font elems contain only strings +# (by pushing the font changes down). +# Answer an be a list, so return beginning and end of list. +# Leave strings bare if font change would be to deffont, +# and adjust deffont appropriately when entering Title and +# Heading environments. +canonfonts(e: ref Celem, curfont, deffont: int) : (ref Celem, ref Celem) +{ + f := curfont; + head : ref Celem = nil; + tail : ref Celem = nil; + tocombine : ref Celem = nil; + if(e.tag == Text) { + if(f == deffont) { + head = e; + tail = e; + } + else { + head = ref Celem(f, nil, e, nil, nil, nil); + e.parent = head; + tail = head; + } + } + else if(e.contents == nil) { + head = e; + tail = e; + } + else if(e.tag < NFONTTAG) { + f = e.tag; + allstrings := 1; + for(g := e.contents; g != nil; g = g.next) { + if(g.tag != Text) + allstrings = 0; + tail = g; + } + if(allstrings) { + if(f == deffont) + head = e.contents; + else { + head = e; + tail = e; + } + } + } + if(head == nil) { + if(e.tag == Title) + deffont = TitleFont*NSIZE+TitleSize; + else if(e.tag == Heading) + deffont = HeadingFont*NSIZE+HeadingSize; + for(h := e.contents; h != nil; ) { + prev := h.prev; + next := h.next; + excise(h); + (e1, en) := canonfonts(h, f, deffont); + splicebetween(e1, en, prev, next); + if(prev == nil) + head = e1; + tail = en; + h = next; + } + tocombine = head; + if(e.tag >= NFONTTAG) { + e.contents = head; + head.parent = e; + head = e; + tail = e; + } + } + if(tocombine != nil) { + # combine adjacent font changes to same font + r := tocombine; + while(r != nil) { + if(r.tag < NFONTTAG && r.next != nil && r.next.tag == r.tag) { + for(v := r.next; v != nil; v = v.next) { + if(v.tag != r.tag) + break; + if(v == tail) + tail = r; + } + # now r up to, not including v, all change to same font + for(p := r.next; p != v; p = p.next) { + append(r.contents, p.contents); + } + r.next = v; + if(v != nil) + v.prev = r; + r = v; + } + else + r = r.next; + } + } + head.parent = nil; + return (head, tail); +} + +# Remove Pars that appear just before or just after Heading, Title, Examples, Extensions +# Really should worry about this happening at different nesting levels, but in +# practice this happens all at the same nesting level +cleanpars(e: ref Celem) +{ + for(h := e.contents; h != nil; h = h.next) { + cleanpars(h); + if(h.tag == Title || h.tag == Heading || h.tag == Example || h.tag == Extension) { + hp := h.prev; + hn := h.next; + if(hp !=nil && hp.tag == Par) + delete(hp); + if(hn != nil && hn.tag == Par) + delete(hn); + } + } +} + +# Remove a single tab if it appears before an Extension +cleanexts(e: ref Celem) +{ + for(h := e.contents; h != nil; h = h.next) { + cleanexts(h); + if(h.tag == Extension) { + hp := h.prev; + if(hp != nil && stringof(hp) == "\t") + delete(hp); + } + } +} + +mergeable := array[] of { List, Exercise, Caption,Index, Indextopic }; + +# Merge some adjacent elements (which were probably created separate +# because of font changes) +mergeadjs(e: ref Celem) +{ + for(h := e.contents; h != nil; h = h.next) { + hn := h.next; + domerge := 0; + if(hn != nil) { + for(i := 0; i < len mergeable; i++) { + mi := mergeable[i]; + if(h.tag == mi && hn.tag == mi) + domerge = 1; + } + } + if(domerge) { + append(h.contents, hn.contents); + delete(hn); + } + else + mergeadjs(h); + } +} + +# Find floats: they are paragraphs with Captions at the end. +findfloats(e: ref Celem) +{ + lastpar : ref Celem = nil; + for(h := e.contents; h != nil; h = h.next) { + if(h.tag == Par) + lastpar = h; + else if(h.tag == Caption) { + ne := ref Celem(Float, "", nil, nil, nil, nil); + if(lastpar == nil) + flhead := e.contents; + else + flhead = lastpar.next; + insertbefore(ne, flhead); + # now move flhead ... h into contents of ne + ne.contents = flhead; + flhead.parent = ne; + flhead.prev = nil; + ne.next = h.next; + if(ne.next != nil) + ne.next.prev = ne; + h.next = nil; + h = ne; + } + else + findfloats(h); + } +} + +insertbefore(e, ebefore: ref Celem) +{ + e.prev = ebefore.prev; + if(e.prev == nil) { + e.parent = ebefore.parent; + ebefore.parent = nil; + e.parent.contents = e; + } + else + e.prev.next = e; + e.next = ebefore; + ebefore.prev = e; +} + +insertafter(e, eafter: ref Celem) +{ + e.next = eafter.next; + if(e.next != nil) + e.next.prev = e; + e.prev = eafter; + eafter.next = e; +} + +# remove e from its list, leaving siblings disconnected +excise(e: ref Celem) +{ + next := e. next; + prev := e.prev; + e.next = nil; + e.prev = nil; + if(prev != nil) + prev.next = nil; + if(next != nil) + next.prev = nil; + e.parent = nil; +} + +splicebetween(e1, en, prev, next: ref Celem) +{ + if(prev != nil) + prev.next = e1; + e1.prev = prev; + en.next = next; + if(next != nil) + next.prev = en; +} + +append(e1, e2: ref Celem) +{ + e1last := last(e1); + e1last.next = e2; + e2.prev = e1last; + e2.parent = nil; +} + +last(e: ref Celem) : ref Celem +{ + if(e != nil) + while(e.next != nil) + e = e.next; + return e; +} + +succ(e: ref Celem) : ref Celem +{ + if(e == nil) + return nil; + if(e.next != nil) + return e.next; + return succ(e.parent); +} + +delete(e: ref Celem) +{ + ep := e.prev; + en := e.next; + eu := e.parent; + if(ep == nil) { + if(eu != nil) + eu.contents = en; + if(en != nil) + en.parent = eu; + } + else + ep.next = en; + if(en != nil) + en.prev = ep; +} + +# return string represented by e, peering through font changes +stringof(e: ref Celem) : string +{ + if(e != nil) { + if(e.tag == Text) + return e.s; + if(e.tag < NFONTTAG) + return stringof(e.contents); + } + return ""; +} + +# remove any initial whitespace from e and its sucessors, +dropwhite(e: ref Celem) +{ + if(e == nil) + return; + del := 0; + if(e.tag == Text) { + e.s = drop(e.s, " \t\n"); + if(e.s == "") + del = 1;; + } + else if(e.tag < NFONTTAG) { + dropwhite(e.contents); + if(e.contents == nil) + del = 1; + } + if(del) { + enext := e.next; + delete(e); + dropwhite(enext); + } + +} + +firstchar(e: ref Celem) : int +{ + s := stringof(e); + if(len s >= 1) + return s[0]; + return -1; +} + +lastchar(e: ref Celem) : int +{ + if(e == nil) + return -1; + while(e.next != nil) + e = e.next; + s := stringof(e); + if(len s >= 1) + return s[len s -1]; + return -1; +} + +tlookup(t: array of Transtab, v: int) : string +{ + n := len t; + for(i := 0; i < n; i++) + if(t[i].ch == v) + return t[i].trans; + return ""; +} + +initasciitrans(t: array of Transtab) +{ + n := len t; + for(i := 0; i < n; i++) { + c := t[i].ch; + if(c < 128) + asciitrans[c] = t[i].trans; + } +} + +tagname(id: int) : string +{ + name := T->revlookup(tagstringtab, id); + if(name == nil) + name = "_unknown_"; + return name; +} + +printelem(e: ref Celem, indent: string, recurse: int) +{ + fout.puts(indent); + if(debug > 1) { + fout.puts(sys->sprint("%x: ", e)); + if(e != nil && e.parent != nil) + fout.puts(sys->sprint("(parent %x): ", e.parent)); + } + if(e == nil) + fout.puts("NIL\n"); + else if(e.tag == Text || e.tag == Special || e.tag == Extension) { + if(e.tag == Special) + fout.puts("S"); + else if(e.tag == Extension) + fout.puts("E"); + fout.puts("«"); + fout.puts(e.s); + fout.puts("»\n"); + } + else { + name := tagname(e.tag); + fout.puts("<" + name + ">\n"); + if(recurse && e.contents != nil) + printelems(e.contents, indent + " ", recurse); + } +} + +printelems(els: ref Celem, indent: string, recurse: int) +{ + for(; els != nil; els = els.next) + printelem(els, indent, recurse); +} + +check(e: ref Celem, msg: string) +{ + err := checke(e); + if(err != "") { + fout.puts(msg + ": tree is inconsistent:\n" + err); + printelem(e, "", 1); + fout.flush(); + exit; + } +} + +checke(e: ref Celem) : string +{ + err := ""; + if(e.tag == SGML && e.next != nil) + err = sys->sprint("root %x has a next field\n", e); + ec := e.contents; + if(ec != nil) { + if(ec.parent != e) + err += sys->sprint("node %x contents %x has bad parent %x\n", e, ec, e.parent); + if(ec.prev != nil) + err += sys->sprint("node %x contents %x has non-nil prev %x\n", e, ec, e.prev); + p := ec; + for(h := ec.next; h != nil; h = h.next) { + if(h.prev != p) + err += sys->sprint("node %x comes after %x, but prev is %x\n", h, p, h.prev); + if(h.parent != nil) + err += sys->sprint("node %x, not first in siblings, has parent %x\n", h, h.parent); + p = h; + } + for(h = ec; h != nil; h = h.next) { + err2 := checke(h); + if(err2 != nil) + err += err2; + } + } + return err; +} + +# Translation to Latex + +# state bits +SLT, SLB, SLI, SLS6, SLS8, SLS12, SLS16, SLE, SLO, SLF : con (1< SLS6, + Roman*NSIZE+Size8 => SLS8, + Roman*NSIZE+Size10 => 0, + Roman*NSIZE+Size12 => SLS12, + Roman*NSIZE+Size16 => SLS16, + Italic*NSIZE+Size6 => SLI | SLS6, + Italic*NSIZE+Size8 => SLI | SLS8, + Italic*NSIZE+Size10 => SLI, + Italic*NSIZE+Size12 => SLI | SLS12, + Italic*NSIZE+Size16 => SLI | SLS16, + Bold*NSIZE+Size6 => SLB | SLS6, + Bold*NSIZE+Size8 => SLB | SLS8, + Bold*NSIZE+Size10 => SLB, + Bold*NSIZE+Size12 => SLB | SLS12, + Bold*NSIZE+Size16 => SLB | SLS16, + Type*NSIZE+Size6 => SLT | SLS6, + Type*NSIZE+Size8 => SLT | SLS8, + Type*NSIZE+Size10 => SLT, + Type*NSIZE+Size12 => SLT | SLS12, + Type*NSIZE+Size16 => SLT | SLS16 +}; + +lsizecmd := array[] of { "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large"}; +llinepos : int; +lslidenum : int; +LTABSIZE : con 4; + +latexconv(e: ref Celem) +{ + initasciitrans(ltranstab); + + case fmt { + FLatex or FLatexProc => + if(fmt == FLatex) { + fout.puts("\\documentclass{article}\n"); + fout.puts("\\def\\encodingdefault{T1}\n"); + } + else { + fout.puts("\\documentclass[10pt,twocolumn]{article}\n"); + fout.puts("\\def\\encodingdefault{T1}\n"); + fout.puts("\\usepackage{latex8}\n"); + fout.puts("\\bibliographystyle{latex8}\n"); + } + fout.puts("\\usepackage{times}\n"); + fout.puts("\\usepackage{brutus}\n"); + fout.puts("\\usepackage{unicode}\n"); + fout.puts("\\usepackage{epsf}\n"); + title := lfindtitle(e); + authors := lfindauthors(e); + abstract := lfindabstract(e); + fout.puts("\\begin{document}\n"); + if(title != nil) { + fout.puts("\\title{"); + llinepos = 0; + lconvl(title, 0); + fout.puts("}\n"); + if(authors != nil) { + fout.puts("\\author{"); + for(l := authors; l != nil; l = tl l) { + llinepos = 0; + lconvl(hd l, SLO|SLI); + if(tl l != nil) + fout.puts("\n\\and\n"); + } + fout.puts("}\n"); + } + fout.puts("\\maketitle\n"); + } + fout.puts("\\pagestyle{empty}\\thispagestyle{empty}\n"); + if(abstract != nil) { + if(fmt == FLatexProc) { + fout.puts("\\begin{abstract}\n"); + llinepos = 0; + lconvl(abstract, 0); + fout.puts("\\end{abstract}\n"); + } + else { + fout.puts("\\section*{Abstract}\n"); + llinepos = 0; + lconvl(abstract, 0); + } + } + FLatexBook => + fout.puts("\\documentclass{ibook}\n"); + fout.puts("\\usepackage{brutus}\n"); + fout.puts("\\usepackage{epsf}\n"); + fout.puts("\\begin{document}\n"); + FLatexSlides => + fout.puts("\\documentclass[portrait]{seminar}\n"); + fout.puts("\\def\\encodingdefault{T1}\n"); + fout.puts("\\usepackage{times}\n"); + fout.puts("\\usepackage{brutus}\n"); + fout.puts("\\usepackage{unicode}\n"); + fout.puts("\\usepackage{epsf}\n"); + fout.puts("\\centerslidesfalse\n"); + fout.puts("\\slideframe{none}\n"); + fout.puts("\\slidestyle{empty}\n"); + fout.puts("\\pagestyle{empty}\n"); + fout.puts("\\begin{document}\n"); + lslidenum = 0; + } + + llinepos = 0; + if(e.tag == SGML) + lconvl(e.contents, 0); + + if(fmt == FLatexSlides && lslidenum > 0) + fout.puts("\\vfill\\end{slide*}\n"); + if(fmt != FLatexPart) + fout.puts("\\end{document}\n"); +} + +lconvl(el: ref Celem, state: int) +{ + for(e := el; e != nil; e = e.next) { + tag := e.tag; + op := ""; + cl := ""; + parlike := 1; + nstate := state; + if(tag < NFONTTAG) { + parlike = 0; + ss := lftagtostate[tag]; + if((state & SLFONTMASK) != ss) { + t := state & SLT; + b := state & SLB; + i := state & SLI; + newt := ss & SLT; + newb := ss & SLB; + newi := ss & SLI; + op = "{"; + cl = "}"; + if(t && !newt) + op += "\\rmfamily"; + else if(!t && newt) + op += "\\ttfamily"; + if(b && !newb) + op += "\\mdseries"; + else if(!b && newb) + op += "\\bfseries"; + if(i && !newi) + op += "\\upshape"; + else if(!i && newi) { + op += "\\itshape"; + bc := lastchar(e.contents); + ac := firstchar(e.next); + if(bc != -1 && bc != ' ' && bc != '\n' && ac != -1 && ac != '.' && ac != ',') + cl = "\\/}"; + } + if((state & SLSIZEMASK) != (ss & SLSIZEMASK)) { + nsize := 2; + if(ss & SLS6) + nsize = 0; + else if(ss & SLS8) + nsize = 1; + else if(ss & SLS12) + nsize = 3; + else if(ss & SLS16) + nsize = 4; + # examples shrunk one size + if((state & SLE) && nsize > 0) + nsize--; + op += lsizecmd[nsize]; + } + fc := firstchar(e.contents); + if(fc == ' ') + op += "{}"; + else + op += " "; + nstate = (state & ~SLFONTMASK) | ss; + } + } + else + case tag { + Text => + parlike = 0; + if(state & SLO) { + asciitrans[' '] = "\\ "; + asciitrans['\n'] = "\\\\\n"; + } + s := e.s; + n := len s; + for(k := 0; k < n; k++) { + c := s[k]; + x := ""; + if(c < 128) + x = asciitrans[c]; + else + x = tlookup(ltranstab, c); + if(x == "") { + fout.putc(c); + if(c == '\n') + llinepos = 0; + else + llinepos++; + } + else { + # split up ligatures + if(c == '-' && k < n-1 && s[k+1] == '-') + x = "-{}"; + # Avoid the 'no line to end here' latex error + if((state&SLO) && c == '\n' && llinepos == 0) + fout.puts("\\ "); + else if((state&SLO) && c == '\t') { + nspace := LTABSIZE - llinepos%LTABSIZE; + llinepos += nspace; + while(nspace-- > 0) + fout.puts("\\ "); + + } + else { + fout.puts(x); + if(x[len x - 1] == '\n') + llinepos = 0; + else + llinepos++; + } + } + } + if(state & SLO) { + asciitrans[' '] = nil; + asciitrans['\n'] = nil; + } + Example => + if(!(state&SLE)) { + op = "\\begin{example}"; + cl = "\\end{example}\\noindent "; + nstate |= SLE | SLO; + } + List => + (n, bigle) := lfindbigle(e.contents); + if(n <= 2) { + op = "\\begin{itemize}\n"; + cl = "\\end{itemize}"; + } + else { + fout.puts("\\begin{itemizew}{"); + lconvl(bigle.contents, nstate); + op = "}\n"; + cl = "\\end{itemizew}"; + } + Listelem => + op = "\\item[{"; + cl = "}]"; + Heading => + if(fmt == FLatexProc) + op = "\n\\Section{"; + else + op = "\n\\section{"; + cl = "}\n"; + nstate = (state & ~SLFONTMASK) | (SLB | SLS12); + Nofill => + op = "\\begin{nofill}"; + cl = "\\end{nofill}\\noindent "; + nstate |= SLO; + Title => + if(fmt == FLatexSlides) { + op = "\\begin{slide*}\n" + + "\\begin{center}\\Large\\bfseries "; + if(lslidenum > 0) + op = "\\vfill\\end{slide*}\n" + op; + cl = "\\end{center}\n"; + lslidenum++; + } + else { + if(stringof(e.contents) == "Index") { + op = "\\printindex\n"; + e.contents = nil; + } + else { + op = "\\chapter{"; + cl = "}\n"; + } + } + nstate = (state & ~SLFONTMASK) | (SLB | SLS16); + Par => + op = "\n\\par\n"; + while(e.next != nil && e.next.tag == Par) + e = e.next; + Extension => + e.contents = convextension(e.s); + if(e.contents != nil) + e.contents.parent = e; + Special => + fout.puts(e.s); + Float => + if(!(state&SLF)) { + isfig := lfixfloat(e); + if(isfig) { + op = "\\begin{figure}\\begin{center}\\leavevmode "; + cl = "\\end{center}\\end{figure}"; + } + else { + op = "\\begin{table}\\begin{center}\\leavevmode "; + cl = "\\end{center}\\end{table}"; + } + nstate |= SLF; + } + Caption=> + if(state&SLF) { + op = "\\caption{"; + cl = "}"; + nstate = (state & ~SLFONTMASK) | SLS8; + } + else { + op = "\\begin{center}"; + cl = "\\end{center}"; + } + Label or Labelref => + parlike = 0; + if(tag == Label) + op = "\\label"; + else + op = "\\ref"; + cl = "{" + stringof(e.contents) + "}"; + e.contents = nil; + Exercise => + lfixexercise(e); + op = "\\begin{exercise}"; + cl = "\\end{exercise}"; + Index or Indextopic => + parlike = 0; + if(tag == Index) + lconvl(e.contents, nstate); + fout.puts("\\showidx{"); + lconvl(e.contents, nstate); + fout.puts("}"); + lconvindex(e.contents, nstate); + e.contents = nil; + } + if(op != "") + fout.puts(op); + if(e.contents != nil) { + if(parlike) + llinepos = 0; + lconvl(e.contents, nstate); + if(parlike) + llinepos = 0; + } + if(cl != "") + fout.puts(cl); + } +} + +lfixfloat(e: ref Celem) : int +{ + dropwhite(e.contents); + fstart := e.contents; + fend := last(fstart); + hasfig := 0; + hastab := 0; + if(fend.tag == Caption) { + dropwhite(fend.prev); + if(fend.prev != nil && stringof(fstart) == "\t") + delete(fend.prev); + # If fend.contents is "YYY "