From 131499a07c5e2e3cc3bc6d71d2de13ff95972d42 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Thu, 3 Oct 2024 05:32:32 +1000 Subject: [PATCH] vendor updates - twapi tcllib --- .../{twapi4.7.2 => twapi-5.0b1}/LICENSE | 58 +- src/vendorlib_tcl8/twapi-5.0b1/README.md | 73 + .../{twapi4.7.2 => twapi-5.0b1}/account.tcl | 2320 +- .../{twapi4.7.2 => twapi-5.0b1}/adsi.tcl | 54 +- .../{twapi4.7.2 => twapi-5.0b1}/apputil.tcl | 228 +- .../{twapi4.7.2 => twapi-5.0b1}/base.tcl | 3749 +- .../{twapi4.7.2 => twapi-5.0b1}/clipboard.tcl | 508 +- .../{twapi4.7.2 => twapi-5.0b1}/com.tcl | 8476 +- .../{twapi4.7.2 => twapi-5.0b1}/console.tcl | 1472 +- .../{twapi4.7.2 => twapi-5.0b1}/crypto.tcl | 6913 +- .../{twapi4.7.2 => twapi-5.0b1}/device.tcl | 1248 +- .../{twapi4.7.2 => twapi-5.0b1}/etw.tcl | 2780 +- .../{twapi4.7.2 => twapi-5.0b1}/eventlog.tcl | 782 +- .../{twapi4.7.2 => twapi-5.0b1}/evt.tcl | 1436 +- .../{twapi4.7.2 => twapi-5.0b1}/handle.tcl | 472 +- .../{twapi4.7.2 => twapi-5.0b1}/input.tcl | 1246 +- .../{twapi4.7.2 => twapi-5.0b1}/msi.tcl | 835 +- .../{twapi4.7.2 => twapi-5.0b1}/mstask.tcl | 1490 +- .../multimedia.tcl | 150 +- .../{twapi4.7.2 => twapi-5.0b1}/namedpipe.tcl | 206 +- .../{twapi4.7.2 => twapi-5.0b1}/network.tcl | 2248 +- .../{twapi4.7.2 => twapi-5.0b1}/nls.tcl | 934 +- .../{twapi4.7.2 => twapi-5.0b1}/os.tcl | 2426 +- .../{twapi4.7.2 => twapi-5.0b1}/pdh.tcl | 1968 +- src/vendorlib_tcl8/twapi-5.0b1/pkgIndex.tcl | 100 + .../{twapi4.7.2 => twapi-5.0b1}/power.tcl | 272 +- .../{twapi4.7.2 => twapi-5.0b1}/printer.tcl | 116 +- .../{twapi4.7.2 => twapi-5.0b1}/process.tcl | 4056 +- .../{twapi4.7.2 => twapi-5.0b1}/rds.tcl | 382 +- .../{twapi4.7.2 => twapi-5.0b1}/registry.tcl | 980 +- .../{twapi4.7.2 => twapi-5.0b1}/resource.tcl | 916 +- .../{twapi4.7.2 => twapi-5.0b1}/security.tcl | 4777 +- .../{twapi4.7.2 => twapi-5.0b1}/service.tcl | 2374 +- .../{twapi4.7.2 => twapi-5.0b1}/share.tcl | 1932 +- .../{twapi4.7.2 => twapi-5.0b1}/shell.tcl | 1254 +- .../{twapi4.7.2 => twapi-5.0b1}/sspi.tcl | 1602 +- .../{twapi4.7.2 => twapi-5.0b1}/storage.tcl | 1232 +- .../{twapi4.7.2 => twapi-5.0b1}/synch.tcl | 188 +- .../{twapi4.7.2 => twapi-5.0b1}/tls.tcl | 2609 +- .../{twapi4.7.2 => twapi-5.0b1}/twapi.tcl | 1713 +- .../{twapi4.7.2 => twapi-5.0b1}/ui.tcl | 2860 +- .../{twapi4.7.2 => twapi-5.0b1}/win.tcl | 262 +- .../twapi-5.0b1/win32-ix86/tcl9twapi50b1.dll | Bin 0 -> 530432 bytes .../twapi-5.0b1/win32-ix86/twapi50b1t.dll | Bin 0 -> 531456 bytes .../win32-x86_64/tcl9twapi50b1.dll | Bin 0 -> 658432 bytes .../twapi-5.0b1/win32-x86_64/twapi50b1t.dll | Bin 0 -> 652288 bytes .../{twapi4.7.2 => twapi-5.0b1}/winlog.tcl | 608 +- .../{twapi4.7.2 => twapi-5.0b1}/winsta.tcl | 226 +- .../{twapi4.7.2 => twapi-5.0b1}/wmi.tcl | 446 +- src/vendorlib_tcl8/twapi-5.0b1/wts.tcl | 64 + src/vendorlib_tcl8/twapi4.7.2/metoo.tcl | 605 - src/vendorlib_tcl8/twapi4.7.2/pkgIndex.tcl | 119 - src/vendorlib_tcl8/twapi4.7.2/twapi472.dll | Bin 610816 -> 0 bytes src/vendorlib_tcl8/twapi4.7.2/twapi_entry.tcl | 11 - .../tcllib2.0/0compatibility/pkgIndex.tcl | 8 + src/vendorlib_tcl9/tcllib2.0/aes/aes.tcl | 625 + src/vendorlib_tcl9/tcllib2.0/aes/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/amazon-s3/S3.tcl | 1960 + .../tcllib2.0/amazon-s3/pkgIndex.tcl | 9 + .../tcllib2.0/amazon-s3/xsxp.tcl | 254 + src/vendorlib_tcl9/tcllib2.0/asn/asn.tcl | 1580 + src/vendorlib_tcl9/tcllib2.0/asn/pkgIndex.tcl | 4 + .../tcllib2.0/base32/base32.tcl | 180 + .../tcllib2.0/base32/base32_c.tcl | 254 + .../tcllib2.0/base32/base32_tcl.tcl | 73 + .../tcllib2.0/base32/base32core.tcl | 134 + .../tcllib2.0/base32/base32hex.tcl | 182 + .../tcllib2.0/base32/base32hex_c.tcl | 254 + .../tcllib2.0/base32/base32hex_tcl.tcl | 79 + .../tcllib2.0/base32/pkgIndex.tcl | 4 + .../tcllib2.0/base64/ascii85.tcl | 270 + .../tcllib2.0/base64/base64.tcl | 411 + .../tcllib2.0/base64/base64c.tcl | 19 + .../tcllib2.0/base64/pkgIndex.tcl | 5 + .../tcllib2.0/base64/uuencode.tcl | 337 + .../tcllib2.0/base64/yencode.tcl | 309 + src/vendorlib_tcl9/tcllib2.0/bee/bee.tcl | 999 + src/vendorlib_tcl9/tcllib2.0/bee/pkgIndex.tcl | 4 + src/vendorlib_tcl9/tcllib2.0/bench/bench.tcl | 556 + .../tcllib2.0/bench/bench_read.tcl | 162 + .../tcllib2.0/bench/bench_wcsv.tcl | 101 + .../tcllib2.0/bench/bench_wtext.tcl | 165 + .../tcllib2.0/bench/libbench.tcl | 561 + .../tcllib2.0/bench/pkgIndex.tcl | 7 + .../tcllib2.0/bibtex/bibtex.tcl | 501 + .../tcllib2.0/bibtex/pkgIndex.tcl | 2 + .../tcllib2.0/blowfish/blowfish.tcl | 755 + .../tcllib2.0/blowfish/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/cache/async.tcl | 185 + .../tcllib2.0/cache/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/clay/clay.tcl | 2227 + .../tcllib2.0/clay/pkgIndex.tcl | 3 + .../tcllib2.0/clock/iso8601.tcl | 280 + .../tcllib2.0/clock/pkgIndex.tcl | 3 + .../tcllib2.0/clock/rfc2822.tcl | 214 + .../tcllib2.0/cmdline/cmdline.tcl | 933 + .../tcllib2.0/cmdline/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/comm/comm.tcl | 1806 + .../tcllib2.0/comm/pkgIndex.tcl | 2 + .../tcllib2.0/control/ascaller.tcl | 72 + .../tcllib2.0/control/assert.tcl | 91 + .../tcllib2.0/control/control.tcl | 24 + src/vendorlib_tcl9/tcllib2.0/control/do.tcl | 81 + .../tcllib2.0/control/no-op.tcl | 14 + .../tcllib2.0/control/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/control/tclIndex | 18 + .../tcllib2.0/coroutine/coro_auto.tcl | 403 + .../tcllib2.0/coroutine/coroutine.tcl | 495 + .../tcllib2.0/coroutine/pkgIndex.tcl | 5 + .../tcllib2.0/counter/counter.tcl | 1263 + .../tcllib2.0/counter/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/crc/cksum.tcl | 200 + src/vendorlib_tcl9/tcllib2.0/crc/crc16.tcl | 606 + src/vendorlib_tcl9/tcllib2.0/crc/crc32.tcl | 336 + src/vendorlib_tcl9/tcllib2.0/crc/crc32c.tcl | 87 + src/vendorlib_tcl9/tcllib2.0/crc/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/crc/sum.tcl | 223 + src/vendorlib_tcl9/tcllib2.0/crc/sumc.tcl | 84 + src/vendorlib_tcl9/tcllib2.0/cron/cron.tcl | 620 + .../tcllib2.0/cron/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/csv/csv.tcl | 787 + src/vendorlib_tcl9/tcllib2.0/csv/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/debug/caller.tcl | 97 + src/vendorlib_tcl9/tcllib2.0/debug/debug.tcl | 306 + .../tcllib2.0/debug/heartbeat.tcl | 68 + .../tcllib2.0/debug/pkgIndex.tcl | 5 + .../tcllib2.0/debug/timestamp.tcl | 47 + src/vendorlib_tcl9/tcllib2.0/defer/defer.tcl | 120 + .../tcllib2.0/defer/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/des/des.tcl | 272 + src/vendorlib_tcl9/tcllib2.0/des/pkgIndex.tcl | 7 + src/vendorlib_tcl9/tcllib2.0/des/tcldes.tcl | 1089 + src/vendorlib_tcl9/tcllib2.0/des/tcldesjr.tcl | 1055 + .../tcllib2.0/dicttool/dicttool.tcl | 155 + .../tcllib2.0/dicttool/pkgIndex.tcl | 1 + src/vendorlib_tcl9/tcllib2.0/dns/dns.tcl | 1526 + src/vendorlib_tcl9/tcllib2.0/dns/ip.tcl | 558 + src/vendorlib_tcl9/tcllib2.0/dns/ipMore.tcl | 1279 + src/vendorlib_tcl9/tcllib2.0/dns/ipMoreC.tcl | 243 + src/vendorlib_tcl9/tcllib2.0/dns/msgs/en.msg | 8 + src/vendorlib_tcl9/tcllib2.0/dns/pkgIndex.tcl | 7 + src/vendorlib_tcl9/tcllib2.0/dns/resolv.tcl | 249 + src/vendorlib_tcl9/tcllib2.0/dns/spf.tcl | 528 + .../tcllib2.0/docstrip/docstrip.tcl | 163 + .../tcllib2.0/docstrip/docstrip_util.tcl | 653 + .../tcllib2.0/docstrip/pkgIndex.tcl | 21 + src/vendorlib_tcl9/tcllib2.0/doctools/api.tcl | 31 + .../tcllib2.0/doctools/api_idx.tcl | 26 + .../tcllib2.0/doctools/api_toc.tcl | 26 + .../tcllib2.0/doctools/changelog.tcl | 278 + .../tcllib2.0/doctools/checker.tcl | 734 + .../tcllib2.0/doctools/checker_idx.tcl | 207 + .../tcllib2.0/doctools/checker_toc.tcl | 214 + src/vendorlib_tcl9/tcllib2.0/doctools/cvs.tcl | 134 + .../tcllib2.0/doctools/docidx.tcl | 961 + .../tcllib2.0/doctools/doctoc.tcl | 967 + .../tcllib2.0/doctools/doctools.tcl | 1362 + .../tcllib2.0/doctools/mpformats/_common.tcl | 316 + .../tcllib2.0/doctools/mpformats/_html.tcl | 199 + .../doctools/mpformats/_idx_common.tcl | 31 + .../doctools/mpformats/_markdown.tcl | 216 + .../tcllib2.0/doctools/mpformats/_nroff.tcl | 182 + .../tcllib2.0/doctools/mpformats/_text.tcl | 184 + .../doctools/mpformats/_text_bullets.tcl | 30 + .../doctools/mpformats/_text_ccore.tcl | 141 + .../doctools/mpformats/_text_cstack.tcl | 31 + .../doctools/mpformats/_text_dlist.tcl | 278 + .../doctools/mpformats/_text_margin.tcl | 21 + .../doctools/mpformats/_text_para.tcl | 65 + .../doctools/mpformats/_text_state.tcl | 22 + .../doctools/mpformats/_text_utils.tcl | 60 + .../doctools/mpformats/_toc_common.tcl | 31 + .../tcllib2.0/doctools/mpformats/_xml.tcl | 233 + .../tcllib2.0/doctools/mpformats/_xref.tcl | 129 + .../tcllib2.0/doctools/mpformats/c.msg | 58 + .../tcllib2.0/doctools/mpformats/de.msg | 54 + .../tcllib2.0/doctools/mpformats/en.msg | 54 + .../tcllib2.0/doctools/mpformats/fmt.desc | 49 + .../tcllib2.0/doctools/mpformats/fmt.html | 644 + .../tcllib2.0/doctools/mpformats/fmt.latex | 427 + .../tcllib2.0/doctools/mpformats/fmt.list | 52 + .../tcllib2.0/doctools/mpformats/fmt.markdown | 529 + .../tcllib2.0/doctools/mpformats/fmt.nroff | 308 + .../tcllib2.0/doctools/mpformats/fmt.null | 30 + .../tcllib2.0/doctools/mpformats/fmt.text | 565 + .../tcllib2.0/doctools/mpformats/fmt.tmml | 309 + .../tcllib2.0/doctools/mpformats/fmt.wiki | 305 + .../tcllib2.0/doctools/mpformats/fr.msg | 34 + .../tcllib2.0/doctools/mpformats/idx.html | 333 + .../tcllib2.0/doctools/mpformats/idx.markdown | 219 + .../tcllib2.0/doctools/mpformats/idx.nroff | 101 + .../tcllib2.0/doctools/mpformats/idx.null | 23 + .../tcllib2.0/doctools/mpformats/idx.text | 77 + .../tcllib2.0/doctools/mpformats/idx.wiki | 63 + .../tcllib2.0/doctools/mpformats/man.macros | 267 + .../tcllib2.0/doctools/mpformats/toc.html | 198 + .../tcllib2.0/doctools/mpformats/toc.markdown | 75 + .../tcllib2.0/doctools/mpformats/toc.nroff | 70 + .../tcllib2.0/doctools/mpformats/toc.null | 23 + .../tcllib2.0/doctools/mpformats/toc.text | 106 + .../tcllib2.0/doctools/mpformats/toc.tmml | 34 + .../tcllib2.0/doctools/mpformats/toc.wiki | 63 + .../tcllib2.0/doctools/pkgIndex.tcl | 6 + .../tcllib2.0/doctools2base/html.tcl | 209 + .../doctools2base/html_cssdefaults.tcl | 158 + .../tcllib2.0/doctools2base/msgcat.tcl | 59 + .../doctools2base/nroff_manmacros.tcl | 261 + .../tcllib2.0/doctools2base/pkgIndex.tcl | 17 + .../tcllib2.0/doctools2base/tcl_parse.tcl | 800 + .../tcllib2.0/doctools2base/text.tcl | 216 + .../tcllib2.0/doctools2idx/container.tcl | 405 + .../tcllib2.0/doctools2idx/export.tcl | 123 + .../tcllib2.0/doctools2idx/export_docidx.tcl | 210 + .../tcllib2.0/doctools2idx/export_html.tcl | 421 + .../tcllib2.0/doctools2idx/export_json.tcl | 214 + .../tcllib2.0/doctools2idx/export_nroff.tcl | 213 + .../tcllib2.0/doctools2idx/export_text.tcl | 136 + .../tcllib2.0/doctools2idx/export_wiki.tcl | 163 + .../tcllib2.0/doctools2idx/import.tcl | 189 + .../tcllib2.0/doctools2idx/import_docidx.tcl | 91 + .../tcllib2.0/doctools2idx/import_json.tcl | 78 + .../tcllib2.0/doctools2idx/msgcat_c.tcl | 26 + .../tcllib2.0/doctools2idx/msgcat_de.tcl | 26 + .../tcllib2.0/doctools2idx/msgcat_en.tcl | 26 + .../tcllib2.0/doctools2idx/msgcat_fr.tcl | 29 + .../tcllib2.0/doctools2idx/parse.tcl | 1043 + .../tcllib2.0/doctools2idx/pkgIndex.tcl | 33 + .../tcllib2.0/doctools2idx/structure.tcl | 288 + .../tcllib2.0/doctools2toc/container.tcl | 545 + .../tcllib2.0/doctools2toc/export.tcl | 123 + .../tcllib2.0/doctools2toc/export_doctoc.tcl | 217 + .../tcllib2.0/doctools2toc/export_html.tcl | 323 + .../tcllib2.0/doctools2toc/export_json.tcl | 223 + .../tcllib2.0/doctools2toc/export_nroff.tcl | 218 + .../tcllib2.0/doctools2toc/export_text.tcl | 142 + .../tcllib2.0/doctools2toc/export_wiki.tcl | 144 + .../tcllib2.0/doctools2toc/import.tcl | 189 + .../tcllib2.0/doctools2toc/import_doctoc.tcl | 91 + .../tcllib2.0/doctools2toc/import_json.tcl | 77 + .../tcllib2.0/doctools2toc/msgcat_c.tcl | 28 + .../tcllib2.0/doctools2toc/msgcat_de.tcl | 28 + .../tcllib2.0/doctools2toc/msgcat_en.tcl | 28 + .../tcllib2.0/doctools2toc/msgcat_fr.tcl | 31 + .../tcllib2.0/doctools2toc/parse.tcl | 1058 + .../tcllib2.0/doctools2toc/pkgIndex.tcl | 33 + .../tcllib2.0/doctools2toc/structure.tcl | 388 + .../tcllib2.0/dtplite/dtplite.tcl | 1785 + .../tcllib2.0/dtplite/pkgIndex.tcl | 5 + .../tcllib2.0/fileutil/decode.tcl | 203 + .../tcllib2.0/fileutil/fileutil.tcl | 2125 + .../tcllib2.0/fileutil/multi.tcl | 28 + .../tcllib2.0/fileutil/multiop.tcl | 646 + .../tcllib2.0/fileutil/paths.tcl | 74 + .../tcllib2.0/fileutil/pkgIndex.tcl | 7 + .../tcllib2.0/fileutil/traverse.tcl | 393 + src/vendorlib_tcl9/tcllib2.0/ftp/ftp.tcl | 3159 + .../tcllib2.0/ftp/ftp_geturl.tcl | 135 + src/vendorlib_tcl9/tcllib2.0/ftp/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/ftpd/ftpd.tcl | 2064 + .../tcllib2.0/ftpd/pkgIndex.tcl | 2 + .../tcllib2.0/fumagic/cfront.tcl | 932 + src/vendorlib_tcl9/tcllib2.0/fumagic/cgen.tcl | 705 + .../tcllib2.0/fumagic/filetypes.tcl | 85040 ++++++++++++++++ .../tcllib2.0/fumagic/pkgIndex.tcl | 14 + .../tcllib2.0/fumagic/rtcore.tcl | 1019 + .../tcllib2.0/generator/generator.tcl | 381 + .../tcllib2.0/generator/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/gpx/gpx.tcl | 294 + src/vendorlib_tcl9/tcllib2.0/gpx/pkgIndex.tcl | 2 + .../tcllib2.0/grammar_aycock/aycock-build.tcl | 735 + .../tcllib2.0/grammar_aycock/aycock-debug.tcl | 189 + .../grammar_aycock/aycock-runtime.tcl | 425 + .../tcllib2.0/grammar_aycock/pkgIndex.tcl | 8 + .../tcllib2.0/grammar_fa/dacceptor.tcl | 166 + .../tcllib2.0/grammar_fa/dexec.tcl | 188 + .../tcllib2.0/grammar_fa/fa.tcl | 1242 + .../tcllib2.0/grammar_fa/faop.tcl | 1618 + .../tcllib2.0/grammar_fa/pkgIndex.tcl | 6 + .../tcllib2.0/grammar_me/gasm.tcl | 207 + .../tcllib2.0/grammar_me/me_cpu.tcl | 103 + .../tcllib2.0/grammar_me/me_cpucore.tcl | 1162 + .../tcllib2.0/grammar_me/me_tcl.tcl | 521 + .../tcllib2.0/grammar_me/me_util.tcl | 191 + .../tcllib2.0/grammar_me/pkgIndex.tcl | 7 + .../tcllib2.0/grammar_peg/peg.tcl | 541 + .../tcllib2.0/grammar_peg/peg_interp.tcl | 350 + .../tcllib2.0/grammar_peg/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/hook/hook.tcl | 358 + .../tcllib2.0/hook/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/html/html.tcl | 1581 + .../tcllib2.0/html/pkgIndex.tcl | 2 + .../tcllib2.0/htmlparse/htmlparse.tcl | 1444 + .../tcllib2.0/htmlparse/pkgIndex.tcl | 2 + .../tcllib2.0/http/autoproxy.tcl | 588 + .../tcllib2.0/http/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/httpd/httpd.tcl | 2016 + .../tcllib2.0/httpd/pkgIndex.tcl | 4 + .../tcllib2.0/httpwget/pkgIndex.tcl | 1 + .../tcllib2.0/httpwget/wget.tcl | 62 + src/vendorlib_tcl9/tcllib2.0/ident/ident.tcl | 89 + .../tcllib2.0/ident/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/imap4/imap4.tcl | 1385 + .../tcllib2.0/imap4/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/inifile/ini.tcl | 422 + .../tcllib2.0/inifile/pkgIndex.tcl | 2 + .../tcllib2.0/interp/deleg_method.tcl | 64 + .../tcllib2.0/interp/deleg_proc.tcl | 68 + .../tcllib2.0/interp/interp.tcl | 87 + .../tcllib2.0/interp/pkgIndex.tcl | 4 + src/vendorlib_tcl9/tcllib2.0/irc/irc.tcl | 531 + src/vendorlib_tcl9/tcllib2.0/irc/picoirc.tcl | 450 + src/vendorlib_tcl9/tcllib2.0/irc/pkgIndex.tcl | 7 + .../tcllib2.0/javascript/javascript.tcl | 453 + .../tcllib2.0/javascript/pkgIndex.tcl | 4 + src/vendorlib_tcl9/tcllib2.0/jpeg/jpeg.tcl | 1118 + .../tcllib2.0/jpeg/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/json/json.tcl | 285 + .../tcllib2.0/json/json_tcl.tcl | 291 + .../tcllib2.0/json/json_write.tcl | 212 + src/vendorlib_tcl9/tcllib2.0/json/jsonc.tcl | 171 + .../tcllib2.0/json/pkgIndex.tcl | 5 + .../tcllib2.0/lambda/lambda.tcl | 43 + .../tcllib2.0/lambda/pkgIndex.tcl | 8 + .../tcllib2.0/lazyset/lazyset.tcl | 88 + .../tcllib2.0/lazyset/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/ldap/ldap.tcl | 2327 + src/vendorlib_tcl9/tcllib2.0/ldap/ldapx.tcl | 1804 + .../tcllib2.0/ldap/pkgIndex.tcl | 7 + src/vendorlib_tcl9/tcllib2.0/log/log.tcl | 897 + src/vendorlib_tcl9/tcllib2.0/log/logger.tcl | 1297 + .../tcllib2.0/log/loggerAppender.tcl | 444 + .../tcllib2.0/log/loggerUtils.tcl | 538 + src/vendorlib_tcl9/tcllib2.0/log/msgs/en.msg | 7 + src/vendorlib_tcl9/tcllib2.0/log/pkgIndex.tcl | 5 + .../tcllib2.0/map/map_geocode_nominatim.tcl | 91 + .../tcllib2.0/map/map_slippy.tcl | 285 + .../tcllib2.0/map/map_slippy_c.tcl | 837 + .../tcllib2.0/map/map_slippy_cache.tcl | 143 + .../tcllib2.0/map/map_slippy_fetcher.tcl | 170 + .../tcllib2.0/map/map_slippy_tcl.tcl | 852 + src/vendorlib_tcl9/tcllib2.0/map/pkgIndex.tcl | 6 + .../tcllib2.0/mapproj/mapproj.tcl | 1817 + .../tcllib2.0/mapproj/pkgIndex.tcl | 2 + .../tcllib2.0/markdown/markdown.tcl | 813 + .../tcllib2.0/markdown/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/math/bessel.tcl | 200 + .../tcllib2.0/math/bigfloat2.tcl | 2217 + src/vendorlib_tcl9/tcllib2.0/math/bignum.tcl | 900 + .../tcllib2.0/math/calculus.tcl | 1652 + .../tcllib2.0/math/changepoint.tcl | 311 + .../tcllib2.0/math/classic_polyns.tcl | 207 + .../tcllib2.0/math/combinatorics.tcl | 447 + .../tcllib2.0/math/combinatoricsExt.tcl | 825 + .../tcllib2.0/math/constants.tcl | 211 + src/vendorlib_tcl9/tcllib2.0/math/decimal.tcl | 1742 + .../tcllib2.0/math/elliptic.tcl | 246 + src/vendorlib_tcl9/tcllib2.0/math/exact.tcl | 4060 + .../tcllib2.0/math/exponential.tcl | 449 + .../tcllib2.0/math/figurate.tcl | 294 + .../tcllib2.0/math/filtergen.tcl | 251 + src/vendorlib_tcl9/tcllib2.0/math/fourier.tcl | 376 + src/vendorlib_tcl9/tcllib2.0/math/fuzzy.tcl | 173 + .../tcllib2.0/math/geometry.tcl | 1569 + .../tcllib2.0/math/geometry_circle.tcl | 382 + .../tcllib2.0/math/geometry_ext.tcl | 1023 + .../tcllib2.0/math/interpolate.tcl | 667 + src/vendorlib_tcl9/tcllib2.0/math/kruskal.tcl | 154 + src/vendorlib_tcl9/tcllib2.0/math/linalg.tcl | 2299 + .../tcllib2.0/math/liststat.tcl | 95 + .../tcllib2.0/math/machineparameters.tcl | 378 + src/vendorlib_tcl9/tcllib2.0/math/math.tcl | 44 + src/vendorlib_tcl9/tcllib2.0/math/misc.tcl | 385 + .../tcllib2.0/math/mvlinreg.tcl | 265 + .../tcllib2.0/math/numtheory.tcl | 82 + .../tcllib2.0/math/optimize.tcl | 1323 + src/vendorlib_tcl9/tcllib2.0/math/pca.tcl | 403 + .../tcllib2.0/math/pdf_stat.tcl | 2676 + .../tcllib2.0/math/pkgIndex.tcl | 35 + .../tcllib2.0/math/plotstat.tcl | 312 + .../tcllib2.0/math/polynomials.tcl | 564 + src/vendorlib_tcl9/tcllib2.0/math/primes.tcl | 569 + src/vendorlib_tcl9/tcllib2.0/math/probopt.tcl | 19 + .../tcllib2.0/math/probopt_diffev.tcl | 275 + .../tcllib2.0/math/probopt_lipo.tcl | 241 + .../tcllib2.0/math/probopt_pso.tcl | 427 + .../tcllib2.0/math/probopt_sce.tcl | 378 + .../tcllib2.0/math/qcomplex.tcl | 178 + .../tcllib2.0/math/quasirandom.tcl | 522 + .../tcllib2.0/math/rational_funcs.tcl | 367 + .../tcllib2.0/math/romannumerals.tcl | 164 + .../tcllib2.0/math/rootfind.tcl | 343 + src/vendorlib_tcl9/tcllib2.0/math/special.tcl | 463 + .../tcllib2.0/math/stat_kernel.tcl | 217 + .../tcllib2.0/math/stat_logit.tcl | 120 + .../tcllib2.0/math/stat_wasserstein.tcl | 214 + .../tcllib2.0/math/statistics.tcl | 2039 + src/vendorlib_tcl9/tcllib2.0/math/symdiff.tcl | 1229 + src/vendorlib_tcl9/tcllib2.0/math/tclIndex | 26 + src/vendorlib_tcl9/tcllib2.0/math/trig.tcl | 290 + .../tcllib2.0/math/wilcoxon.tcl | 338 + src/vendorlib_tcl9/tcllib2.0/md4/md4.tcl | 569 + src/vendorlib_tcl9/tcllib2.0/md4/md4c.tcl | 121 + src/vendorlib_tcl9/tcllib2.0/md4/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/md5/md5.tcl | 463 + src/vendorlib_tcl9/tcllib2.0/md5/md5c.tcl | 149 + src/vendorlib_tcl9/tcllib2.0/md5/md5x.tcl | 738 + src/vendorlib_tcl9/tcllib2.0/md5/pkgIndex.tcl | 3 + .../tcllib2.0/md5c/critcl-rt.tcl | 381 + .../tcllib2.0/md5c/license.terms | 1 + .../tcllib2.0/md5c/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/md5c/teapot.txt | 7 + .../tcllib2.0/md5c/win32-x86_64/md5c.dll | Bin 0 -> 22528 bytes .../tcllib2.0/md5crypt/md5crypt.tcl | 155 + .../tcllib2.0/md5crypt/md5cryptc.tcl | 182 + .../tcllib2.0/md5crypt/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/mime/mime.tcl | 3930 + .../tcllib2.0/mime/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/mime/smtp.tcl | 1508 + src/vendorlib_tcl9/tcllib2.0/mkdoc/mkdoc.tcl | 701 + .../tcllib2.0/mkdoc/pkgIndex.tcl | 2 + .../tcllib2.0/multiplexer/multiplexer.tcl | 291 + .../tcllib2.0/multiplexer/pkgIndex.tcl | 12 + .../tcllib2.0/namespacex/namespacex.tcl | 335 + .../tcllib2.0/namespacex/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/ncgi/ncgi.tcl | 1121 + .../tcllib2.0/ncgi/pkgIndex.tcl | 2 + .../tcllib2.0/nettool/nettool.tcl | 1814 + .../tcllib2.0/nettool/pkgIndex.tcl | 6 + src/vendorlib_tcl9/tcllib2.0/nmea/nmea.tcl | 199 + .../tcllib2.0/nmea/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/nns/common.tcl | 38 + src/vendorlib_tcl9/tcllib2.0/nns/nns.tcl | 432 + src/vendorlib_tcl9/tcllib2.0/nns/nns_auto.tcl | 443 + src/vendorlib_tcl9/tcllib2.0/nns/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/nns/server.tcl | 385 + src/vendorlib_tcl9/tcllib2.0/nntp/nntp.tcl | 979 + .../tcllib2.0/nntp/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/ntp/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/ntp/time.tcl | 382 + src/vendorlib_tcl9/tcllib2.0/oauth/oauth.tcl | 304 + .../tcllib2.0/oauth/pkgIndex.tcl | 2 + .../tcllib2.0/oodialect/oodialect.tcl | 265 + .../tcllib2.0/oodialect/pkgIndex.tcl | 1 + .../tcllib2.0/oometa/oometa.tcl | 503 + .../tcllib2.0/oometa/oooption.tcl | 169 + .../tcllib2.0/oometa/pkgIndex.tcl | 8 + .../tcllib2.0/ooutil/ooutil.tcl | 189 + .../tcllib2.0/ooutil/pkgIndex.tcl | 7 + src/vendorlib_tcl9/tcllib2.0/otp/otp.tcl | 430 + src/vendorlib_tcl9/tcllib2.0/otp/pkgIndex.tcl | 3 + .../tcllib2.0/page/analysis_peg_emodes.tcl | 458 + .../tcllib2.0/page/analysis_peg_minimize.tcl | 51 + .../tcllib2.0/page/analysis_peg_reachable.tcl | 150 + .../page/analysis_peg_realizable.tcl | 257 + .../tcllib2.0/page/compiler_peg_mecpu.tcl | 1642 + .../tcllib2.0/page/gen_peg_canon.tcl | 481 + .../tcllib2.0/page/gen_peg_cpkg.tcl | 171 + .../tcllib2.0/page/gen_peg_hb.tcl | 79 + .../tcllib2.0/page/gen_peg_me.tcl | 888 + .../tcllib2.0/page/gen_peg_me.template | 61 + .../tcllib2.0/page/gen_peg_mecpu.tcl | 289 + .../tcllib2.0/page/gen_peg_mecpu.template | 48 + .../tcllib2.0/page/gen_peg_ser.tcl | 63 + .../tcllib2.0/page/gen_tree_text.tcl | 94 + .../tcllib2.0/page/parse_lemon.tcl | 7420 ++ .../tcllib2.0/page/parse_peg.tcl | 4415 + .../tcllib2.0/page/parse_peghb.tcl | 118 + .../tcllib2.0/page/parse_pegser.tcl | 99 + .../tcllib2.0/page/peg_grammar.tcl | 117 + .../tcllib2.0/page/pkgIndex.tcl | 80 + .../tcllib2.0/page/pluginmgr.tcl | 581 + .../tcllib2.0/page/plugins/config_peg.tcl | 14 + .../tcllib2.0/page/plugins/pkgIndex.tcl | 34 + .../tcllib2.0/page/plugins/reader_hb.tcl | 114 + .../tcllib2.0/page/plugins/reader_lemon.tcl | 170 + .../tcllib2.0/page/plugins/reader_peg.tcl | 169 + .../tcllib2.0/page/plugins/reader_ser.tcl | 114 + .../tcllib2.0/page/plugins/reader_treeser.tcl | 116 + .../page/plugins/transform_mecpu.tcl | 107 + .../page/plugins/transform_reachable.tcl | 107 + .../page/plugins/transform_realizable.tcl | 106 + .../tcllib2.0/page/plugins/writer_hb.tcl | 106 + .../page/plugins/writer_identity.tcl | 98 + .../tcllib2.0/page/plugins/writer_me.tcl | 115 + .../tcllib2.0/page/plugins/writer_mecpu.tcl | 116 + .../tcllib2.0/page/plugins/writer_null.tcl | 97 + .../tcllib2.0/page/plugins/writer_peg.tcl | 106 + .../tcllib2.0/page/plugins/writer_ser.tcl | 104 + .../tcllib2.0/page/plugins/writer_tpc.tcl | 105 + .../tcllib2.0/page/plugins/writer_tree.tcl | 105 + .../tcllib2.0/page/util_flow.tcl | 90 + .../tcllib2.0/page/util_norm_lemon.tcl | 427 + .../tcllib2.0/page/util_norm_peg.tcl | 415 + .../tcllib2.0/page/util_peg.tcl | 209 + .../tcllib2.0/page/util_quote.tcl | 173 + src/vendorlib_tcl9/tcllib2.0/pkgIndex.tcl | 40 + src/vendorlib_tcl9/tcllib2.0/pki/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/pki/pki.tcl | 3321 + .../tcllib2.0/pluginmgr/pkgIndex.tcl | 2 + .../tcllib2.0/pluginmgr/pluginmgr.tcl | 418 + src/vendorlib_tcl9/tcllib2.0/png/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/png/png.tcl | 306 + .../tcllib2.0/pop3/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/pop3/pop3.tcl | 832 + .../tcllib2.0/pop3d/pkgIndex.tcl | 14 + src/vendorlib_tcl9/tcllib2.0/pop3d/pop3d.tcl | 1147 + .../tcllib2.0/pop3d/pop3d_dbox.tcl | 485 + .../tcllib2.0/pop3d/pop3d_udb.tcl | 300 + .../tcllib2.0/practcl/pkgIndex.tcl | 4 + .../tcllib2.0/practcl/practcl.tcl | 8463 ++ .../tcllib2.0/processman/pkgIndex.tcl | 3 + .../tcllib2.0/processman/processman.tcl | 348 + .../tcllib2.0/profiler/pkgIndex.tcl | 2 + .../tcllib2.0/profiler/profiler.tcl | 667 + src/vendorlib_tcl9/tcllib2.0/pt/char.tcl | 289 + src/vendorlib_tcl9/tcllib2.0/pt/pkgIndex.tcl | 71 + src/vendorlib_tcl9/tcllib2.0/pt/pt_astree.tcl | 239 + .../tcllib2.0/pt/pt_cparam_config_critcl.tcl | 509 + .../tcllib2.0/pt/pt_cparam_config_tea.tcl | 477 + .../tcllib2.0/pt/pt_parse_peg.tcl | 180 + .../tcllib2.0/pt/pt_parse_peg_c.tcl | 4978 + .../tcllib2.0/pt/pt_parse_peg_tcl.tcl | 2431 + .../tcllib2.0/pt/pt_peg_container.tcl | 530 + .../tcllib2.0/pt/pt_peg_container_peg.tcl | 146 + .../tcllib2.0/pt/pt_peg_export.tcl | 117 + .../tcllib2.0/pt/pt_peg_export_container.tcl | 51 + .../tcllib2.0/pt/pt_peg_export_json.tcl | 50 + .../tcllib2.0/pt/pt_peg_export_peg.tcl | 51 + .../tcllib2.0/pt/pt_peg_from_json.tcl | 48 + .../tcllib2.0/pt/pt_peg_from_peg.tcl | 394 + .../tcllib2.0/pt/pt_peg_import.tcl | 188 + .../tcllib2.0/pt/pt_peg_import_json.tcl | 40 + .../tcllib2.0/pt/pt_peg_import_peg.tcl | 41 + .../tcllib2.0/pt/pt_peg_interp.tcl | 385 + src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_op.tcl | 375 + .../tcllib2.0/pt/pt_peg_to_container.tcl | 345 + .../tcllib2.0/pt/pt_peg_to_cparam.tcl | 1662 + .../tcllib2.0/pt/pt_peg_to_json.tcl | 149 + .../tcllib2.0/pt/pt_peg_to_param.tcl | 1029 + .../tcllib2.0/pt/pt_peg_to_peg.tcl | 413 + .../tcllib2.0/pt/pt_peg_to_tclparam.tcl | 1274 + .../tcllib2.0/pt/pt_pegrammar.tcl | 380 + .../tcllib2.0/pt/pt_pexpr_op.tcl | 331 + .../tcllib2.0/pt/pt_pexpression.tcl | 321 + src/vendorlib_tcl9/tcllib2.0/pt/pt_pgen.tcl | 278 + .../tcllib2.0/pt/pt_rdengine.tcl | 206 + .../tcllib2.0/pt/pt_rdengine_c.tcl | 168 + .../tcllib2.0/pt/pt_rdengine_nx.tcl | 148 + .../tcllib2.0/pt/pt_rdengine_oo.tcl | 2170 + .../tcllib2.0/pt/pt_rdengine_tcl.tcl | 2283 + .../tcllib2.0/pt/pt_tclparam_config_nx.tcl | 113 + .../tcllib2.0/pt/pt_tclparam_config_snit.tcl | 141 + .../tcllib2.0/pt/pt_tclparam_config_tcloo.tcl | 121 + src/vendorlib_tcl9/tcllib2.0/pt/pt_util.tcl | 160 + .../tcllib2.0/pt/rde_critcl/m.c | 2735 + .../tcllib2.0/pt/rde_critcl/m.h | 150 + .../tcllib2.0/pt/rde_critcl/ms.c | 317 + .../tcllib2.0/pt/rde_critcl/ms.h | 20 + .../tcllib2.0/pt/rde_critcl/ot.c | 230 + .../tcllib2.0/pt/rde_critcl/ot.h | 24 + .../tcllib2.0/pt/rde_critcl/p.c | 183 + .../tcllib2.0/pt/rde_critcl/p.h | 24 + .../tcllib2.0/pt/rde_critcl/pInt.h | 50 + .../tcllib2.0/pt/rde_critcl/param.c | 1789 + .../tcllib2.0/pt/rde_critcl/param.h | 183 + .../tcllib2.0/pt/rde_critcl/stack.c | 160 + .../tcllib2.0/pt/rde_critcl/stack.h | 63 + .../tcllib2.0/pt/rde_critcl/tc.c | 187 + .../tcllib2.0/pt/rde_critcl/tc.h | 31 + .../tcllib2.0/pt/rde_critcl/util.c | 145 + .../tcllib2.0/pt/rde_critcl/util.h | 79 + .../tcllib2.0/pt/text_write.tcl | 249 + src/vendorlib_tcl9/tcllib2.0/rc4/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/rc4/rc4.tcl | 422 + src/vendorlib_tcl9/tcllib2.0/rc4/rc4c.tcl | 169 + src/vendorlib_tcl9/tcllib2.0/rcs/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/rcs/rcs.tcl | 281 + .../tcllib2.0/report/pkgIndex.tcl | 2 + .../tcllib2.0/report/report.tcl | 1394 + .../tcllib2.0/rest/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/rest/rest.tcl | 857 + .../tcllib2.0/ripemd/pkgIndex.tcl | 3 + .../tcllib2.0/ripemd/ripemd128.tcl | 731 + .../tcllib2.0/ripemd/ripemd160.tcl | 874 + src/vendorlib_tcl9/tcllib2.0/sasl/gtoken.tcl | 92 + src/vendorlib_tcl9/tcllib2.0/sasl/ntlm.tcl | 376 + .../tcllib2.0/sasl/pkgIndex.tcl | 11 + src/vendorlib_tcl9/tcllib2.0/sasl/sasl.tcl | 682 + src/vendorlib_tcl9/tcllib2.0/sasl/scram.tcl | 503 + .../tcllib2.0/sha1/pkgIndex.tcl | 5 + src/vendorlib_tcl9/tcllib2.0/sha1/sha1.c | 268 + src/vendorlib_tcl9/tcllib2.0/sha1/sha1.h | 28 + src/vendorlib_tcl9/tcllib2.0/sha1/sha1.tcl | 814 + src/vendorlib_tcl9/tcllib2.0/sha1/sha1c.tcl | 126 + src/vendorlib_tcl9/tcllib2.0/sha1/sha1v1.tcl | 710 + src/vendorlib_tcl9/tcllib2.0/sha1/sha256.c | 524 + src/vendorlib_tcl9/tcllib2.0/sha1/sha256.h | 88 + src/vendorlib_tcl9/tcllib2.0/sha1/sha256.tcl | 833 + src/vendorlib_tcl9/tcllib2.0/sha1/sha256c.tcl | 191 + .../tcllib2.0/simulation/annealing.tcl | 564 + .../tcllib2.0/simulation/montecarlo.tcl | 486 + .../tcllib2.0/simulation/pkgIndex.tcl | 3 + .../tcllib2.0/simulation/random.tcl | 645 + .../tcllib2.0/smtpd/pkgIndex.tcl | 12 + src/vendorlib_tcl9/tcllib2.0/smtpd/smtpd.tcl | 924 + src/vendorlib_tcl9/tcllib2.0/snit/main1.tcl | 3987 + src/vendorlib_tcl9/tcllib2.0/snit/main2.tcl | 3888 + .../tcllib2.0/snit/pkgIndex.tcl | 6 + src/vendorlib_tcl9/tcllib2.0/snit/snit.tcl | 32 + src/vendorlib_tcl9/tcllib2.0/snit/snit2.tcl | 32 + .../tcllib2.0/snit/validate.tcl | 720 + .../tcllib2.0/soundex/pkgIndex.tcl | 12 + .../tcllib2.0/soundex/soundex.tcl | 96 + .../tcllib2.0/stooop/mkpkgidx.tcl | 112 + .../tcllib2.0/stooop/pkgIndex.tcl | 22 + .../tcllib2.0/stooop/stooop.tcl | 937 + .../tcllib2.0/stooop/switched.tcl | 133 + src/vendorlib_tcl9/tcllib2.0/stooop/xifo.tcl | 142 + .../tcllib2.0/string/pkgIndex.tcl | 6 + src/vendorlib_tcl9/tcllib2.0/string/token.tcl | 94 + .../tcllib2.0/string/token_shell.tcl | 172 + .../tcllib2.0/stringprep/pkgIndex.tcl | 5 + .../tcllib2.0/stringprep/stringprep.tcl | 303 + .../tcllib2.0/stringprep/stringprep_data.tcl | 1033 + .../tcllib2.0/stringprep/unicode.tcl | 289 + .../tcllib2.0/stringprep/unicode_data.tcl | 1549 + .../tcllib2.0/struct/disjointset.tcl | 385 + src/vendorlib_tcl9/tcllib2.0/struct/graph.tcl | 177 + .../tcllib2.0/struct/graph1.tcl | 2154 + .../tcllib2.0/struct/graph_c.tcl | 158 + .../tcllib2.0/struct/graph_tcl.tcl | 3280 + .../tcllib2.0/struct/graphops.tcl | 3787 + src/vendorlib_tcl9/tcllib2.0/struct/list.tcl | 1860 + .../tcllib2.0/struct/list.test.tcl | 1288 + src/vendorlib_tcl9/tcllib2.0/struct/map.tcl | 104 + .../tcllib2.0/struct/matrix.tcl | 2808 + .../tcllib2.0/struct/pkgIndex.tcl | 25 + src/vendorlib_tcl9/tcllib2.0/struct/pool.tcl | 715 + .../tcllib2.0/struct/prioqueue.tcl | 536 + src/vendorlib_tcl9/tcllib2.0/struct/queue.tcl | 183 + .../tcllib2.0/struct/queue_c.tcl | 151 + .../tcllib2.0/struct/queue_oo.tcl | 228 + .../tcllib2.0/struct/queue_tcl.tcl | 383 + .../tcllib2.0/struct/record.tcl | 830 + src/vendorlib_tcl9/tcllib2.0/struct/sets.tcl | 185 + .../tcllib2.0/struct/sets_c.tcl | 91 + .../tcllib2.0/struct/sets_tcl.tcl | 430 + .../tcllib2.0/struct/skiplist.tcl | 437 + src/vendorlib_tcl9/tcllib2.0/struct/stack.tcl | 183 + .../tcllib2.0/struct/stack_c.tcl | 156 + .../tcllib2.0/struct/stack_oo.tcl | 298 + .../tcllib2.0/struct/stack_tcl.tcl | 507 + .../tcllib2.0/struct/struct.tcl | 18 + .../tcllib2.0/struct/struct1.tcl | 17 + src/vendorlib_tcl9/tcllib2.0/struct/tree.tcl | 182 + src/vendorlib_tcl9/tcllib2.0/struct/tree1.tcl | 1487 + .../tcllib2.0/struct/tree_c.tcl | 206 + .../tcllib2.0/struct/tree_tcl.tcl | 2443 + src/vendorlib_tcl9/tcllib2.0/tar/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/tar/tar.tcl | 674 + .../tcllib2.0/tcllibc/critcl-rt.tcl | 381 + .../tcllib2.0/tcllibc/license.terms | 1 + .../tcllib2.0/tcllibc/pkgIndex.tcl | 2 + .../tcllib2.0/tcllibc/teapot.txt | 22 + .../tcllibc/win32-x86_64/tcllibc.dll | Bin 0 -> 351744 bytes .../tcllib2.0/tepam/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/tepam/tepam.tcl | 2783 + .../tcllib2.0/tepam/tepam_doc_gen.tcl | 748 + .../tcllib2.0/term/ansi/code.tcl | 56 + .../tcllib2.0/term/ansi/code/attr.tcl | 108 + .../tcllib2.0/term/ansi/code/ctrl.tcl | 272 + .../tcllib2.0/term/ansi/code/macros.tcl | 93 + .../tcllib2.0/term/ansi/ctrlunix.tcl | 91 + .../tcllib2.0/term/ansi/send.tcl | 92 + src/vendorlib_tcl9/tcllib2.0/term/bind.tcl | 132 + src/vendorlib_tcl9/tcllib2.0/term/imenu.tcl | 202 + src/vendorlib_tcl9/tcllib2.0/term/ipager.tcl | 206 + .../tcllib2.0/term/pkgIndex.tcl | 13 + src/vendorlib_tcl9/tcllib2.0/term/receive.tcl | 60 + src/vendorlib_tcl9/tcllib2.0/term/send.tcl | 34 + src/vendorlib_tcl9/tcllib2.0/term/term.tcl | 19 + .../tcllib2.0/textutil/adjust.tcl | 763 + .../tcllib2.0/textutil/dehypht.tex | 902 + .../tcllib2.0/textutil/eshyph_vo.tex | 1104 + .../tcllib2.0/textutil/expander.tcl | 1122 + .../tcllib2.0/textutil/ithyph.tex | 223 + .../tcllib2.0/textutil/patch.tcl | 180 + .../tcllib2.0/textutil/pkgIndex.tcl | 14 + .../tcllib2.0/textutil/repeat.tcl | 91 + .../tcllib2.0/textutil/split.tcl | 144 + .../tcllib2.0/textutil/string.tcl | 144 + .../tcllib2.0/textutil/tabify.tcl | 289 + .../tcllib2.0/textutil/textutil.tcl | 80 + .../tcllib2.0/textutil/trim.tcl | 112 + .../tcllib2.0/textutil/wcswidth.tcl | 2252 + src/vendorlib_tcl9/tcllib2.0/tie/pkgIndex.tcl | 9 + src/vendorlib_tcl9/tcllib2.0/tie/tie.tcl | 510 + .../tcllib2.0/tie/tie_array.tcl | 123 + .../tcllib2.0/tie/tie_dsource.tcl | 53 + src/vendorlib_tcl9/tcllib2.0/tie/tie_file.tcl | 272 + .../tcllib2.0/tie/tie_growfile.tcl | 146 + src/vendorlib_tcl9/tcllib2.0/tie/tie_log.tcl | 94 + .../tcllib2.0/tie/tie_rarray.tcl | 117 + .../tcllib2.0/tiff/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/tiff/tiff.tcl | 781 + .../tcllib2.0/tool/pkgIndex.tcl | 13 + src/vendorlib_tcl9/tcllib2.0/tool/tool.tcl | 1849 + .../tcllib2.0/transfer/connect.tcl | 97 + .../tcllib2.0/transfer/copyops.tcl | 389 + .../tcllib2.0/transfer/ddest.tcl | 169 + .../tcllib2.0/transfer/dsource.tcl | 183 + .../tcllib2.0/transfer/pkgIndex.tcl | 8 + .../tcllib2.0/transfer/receiver.tcl | 188 + .../tcllib2.0/transfer/tqueue.tcl | 223 + .../tcllib2.0/transfer/transmitter.tcl | 176 + .../tcllib2.0/treeql/pkgIndex.tcl | 5 + .../tcllib2.0/treeql/treeql.tcl | 24 + .../tcllib2.0/treeql/treeql84.tcl | 734 + .../tcllib2.0/treeql/treeql85.tcl | 737 + src/vendorlib_tcl9/tcllib2.0/try/fhome.tcl | 63 + src/vendorlib_tcl9/tcllib2.0/try/pkgIndex.tcl | 21 + src/vendorlib_tcl9/tcllib2.0/try/throw.tcl | 18 + src/vendorlib_tcl9/tcllib2.0/try/try.tcl | 206 + .../tcllib2.0/udpcluster/pkgIndex.tcl | 4 + .../tcllib2.0/udpcluster/udpcluster.tcl | 661 + src/vendorlib_tcl9/tcllib2.0/uev/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/uev/uevent.tcl | 468 + .../tcllib2.0/uev/uevent_onidle.tcl | 51 + .../tcllib2.0/units/pkgIndex.tcl | 4 + src/vendorlib_tcl9/tcllib2.0/units/units.tcl | 703 + src/vendorlib_tcl9/tcllib2.0/uri/pkgIndex.tcl | 6 + src/vendorlib_tcl9/tcllib2.0/uri/uri.tcl | 1444 + .../tcllib2.0/uri/urn-scheme.tcl | 143 + .../tcllib2.0/uuid/pkgIndex.tcl | 2 + src/vendorlib_tcl9/tcllib2.0/uuid/uuid.tcl | 245 + .../tcllib2.0/valtype/cc_amex.tcl | 68 + .../tcllib2.0/valtype/cc_discover.tcl | 70 + .../tcllib2.0/valtype/cc_mastercard.tcl | 68 + .../tcllib2.0/valtype/cc_visa.tcl | 69 + .../tcllib2.0/valtype/ean13.tcl | 99 + src/vendorlib_tcl9/tcllib2.0/valtype/iban.tcl | 92 + src/vendorlib_tcl9/tcllib2.0/valtype/imei.tcl | 66 + src/vendorlib_tcl9/tcllib2.0/valtype/isbn.tcl | 176 + src/vendorlib_tcl9/tcllib2.0/valtype/luhn.tcl | 128 + .../tcllib2.0/valtype/luhn5.tcl | 140 + .../tcllib2.0/valtype/pkgIndex.tcl | 13 + .../tcllib2.0/valtype/usnpi.tcl | 65 + .../tcllib2.0/valtype/valtype.tcl | 77 + .../tcllib2.0/valtype/verhoeff.tcl | 128 + .../tcllib2.0/virtchannel_base/cat.tcl | 139 + .../tcllib2.0/virtchannel_base/facade.tcl | 238 + .../tcllib2.0/virtchannel_base/fifo.tcl | 142 + .../tcllib2.0/virtchannel_base/fifo2.tcl | 117 + .../tcllib2.0/virtchannel_base/halfpipe.tcl | 198 + .../tcllib2.0/virtchannel_base/memchan.tcl | 173 + .../tcllib2.0/virtchannel_base/null.tcl | 58 + .../tcllib2.0/virtchannel_base/nullzero.tcl | 66 + .../tcllib2.0/virtchannel_base/pkgIndex.tcl | 17 + .../tcllib2.0/virtchannel_base/random.tcl | 84 + .../tcllib2.0/virtchannel_base/randseed.tcl | 58 + .../tcllib2.0/virtchannel_base/std.tcl | 101 + .../tcllib2.0/virtchannel_base/string.tcl | 128 + .../tcllib2.0/virtchannel_base/textwindow.tcl | 78 + .../tcllib2.0/virtchannel_base/variable.tcl | 185 + .../tcllib2.0/virtchannel_base/zero.tcl | 58 + .../tcllib2.0/virtchannel_core/core.tcl | 77 + .../tcllib2.0/virtchannel_core/events.tcl | 158 + .../tcllib2.0/virtchannel_core/pkgIndex.tcl | 8 + .../virtchannel_core/transformcore.tcl | 71 + .../virtchannel_transform/adler32.tcl | 103 + .../virtchannel_transform/base64.tcl | 111 + .../virtchannel_transform/counter.tcl | 94 + .../tcllib2.0/virtchannel_transform/crc32.tcl | 103 + .../tcllib2.0/virtchannel_transform/hex.tcl | 58 + .../virtchannel_transform/identity.tcl | 59 + .../virtchannel_transform/limitsize.tcl | 88 + .../virtchannel_transform/observe.tcl | 80 + .../tcllib2.0/virtchannel_transform/otp.tcl | 98 + .../virtchannel_transform/pkgIndex.tcl | 14 + .../tcllib2.0/virtchannel_transform/rot.tcl | 95 + .../virtchannel_transform/spacer.tcl | 151 + .../tcllib2.0/virtchannel_transform/zlib.tcl | 100 + .../tcllib2.0/websocket/pkgIndex.tcl | 2 + .../tcllib2.0/websocket/websocket.tcl | 1801 + src/vendorlib_tcl9/tcllib2.0/wip/pkgIndex.tcl | 3 + src/vendorlib_tcl9/tcllib2.0/wip/wip.tcl | 463 + src/vendorlib_tcl9/tcllib2.0/wip/wip2.tcl | 464 + src/vendorlib_tcl9/tcllib2.0/yaml/huddle.tcl | 646 + .../tcllib2.0/yaml/huddle_types.tcl | 296 + .../tcllib2.0/yaml/json2huddle.tcl | 389 + .../tcllib2.0/yaml/pkgIndex.tcl | 6 + src/vendorlib_tcl9/tcllib2.0/yaml/yaml.tcl | 1297 + src/vendorlib_tcl9/tcllib2.0/zip/decode.tcl | 749 + src/vendorlib_tcl9/tcllib2.0/zip/encode.tcl | 374 + src/vendorlib_tcl9/tcllib2.0/zip/mkzip.tcl | 295 + src/vendorlib_tcl9/tcllib2.0/zip/pkgIndex.tcl | 8 + src/vendorlib_tcl9/twapi-5.0b1/LICENSE | 29 + src/vendorlib_tcl9/twapi-5.0b1/README.md | 73 + src/vendorlib_tcl9/twapi-5.0b1/account.tcl | 1160 + src/vendorlib_tcl9/twapi-5.0b1/adsi.tcl | 28 + src/vendorlib_tcl9/twapi-5.0b1/apputil.tcl | 114 + src/vendorlib_tcl9/twapi-5.0b1/base.tcl | 1876 + src/vendorlib_tcl9/twapi-5.0b1/clipboard.tcl | 254 + src/vendorlib_tcl9/twapi-5.0b1/com.tcl | 4238 + src/vendorlib_tcl9/twapi-5.0b1/console.tcl | 736 + src/vendorlib_tcl9/twapi-5.0b1/crypto.tcl | 3456 + src/vendorlib_tcl9/twapi-5.0b1/device.tcl | 624 + src/vendorlib_tcl9/twapi-5.0b1/etw.tcl | 1390 + src/vendorlib_tcl9/twapi-5.0b1/eventlog.tcl | 391 + src/vendorlib_tcl9/twapi-5.0b1/evt.tcl | 718 + src/vendorlib_tcl9/twapi-5.0b1/handle.tcl | 236 + src/vendorlib_tcl9/twapi-5.0b1/input.tcl | 623 + src/vendorlib_tcl9/twapi-5.0b1/msi.tcl | 432 + src/vendorlib_tcl9/twapi-5.0b1/mstask.tcl | 745 + src/vendorlib_tcl9/twapi-5.0b1/multimedia.tcl | 75 + src/vendorlib_tcl9/twapi-5.0b1/namedpipe.tcl | 103 + src/vendorlib_tcl9/twapi-5.0b1/network.tcl | 1124 + src/vendorlib_tcl9/twapi-5.0b1/nls.tcl | 467 + src/vendorlib_tcl9/twapi-5.0b1/os.tcl | 1213 + src/vendorlib_tcl9/twapi-5.0b1/pdh.tcl | 984 + src/vendorlib_tcl9/twapi-5.0b1/pkgIndex.tcl | 100 + src/vendorlib_tcl9/twapi-5.0b1/power.tcl | 136 + src/vendorlib_tcl9/twapi-5.0b1/printer.tcl | 58 + src/vendorlib_tcl9/twapi-5.0b1/process.tcl | 2028 + src/vendorlib_tcl9/twapi-5.0b1/rds.tcl | 191 + src/vendorlib_tcl9/twapi-5.0b1/registry.tcl | 490 + src/vendorlib_tcl9/twapi-5.0b1/resource.tcl | 458 + src/vendorlib_tcl9/twapi-5.0b1/security.tcl | 2392 + src/vendorlib_tcl9/twapi-5.0b1/service.tcl | 1187 + src/vendorlib_tcl9/twapi-5.0b1/share.tcl | 966 + src/vendorlib_tcl9/twapi-5.0b1/shell.tcl | 627 + src/vendorlib_tcl9/twapi-5.0b1/sspi.tcl | 801 + src/vendorlib_tcl9/twapi-5.0b1/storage.tcl | 616 + src/vendorlib_tcl9/twapi-5.0b1/synch.tcl | 94 + src/vendorlib_tcl9/twapi-5.0b1/tls.tcl | 1313 + src/vendorlib_tcl9/twapi-5.0b1/twapi.tcl | 855 + src/vendorlib_tcl9/twapi-5.0b1/ui.tcl | 1430 + src/vendorlib_tcl9/twapi-5.0b1/win.tcl | 131 + .../twapi-5.0b1/win32-ix86/tcl9twapi50b1.dll | Bin 0 -> 530432 bytes .../twapi-5.0b1/win32-ix86/twapi50b1t.dll | Bin 0 -> 531456 bytes .../win32-x86_64/tcl9twapi50b1.dll | Bin 0 -> 658432 bytes .../twapi-5.0b1/win32-x86_64/twapi50b1t.dll | Bin 0 -> 652288 bytes src/vendorlib_tcl9/twapi-5.0b1/winlog.tcl | 304 + src/vendorlib_tcl9/twapi-5.0b1/winsta.tcl | 113 + src/vendorlib_tcl9/twapi-5.0b1/wmi.tcl | 223 + src/vendorlib_tcl9/twapi-5.0b1/wts.tcl | 64 + src/vendormodules/fauxlink-0.1.0.tm | 10 + ...ackagetrace-0.7.tm => packagetrace-0.8.tm} | 215 +- 848 files changed, 461881 insertions(+), 36131 deletions(-) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/LICENSE (95%) create mode 100644 src/vendorlib_tcl8/twapi-5.0b1/README.md rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/account.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/adsi.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/apputil.tcl (91%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/base.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/clipboard.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/com.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/console.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/crypto.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/device.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/etw.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/eventlog.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/evt.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/handle.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/input.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/msi.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/mstask.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/multimedia.tcl (95%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/namedpipe.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/network.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/nls.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/os.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/pdh.tcl (97%) create mode 100644 src/vendorlib_tcl8/twapi-5.0b1/pkgIndex.tcl rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/power.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/printer.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/process.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/rds.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/registry.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/resource.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/security.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/service.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/share.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/shell.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/sspi.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/storage.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/synch.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/tls.tcl (95%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/twapi.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/ui.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/win.tcl (96%) create mode 100644 src/vendorlib_tcl8/twapi-5.0b1/win32-ix86/tcl9twapi50b1.dll create mode 100644 src/vendorlib_tcl8/twapi-5.0b1/win32-ix86/twapi50b1t.dll create mode 100644 src/vendorlib_tcl8/twapi-5.0b1/win32-x86_64/tcl9twapi50b1.dll create mode 100644 src/vendorlib_tcl8/twapi-5.0b1/win32-x86_64/twapi50b1t.dll rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/winlog.tcl (97%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/winsta.tcl (96%) rename src/vendorlib_tcl8/{twapi4.7.2 => twapi-5.0b1}/wmi.tcl (96%) create mode 100644 src/vendorlib_tcl8/twapi-5.0b1/wts.tcl delete mode 100644 src/vendorlib_tcl8/twapi4.7.2/metoo.tcl delete mode 100644 src/vendorlib_tcl8/twapi4.7.2/pkgIndex.tcl delete mode 100644 src/vendorlib_tcl8/twapi4.7.2/twapi472.dll delete mode 100644 src/vendorlib_tcl8/twapi4.7.2/twapi_entry.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/0compatibility/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/aes/aes.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/aes/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/amazon-s3/S3.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/amazon-s3/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/amazon-s3/xsxp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/asn/asn.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/asn/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/base32.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/base32_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/base32_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/base32core.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/base32hex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/base32hex_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/base32hex_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base32/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base64/ascii85.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base64/base64.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base64/base64c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base64/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base64/uuencode.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/base64/yencode.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bee/bee.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bee/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bench/bench.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bench/bench_read.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bench/bench_wcsv.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bench/bench_wtext.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bench/libbench.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bench/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bibtex/bibtex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/bibtex/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/blowfish/blowfish.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/blowfish/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/cache/async.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/cache/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/clay/clay.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/clay/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/clock/iso8601.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/clock/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/clock/rfc2822.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/cmdline/cmdline.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/cmdline/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/comm/comm.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/comm/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/control/ascaller.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/control/assert.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/control/control.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/control/do.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/control/no-op.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/control/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/control/tclIndex create mode 100644 src/vendorlib_tcl9/tcllib2.0/coroutine/coro_auto.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/coroutine/coroutine.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/coroutine/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/counter/counter.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/counter/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/crc/cksum.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/crc/crc16.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/crc/crc32.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/crc/crc32c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/crc/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/crc/sum.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/crc/sumc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/cron/cron.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/cron/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/csv/csv.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/csv/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/debug/caller.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/debug/debug.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/debug/heartbeat.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/debug/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/debug/timestamp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/defer/defer.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/defer/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/des/des.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/des/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/des/tcldes.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/des/tcldesjr.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dicttool/dicttool.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dicttool/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/dns.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/ip.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/ipMore.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/ipMoreC.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/msgs/en.msg create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/resolv.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dns/spf.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/docstrip/docstrip.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/docstrip/docstrip_util.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/docstrip/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/api.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/api_idx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/api_toc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/changelog.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/checker.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/checker_idx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/checker_toc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/cvs.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/docidx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/doctoc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/doctools.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_common.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_html.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_idx_common.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_markdown.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_nroff.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_bullets.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_ccore.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_cstack.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_dlist.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_margin.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_para.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_state.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_text_utils.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_toc_common.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_xml.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/_xref.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/c.msg create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/de.msg create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/en.msg create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.desc create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.html create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.latex create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.list create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.markdown create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.nroff create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.null create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.text create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.tmml create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fmt.wiki create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/fr.msg create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/idx.html create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/idx.markdown create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/idx.nroff create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/idx.null create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/idx.text create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/idx.wiki create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/man.macros create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/toc.html create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/toc.markdown create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/toc.nroff create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/toc.null create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/toc.text create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/toc.tmml create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/mpformats/toc.wiki create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2base/html.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2base/html_cssdefaults.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2base/msgcat.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2base/nroff_manmacros.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2base/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2base/tcl_parse.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2base/text.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/container.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/export.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/export_docidx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/export_html.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/export_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/export_nroff.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/export_text.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/export_wiki.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/import.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/import_docidx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/import_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/msgcat_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/msgcat_de.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/msgcat_en.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/msgcat_fr.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/parse.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2idx/structure.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/container.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/export.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/export_doctoc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/export_html.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/export_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/export_nroff.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/export_text.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/export_wiki.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/import.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/import_doctoc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/import_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/msgcat_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/msgcat_de.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/msgcat_en.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/msgcat_fr.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/parse.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/doctools2toc/structure.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dtplite/dtplite.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/dtplite/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fileutil/decode.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fileutil/fileutil.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fileutil/multi.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fileutil/multiop.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fileutil/paths.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fileutil/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fileutil/traverse.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ftp/ftp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ftp/ftp_geturl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ftp/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ftpd/ftpd.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ftpd/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fumagic/cfront.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fumagic/cgen.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fumagic/filetypes.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fumagic/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/fumagic/rtcore.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/generator/generator.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/generator/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/gpx/gpx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/gpx/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_aycock/aycock-build.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_aycock/aycock-debug.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_aycock/aycock-runtime.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_aycock/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_fa/dacceptor.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_fa/dexec.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_fa/fa.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_fa/faop.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_fa/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_me/gasm.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_me/me_cpu.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_me/me_cpucore.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_me/me_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_me/me_util.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_me/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_peg/peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_peg/peg_interp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/grammar_peg/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/hook/hook.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/hook/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/html/html.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/html/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/htmlparse/htmlparse.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/htmlparse/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/http/autoproxy.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/http/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/httpd/httpd.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/httpd/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/httpwget/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/httpwget/wget.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ident/ident.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ident/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/imap4/imap4.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/imap4/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/inifile/ini.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/inifile/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/interp/deleg_method.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/interp/deleg_proc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/interp/interp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/interp/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/irc/irc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/irc/picoirc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/irc/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/javascript/javascript.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/javascript/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/jpeg/jpeg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/jpeg/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/json/json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/json/json_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/json/json_write.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/json/jsonc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/json/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/lambda/lambda.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/lambda/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/lazyset/lazyset.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/lazyset/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ldap/ldap.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ldap/ldapx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ldap/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/log/log.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/log/logger.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/log/loggerAppender.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/log/loggerUtils.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/log/msgs/en.msg create mode 100644 src/vendorlib_tcl9/tcllib2.0/log/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/map/map_geocode_nominatim.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/map/map_slippy.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/map/map_slippy_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/map/map_slippy_cache.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/map/map_slippy_fetcher.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/map/map_slippy_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/map/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/mapproj/mapproj.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/mapproj/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/markdown/markdown.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/markdown/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/bessel.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/bigfloat2.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/bignum.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/calculus.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/changepoint.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/classic_polyns.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/combinatorics.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/combinatoricsExt.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/constants.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/decimal.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/elliptic.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/exact.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/exponential.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/figurate.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/filtergen.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/fourier.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/fuzzy.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/geometry.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/geometry_circle.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/geometry_ext.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/interpolate.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/kruskal.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/linalg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/liststat.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/machineparameters.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/math.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/misc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/mvlinreg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/numtheory.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/optimize.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/pca.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/pdf_stat.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/plotstat.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/polynomials.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/primes.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/probopt.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/probopt_diffev.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/probopt_lipo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/probopt_pso.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/probopt_sce.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/qcomplex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/quasirandom.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/rational_funcs.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/romannumerals.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/rootfind.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/special.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/stat_kernel.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/stat_logit.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/stat_wasserstein.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/statistics.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/symdiff.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/tclIndex create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/trig.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/math/wilcoxon.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md4/md4.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md4/md4c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md4/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5/md5.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5/md5c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5/md5x.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5c/critcl-rt.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5c/license.terms create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5c/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5c/teapot.txt create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5c/win32-x86_64/md5c.dll create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5crypt/md5crypt.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5crypt/md5cryptc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/md5crypt/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/mime/mime.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/mime/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/mime/smtp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/mkdoc/mkdoc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/mkdoc/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/multiplexer/multiplexer.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/multiplexer/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/namespacex/namespacex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/namespacex/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ncgi/ncgi.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ncgi/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nettool/nettool.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nettool/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nmea/nmea.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nmea/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nns/common.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nns/nns.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nns/nns_auto.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nns/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nns/server.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nntp/nntp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/nntp/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ntp/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ntp/time.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/oauth/oauth.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/oauth/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/oodialect/oodialect.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/oodialect/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/oometa/oometa.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/oometa/oooption.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/oometa/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ooutil/ooutil.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ooutil/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/otp/otp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/otp/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/analysis_peg_emodes.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/analysis_peg_minimize.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/analysis_peg_reachable.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/analysis_peg_realizable.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/compiler_peg_mecpu.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_canon.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_cpkg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_hb.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_me.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_me.template create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_mecpu.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_mecpu.template create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_peg_ser.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/gen_tree_text.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/parse_lemon.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/parse_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/parse_peghb.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/parse_pegser.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/peg_grammar.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/pluginmgr.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/config_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/reader_hb.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/reader_lemon.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/reader_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/reader_ser.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/reader_treeser.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/transform_mecpu.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/transform_reachable.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/transform_realizable.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_hb.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_identity.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_me.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_mecpu.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_null.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_ser.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_tpc.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/plugins/writer_tree.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/util_flow.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/util_norm_lemon.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/util_norm_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/util_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/page/util_quote.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pki/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pki/pki.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pluginmgr/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pluginmgr/pluginmgr.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/png/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/png/png.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pop3/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pop3/pop3.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pop3d/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pop3d/pop3d.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pop3d/pop3d_dbox.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pop3d/pop3d_udb.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/practcl/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/practcl/practcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/processman/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/processman/processman.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/profiler/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/profiler/profiler.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/char.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_astree.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_cparam_config_critcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_cparam_config_tea.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_parse_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_parse_peg_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_parse_peg_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_container.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_container_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_export.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_export_container.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_export_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_export_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_from_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_from_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_import.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_import_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_import_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_interp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_op.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_to_container.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_to_cparam.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_to_json.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_to_param.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_to_peg.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_peg_to_tclparam.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_pegrammar.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_pexpr_op.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_pexpression.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_pgen.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_rdengine.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_rdengine_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_rdengine_nx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_rdengine_oo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_rdengine_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_tclparam_config_nx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_tclparam_config_snit.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_tclparam_config_tcloo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/pt_util.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/m.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/m.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/ms.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/ms.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/ot.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/ot.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/p.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/p.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/pInt.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/param.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/param.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/stack.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/stack.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/tc.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/tc.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/util.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/rde_critcl/util.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/pt/text_write.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/rc4/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/rc4/rc4.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/rc4/rc4c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/rcs/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/rcs/rcs.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/report/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/report/report.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/rest/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/rest/rest.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ripemd/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ripemd/ripemd128.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/ripemd/ripemd160.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sasl/gtoken.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sasl/ntlm.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sasl/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sasl/sasl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sasl/scram.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha1.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha1.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha1.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha1c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha1v1.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha256.c create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha256.h create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha256.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/sha1/sha256c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/simulation/annealing.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/simulation/montecarlo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/simulation/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/simulation/random.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/smtpd/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/smtpd/smtpd.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/snit/main1.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/snit/main2.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/snit/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/snit/snit.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/snit/snit2.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/snit/validate.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/soundex/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/soundex/soundex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stooop/mkpkgidx.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stooop/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stooop/stooop.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stooop/switched.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stooop/xifo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/string/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/string/token.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/string/token_shell.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stringprep/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stringprep/stringprep.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stringprep/stringprep_data.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stringprep/unicode.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/stringprep/unicode_data.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/disjointset.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/graph.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/graph1.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/graph_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/graph_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/graphops.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/list.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/list.test.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/map.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/matrix.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/pool.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/prioqueue.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/queue.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/queue_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/queue_oo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/queue_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/record.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/sets.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/sets_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/sets_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/skiplist.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/stack.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/stack_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/stack_oo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/stack_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/struct.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/struct1.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/tree.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/tree1.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/tree_c.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/struct/tree_tcl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tar/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tar/tar.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tcllibc/critcl-rt.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tcllibc/license.terms create mode 100644 src/vendorlib_tcl9/tcllib2.0/tcllibc/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tcllibc/teapot.txt create mode 100644 src/vendorlib_tcl9/tcllib2.0/tcllibc/win32-x86_64/tcllibc.dll create mode 100644 src/vendorlib_tcl9/tcllib2.0/tepam/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tepam/tepam.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tepam/tepam_doc_gen.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/ansi/code.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/ansi/code/attr.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/ansi/code/ctrl.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/ansi/code/macros.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/ansi/ctrlunix.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/ansi/send.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/bind.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/imenu.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/ipager.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/receive.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/send.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/term/term.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/adjust.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/dehypht.tex create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/eshyph_vo.tex create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/expander.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/ithyph.tex create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/patch.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/repeat.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/split.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/string.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/tabify.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/textutil.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/trim.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/textutil/wcswidth.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/tie.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/tie_array.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/tie_dsource.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/tie_file.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/tie_growfile.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/tie_log.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tie/tie_rarray.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tiff/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tiff/tiff.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tool/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/tool/tool.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/connect.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/copyops.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/ddest.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/dsource.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/receiver.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/tqueue.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/transfer/transmitter.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/treeql/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/treeql/treeql.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/treeql/treeql84.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/treeql/treeql85.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/try/fhome.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/try/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/try/throw.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/try/try.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/udpcluster/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/udpcluster/udpcluster.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uev/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uev/uevent.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uev/uevent_onidle.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/units/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/units/units.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uri/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uri/uri.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uri/urn-scheme.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uuid/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/uuid/uuid.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/cc_amex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/cc_discover.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/cc_mastercard.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/cc_visa.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/ean13.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/iban.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/imei.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/isbn.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/luhn.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/luhn5.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/usnpi.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/valtype.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/valtype/verhoeff.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/cat.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/facade.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/fifo.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/fifo2.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/halfpipe.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/memchan.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/null.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/nullzero.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/random.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/randseed.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/std.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/string.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/textwindow.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/variable.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_base/zero.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_core/core.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_core/events.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_core/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_core/transformcore.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/adler32.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/base64.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/counter.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/crc32.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/hex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/identity.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/limitsize.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/observe.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/otp.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/rot.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/spacer.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/virtchannel_transform/zlib.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/websocket/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/websocket/websocket.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/wip/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/wip/wip.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/wip/wip2.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/yaml/huddle.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/yaml/huddle_types.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/yaml/json2huddle.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/yaml/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/yaml/yaml.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/zip/decode.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/zip/encode.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/zip/mkzip.tcl create mode 100644 src/vendorlib_tcl9/tcllib2.0/zip/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/LICENSE create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/README.md create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/account.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/adsi.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/apputil.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/base.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/clipboard.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/com.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/console.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/crypto.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/device.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/etw.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/eventlog.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/evt.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/handle.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/input.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/msi.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/mstask.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/multimedia.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/namedpipe.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/network.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/nls.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/os.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/pdh.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/pkgIndex.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/power.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/printer.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/process.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/rds.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/registry.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/resource.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/security.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/service.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/share.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/shell.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/sspi.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/storage.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/synch.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/tls.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/twapi.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/ui.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/win.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/win32-ix86/tcl9twapi50b1.dll create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/win32-ix86/twapi50b1t.dll create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/win32-x86_64/tcl9twapi50b1.dll create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/win32-x86_64/twapi50b1t.dll create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/winlog.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/winsta.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/wmi.tcl create mode 100644 src/vendorlib_tcl9/twapi-5.0b1/wts.tcl rename src/vendormodules/{packagetrace-0.7.tm => packagetrace-0.8.tm} (65%) diff --git a/src/vendorlib_tcl8/twapi4.7.2/LICENSE b/src/vendorlib_tcl8/twapi-5.0b1/LICENSE similarity index 95% rename from src/vendorlib_tcl8/twapi4.7.2/LICENSE rename to src/vendorlib_tcl8/twapi-5.0b1/LICENSE index fcfc79f8..aac18fff 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/LICENSE +++ b/src/vendorlib_tcl8/twapi-5.0b1/LICENSE @@ -1,29 +1,29 @@ -Copyright (c) 2003-2012, Ashok P. Nadkarni -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -- Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -- The name of the copyright holder and any other contributors may not -be used to endorse or promote products derived from this software -without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2003-2024, Ashok P. Nadkarni +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- The name of the copyright holder and any other contributors may not +be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/vendorlib_tcl8/twapi-5.0b1/README.md b/src/vendorlib_tcl8/twapi-5.0b1/README.md new file mode 100644 index 00000000..4044cf5a --- /dev/null +++ b/src/vendorlib_tcl8/twapi-5.0b1/README.md @@ -0,0 +1,73 @@ +# Tcl Windows API (TWAPI) extension + +The Tcl Windows API (TWAPI) extension provides access to the Windows API from +within the Tcl scripting language. + + * Project source repository is at https://github.com/apnadkarni/twapi + * Documentation is at https://twapi.magicsplat.com + * Binary distribution is at https://sourceforge.net/projects/twapi/files/Current%20Releases/Tcl%20Windows%20API/ + +## Supported platforms + +TWAPI 5.0 requires + + * Windows 7 SP1 or later + * Tcl 8.6.10+ or Tcl 9.x + +### Binary distribution + +The single binary distribution supports Tcl 8.6 and Tcl 9 for both 32- and +64-bit platforms. + +It requires the VC++ runtime to already be installed +on the system. Download from https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist if necessary. + +Windows 7 and 8.x also require the Windows UCRT runtime to be installed if not +present. Download from https://support.microsoft.com/en-gb/topic/update-for-universal-c-runtime-in-windows-c0514201-7fe6-95a3-b0a5-287930f3560c. + +In most cases, both the above should already be present on the system. + +Note that the *modular* and single file *bin* in 4.x distributions are no longer +available and will not be supported in 5.0. + +## TWAPI Summary + +The Tcl Windows API (TWAPI) extension provides access to the Windows API from +within the Tcl scripting language. + +Functions in the following areas are implemented: + + * System functions including OS and CPU information, + shutdown and message formatting + * User and group management + * COM client and server support + * Security and resource access control + * Window management + * User input: generate key/mouse input and hotkeys + * Basic sound playback functions + * Windows services + * Windows event log access + * Windows event tracing + * Process and thread management + * Directory change monitoring + * Lan Manager and file and print shares + * Drive information, file system types etc. + * Network configuration and statistics + * Network connection monitoring and control + * Named pipes + * Clipboard access + * Taskbar icons and notifications + * Console mode functions + * Window stations and desktops + * Internationalization + * Task scheduling + * Shell functions + * Registry + * Windows Management Instrumentation + * Windows Installer + * Synchronization + * Power management + * Device I/O and management + * Crypto API and certificates + * SSL/TLS + * Windows Performance Counters diff --git a/src/vendorlib_tcl8/twapi4.7.2/account.tcl b/src/vendorlib_tcl8/twapi-5.0b1/account.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/account.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/account.tcl index 2b87b35d..9230ffc3 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/account.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/account.tcl @@ -1,1160 +1,1160 @@ -# -# Copyright (c) 2009-2015, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -package require twapi_security - -namespace eval twapi { - record USER_INFO_0 {-name} - record USER_INFO_1 [concat [USER_INFO_0] { - -password -password_age -priv -home_dir -comment -flags -script_path - }] - record USER_INFO_2 [concat [USER_INFO_1] { - -auth_flags -full_name -usr_comment -parms - -workstations -last_logon -last_logoff -acct_expires -max_storage - -units_per_week -logon_hours -bad_pw_count -num_logons - -logon_server -country_code -code_page - }] - record USER_INFO_3 [concat [USER_INFO_2] { - -user_id -primary_group_id -profile -home_dir_drive -password_expired - }] - record USER_INFO_4 [concat [USER_INFO_2] { - -sid -primary_group_id -profile -home_dir_drive -password_expired - }] - - record GROUP_INFO_0 {-name} - record GROUP_INFO_1 {-name -comment} - record GROUP_INFO_2 {-name -comment -group_id -attributes} - record GROUP_INFO_3 {-name -comment -sid -attributes} - - record NetEnumResult {moredata hresume totalentries entries} - -} - -# Add a new user account -proc twapi::new_user {username args} { - array set opts [parseargs args [list \ - system.arg \ - password.arg \ - comment.arg \ - [list priv.arg "user" [array names twapi::priv_level_map]] \ - home_dir.arg \ - script_path.arg \ - ] \ - -nulldefault] - - if {$opts(priv) ne "user"} { - error "Option -priv is deprecated and values other than 'user' are not allowed" - } - - # 1 -> priv level 'user'. NetUserAdd mandates this as only allowed value - NetUserAdd $opts(system) $username $opts(password) 1 \ - $opts(home_dir) $opts(comment) 0 $opts(script_path) - - - # Backward compatibility - add to 'Users' local group - # but only if -system is local - if {$opts(system) eq "" || - ([info exists ::env(COMPUTERNAME)] && - [string equal -nocase $opts(system) $::env(COMPUTERNAME)])} { - trap { - _set_user_priv_level $username $opts(priv) -system $opts(system) - } onerror {} { - # Remove the previously created user account - catch {delete_user $username -system $opts(system)} - rethrow - } - } -} - - -# Delete a user account -proc twapi::delete_user {username args} { - array set opts [parseargs args {system.arg} -nulldefault] - - # Remove the user from the LSA rights database. - _delete_rights $username $opts(system) - - NetUserDel $opts(system) $username -} - - -# Define various functions to set various user account fields -foreach twapi::_field_ { - {name 0} - {password 1003} - {home_dir 1006} - {comment 1007} - {script_path 1009} - {full_name 1011} - {country_code 1024} - {profile 1052} - {home_dir_drive 1053} -} { - proc twapi::set_user_[lindex $::twapi::_field_ 0] {username fieldval args} " - array set opts \[parseargs args { - system.arg - } -nulldefault \] - Twapi_NetUserSetInfo [lindex $::twapi::_field_ 1] \$opts(system) \$username \$fieldval" -} -unset twapi::_field_ - -# Set account expiry time -proc twapi::set_user_expiration {username time args} { - array set opts [parseargs args {system.arg} -nulldefault] - - if {![string is integer -strict $time]} { - if {[string equal $time "never"]} { - set time -1 - } else { - set time [clock scan $time] - } - } - Twapi_NetUserSetInfo 1017 $opts(system) $username $time -} - -# Unlock a user account -proc twapi::unlock_user {username args} { - # UF_LOCKOUT -> 0x10 - _change_user_info_flags $username 0x10 0 {*}$args -} - -# Enable a user account -proc twapi::enable_user {username args} { - # UF_ACCOUNTDISABLE -> 0x2 - _change_user_info_flags $username 0x2 0 {*}$args -} - -# Disable a user account -proc twapi::disable_user {username args} { - # UF_ACCOUNTDISABLE -> 0x2 - _change_user_info_flags $username 0x2 0x2 {*}$args -} - - -# Return the specified fields for a user account -proc twapi::get_user_account_info {account args} { - # Define each option, the corresponding field, and the - # information level at which it is returned - array set fields { - comment 1 - password_expired 4 - full_name 2 - parms 2 - units_per_week 2 - primary_group_id 4 - flags 1 - logon_server 2 - country_code 2 - home_dir 1 - password_age 1 - home_dir_drive 4 - num_logons 2 - acct_expires 2 - last_logon 2 - usr_comment 2 - bad_pw_count 2 - code_page 2 - logon_hours 2 - workstations 2 - last_logoff 2 - name 0 - script_path 1 - profile 4 - max_storage 2 - } - # Left out - auth_flags 2 - # Left out (always returned as NULL) - password {usri3_password 1} - # Note sid is available at level 4 as well but don't want to set - # level 4 just for that since we can get it by other means. Hence - # not listed above - - array set opts [parseargs args \ - [concat [array names fields] sid \ - internet_identity \ - status type password_attrs \ - [list local_groups global_groups system.arg all]] \ - -nulldefault] - - if {$opts(all)} { - set level 4 - set opts(local_groups) 1 - set opts(global_groups) 1 - } else { - # Based on specified fields, figure out what level info to ask for - set level -1 - foreach {opt optval} [array get opts] { - if {[info exists fields($opt)] && - $optval && - $fields($opt) > $level - } { - set level $fields($opt) - } - } - if {$opts(status) || $opts(type) || $opts(password_attrs)} { - # These fields are based on the flags field - if {$level < 1} { - set level 1 - } - } - } - - array set result [list ] - - if {$level > -1} { - set rawdata [NetUserGetInfo $opts(system) $account $level] - array set data [USER_INFO_$level $rawdata] - - # Extract the requested data - foreach opt [array names fields] { - if {$opts(all) || $opts($opt)} { - set result(-$opt) $data(-$opt) - } - } - if {$level == 4 && ($opts(all) || $opts(sid))} { - set result(-sid) $data(-sid) - } - - # Map internal values to more friendly formats - if {$opts(all) || $opts(status) || $opts(type) || $opts(password_attrs)} { - array set result [_map_userinfo_flags $data(-flags)] - if {! $opts(all)} { - if {! $opts(status)} {unset result(-status)} - if {! $opts(type)} {unset result(-type)} - if {! $opts(password_attrs)} {unset result(-password_attrs)} - } - } - - if {[info exists result(-logon_hours)]} { - binary scan $result(-logon_hours) b* result(-logon_hours) - } - - foreach time_field {-acct_expires -last_logon -last_logoff} { - if {[info exists result($time_field)]} { - if {$result($time_field) == -1 || $result($time_field) == 4294967295} { - set result($time_field) "never" - } elseif {$result($time_field) == 0} { - set result($time_field) "unknown" - } - } - } - } - - if {$opts(all) || $opts(internet_identity)} { - set result(-internet_identity) {} - if {[min_os_version 6 2]} { - set inet_ident [NetUserGetInfo $opts(system) $account 24] - if {[llength $inet_ident]} { - set result(-internet_identity) [twine { - internet_provider_name internet_principal_name sid - } [lrange $inet_ident 1 end]] - } - } - } - - # The Net* calls always return structures as lists even when the struct - # contains only one field so we need to lpick to extract the field - - if {$opts(local_groups)} { - set result(-local_groups) [lpick [NetEnumResult entries [NetUserGetLocalGroups $opts(system) $account 0 0]] 0] - } - - if {$opts(global_groups)} { - set result(-global_groups) [lpick [NetEnumResult entries [NetUserGetGroups $opts(system) $account 0]] 0] - } - - if {$opts(sid) && ! [info exists result(-sid)]} { - set result(-sid) [lookup_account_name $account -system $opts(system)] - } - - return [array get result] -} - -proc twapi::get_user_global_groups {account args} { - parseargs args { - system.arg - denyonly - all - } -nulldefault -maxleftover 0 -setvars - - set groups {} - foreach elem [NetEnumResult entries [NetUserGetGroups $system [map_account_to_name $account -system $system] 1]] { - # 0x10 -> SE_GROUP_USE_FOR_DENY_ONLY - set marked_denyonly [expr {[lindex $elem 1] & 0x10}] - if {$all || ($denyonly && $marked_denyonly) || !($denyonly || $marked_denyonly)} { - lappend groups [lindex $elem 0] - } - } - return $groups -} - -proc twapi::get_user_local_groups {account args} { - parseargs args { - system.arg - {recurse.bool 0} - } -nulldefault -maxleftover 0 -setvars - - # The Net* calls always return structures as lists even when the struct - # contains only one field so we need to lpick to extract the field - return [lpick [NetEnumResult entries [NetUserGetLocalGroups $system [map_account_to_name $account -system $system] 0 $recurse]] 0] -} - -proc twapi::get_user_local_groups_recursive {account args} { - return [get_user_local_groups $account {*}$args -recurse 1] -} - - -# Set the specified fields for a user account -proc twapi::set_user_account_info {account args} { - - # Define each option, the corresponding field, and the - # information level at which it is returned - array set opts [parseargs args { - {system.arg ""} - comment.arg - full_name.arg - country_code.arg - home_dir.arg - home_dir.arg - acct_expires.arg - name.arg - script_path.arg - profile.arg - }] - - # TBD - rewrite this to be atomic - - if {[info exists opts(comment)]} { - set_user_comment $account $opts(comment) -system $opts(system) - } - - if {[info exists opts(full_name)]} { - set_user_full_name $account $opts(full_name) -system $opts(system) - } - - if {[info exists opts(country_code)]} { - set_user_country_code $account $opts(country_code) -system $opts(system) - } - - if {[info exists opts(home_dir)]} { - set_user_home_dir $account $opts(home_dir) -system $opts(system) - } - - if {[info exists opts(home_dir_drive)]} { - set_user_home_dir_drive $account $opts(home_dir_drive) -system $opts(system) - } - - if {[info exists opts(acct_expires)]} { - set_user_expiration $account $opts(acct_expires) -system $opts(system) - } - - if {[info exists opts(name)]} { - set_user_name $account $opts(name) -system $opts(system) - } - - if {[info exists opts(script_path)]} { - set_user_script_path $account $opts(script_path) -system $opts(system) - } - - if {[info exists opts(profile)]} { - set_user_profile $account $opts(profile) -system $opts(system) - } -} - - -proc twapi::get_global_group_info {grpname args} { - array set opts [parseargs args { - {system.arg ""} - comment - name - members - sid - attributes - all - } -maxleftover 0] - - set result {} - if {[expr {$opts(comment) || $opts(name) || $opts(sid) || $opts(attributes) || $opts(all)}]} { - # 3 -> GROUP_INFO level 3 - lassign [NetGroupGetInfo $opts(system) $grpname 3] name comment sid attributes - if {$opts(all) || $opts(sid)} { - lappend result -sid $sid - } - if {$opts(all) || $opts(name)} { - lappend result -name $name - } - if {$opts(all) || $opts(comment)} { - lappend result -comment $comment - } - if {$opts(all) || $opts(attributes)} { - lappend result -attributes [map_token_group_attr $attributes] - } - } - - if {$opts(all) || $opts(members)} { - lappend result -members [get_global_group_members $grpname -system $opts(system)] - } - - return $result -} - -# Get info about a local or global group -proc twapi::get_local_group_info {name args} { - array set opts [parseargs args { - {system.arg ""} - comment - name - members - sid - all - } -maxleftover 0] - - set result [list ] - if {$opts(all) || $opts(sid)} { - lappend result -sid [lookup_account_name $name -system $opts(system)] - } - if {$opts(all) || $opts(comment) || $opts(name)} { - lassign [NetLocalGroupGetInfo $opts(system) $name 1] name comment - if {$opts(all) || $opts(name)} { - lappend result -name $name - } - if {$opts(all) || $opts(comment)} { - lappend result -comment $comment - } - } - if {$opts(all) || $opts(members)} { - lappend result -members [get_local_group_members $name -system $opts(system)] - } - return $result -} - -# Get list of users on a system -proc twapi::get_users {args} { - parseargs args { - level.int - } -setvars -ignoreunknown - - # TBD -allow user to specify filter - lappend args -filter 0 - if {[info exists level]} { - lappend args -level $level -fields [USER_INFO_$level] - } - return [_net_enum_helper NetUserEnum $args] -} - -proc twapi::get_global_groups {args} { - parseargs args { - level.int - } -setvars -ignoreunknown - - # TBD - level 3 returns an ERROR_INVALID_LEVEL even though - # MSDN says its valid for NetGroupEnum - - if {[info exists level]} { - lappend args -level $level -fields [GROUP_INFO_$level] - } - return [_net_enum_helper NetGroupEnum $args] -} - -proc twapi::get_local_groups {args} { - parseargs args { - level.int - } -setvars -ignoreunknown - - if {[info exists level]} { - lappend args -level $level -fields [dict get {0 {-name} 1 {-name -comment}} $level] - } - return [_net_enum_helper NetLocalGroupEnum $args] -} - -# Create a new global group -proc twapi::new_global_group {grpname args} { - array set opts [parseargs args { - system.arg - comment.arg - } -nulldefault] - - NetGroupAdd $opts(system) $grpname $opts(comment) -} - -# Create a new local group -proc twapi::new_local_group {grpname args} { - array set opts [parseargs args { - system.arg - comment.arg - } -nulldefault] - - NetLocalGroupAdd $opts(system) $grpname $opts(comment) -} - - -# Delete a global group -proc twapi::delete_global_group {grpname args} { - array set opts [parseargs args {system.arg} -nulldefault] - - # Remove the group from the LSA rights database. - _delete_rights $grpname $opts(system) - - NetGroupDel $opts(system) $grpname -} - -# Delete a local group -proc twapi::delete_local_group {grpname args} { - array set opts [parseargs args {system.arg} -nulldefault] - - # Remove the group from the LSA rights database. - _delete_rights $grpname $opts(system) - - NetLocalGroupDel $opts(system) $grpname -} - - -# Enumerate members of a global group -proc twapi::get_global_group_members {grpname args} { - parseargs args { - level.int - } -setvars -ignoreunknown - - if {[info exists level]} { - lappend args -level $level -fields [dict! {0 {-name} 1 {-name -attributes}} $level] - } - - lappend args -preargs [list $grpname] -namelevel 1 - return [_net_enum_helper NetGroupGetUsers $args] -} - -# Enumerate members of a local group -proc twapi::get_local_group_members {grpname args} { - parseargs args { - level.int - } -setvars -ignoreunknown - - if {[info exists level]} { - lappend args -level $level -fields [dict! {0 {-sid} 1 {-sid -sidusage -name} 2 {-sid -sidusage -domainandname} 3 {-domainandname}} $level] - } - - lappend args -preargs [list $grpname] -namelevel 1 -namefield 2 - return [_net_enum_helper NetLocalGroupGetMembers $args] -} - -# Add a user to a global group -proc twapi::add_user_to_global_group {grpname username args} { - array set opts [parseargs args {system.arg} -nulldefault] - - # No error if already member of the group - trap { - NetGroupAddUser $opts(system) $grpname $username - } onerror {TWAPI_WIN32 1320} { - # Ignore - } -} - - -# Remove a user from a global group -proc twapi::remove_user_from_global_group {grpname username args} { - array set opts [parseargs args {system.arg} -nulldefault] - - trap { - NetGroupDelUser $opts(system) $grpname $username - } onerror {TWAPI_WIN32 1321} { - # Was not in group - ignore - } -} - - -# Add a user to a local group -proc twapi::add_member_to_local_group {grpname username args} { - array set opts [parseargs args { - system.arg - {type.arg name} - } -nulldefault] - - # No error if already member of the group - trap { - Twapi_NetLocalGroupMembers 0 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] [list $username] - } onerror {TWAPI_WIN32 1378} { - # Ignore - } -} - -proc twapi::add_members_to_local_group {grpname accts args} { - array set opts [parseargs args { - system.arg - {type.arg name} - } -nulldefault] - - Twapi_NetLocalGroupMembers 0 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] $accts -} - - -# Remove a user from a local group -proc twapi::remove_member_from_local_group {grpname username args} { - array set opts [parseargs args { - system.arg - {type.arg name} - } -nulldefault] - - trap { - Twapi_NetLocalGroupMembers 1 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] [list $username] - } onerror {TWAPI_WIN32 1377} { - # Was not in group - ignore - } -} - -proc twapi::remove_members_from_local_group {grpname accts args} { - array set opts [parseargs args { - system.arg - {type.arg name} - } -nulldefault] - - Twapi_NetLocalGroupMembers 1 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] $accts -} - - -# Get rights for an account -proc twapi::get_account_rights {account args} { - array set opts [parseargs args { - {system.arg ""} - } -maxleftover 0] - - set sid [map_account_to_sid $account -system $opts(system)] - - trap { - set lsah [get_lsa_policy_handle -system $opts(system) -access policy_lookup_names] - return [Twapi_LsaEnumerateAccountRights $lsah $sid] - } onerror {TWAPI_WIN32 2} { - # No specific rights for this account - return [list ] - } finally { - if {[info exists lsah]} { - close_lsa_policy_handle $lsah - } - } -} - -# Get accounts having a specific right -proc twapi::find_accounts_with_right {right args} { - array set opts [parseargs args { - {system.arg ""} - name - } -maxleftover 0] - - trap { - set lsah [get_lsa_policy_handle \ - -system $opts(system) \ - -access { - policy_lookup_names - policy_view_local_information - }] - set accounts [list ] - foreach sid [Twapi_LsaEnumerateAccountsWithUserRight $lsah $right] { - if {$opts(name)} { - if {[catch {lappend accounts [lookup_account_sid $sid -system $opts(system)]}]} { - # No mapping for SID - can happen if account has been - # deleted but LSA policy not updated accordingly - lappend accounts $sid - } - } else { - lappend accounts $sid - } - } - return $accounts - } onerror {TWAPI_WIN32 259} { - # No accounts have this right - return [list ] - } finally { - if {[info exists lsah]} { - close_lsa_policy_handle $lsah - } - } - -} - -# Add/remove rights to an account -proc twapi::_modify_account_rights {operation account rights args} { - set switches { - system.arg - handle.arg - } - - switch -exact -- $operation { - add { - # Nothing to do - } - remove { - lappend switches all - } - default { - error "Invalid operation '$operation' specified" - } - } - - array set opts [parseargs args $switches -maxleftover 0] - - if {[info exists opts(system)] && [info exists opts(handle)]} { - error "Options -system and -handle may not be specified together" - } - - if {[info exists opts(handle)]} { - set lsah $opts(handle) - set sid $account - } else { - if {![info exists opts(system)]} { - set opts(system) "" - } - - set sid [map_account_to_sid $account -system $opts(system)] - # We need to open a policy handle ourselves. First try to open - # with max privileges in case the account needs to be created - # and then retry with lower privileges if that fails - catch { - set lsah [get_lsa_policy_handle \ - -system $opts(system) \ - -access { - policy_lookup_names - policy_create_account - }] - } - if {![info exists lsah]} { - set lsah [get_lsa_policy_handle \ - -system $opts(system) \ - -access policy_lookup_names] - } - } - - trap { - if {$operation == "add"} { - LsaAddAccountRights $lsah $sid $rights - } else { - LsaRemoveAccountRights $lsah $sid $opts(all) $rights - } - } finally { - # Close the handle if we opened it - if {! [info exists opts(handle)]} { - close_lsa_policy_handle $lsah - } - } -} - -interp alias {} twapi::add_account_rights {} twapi::_modify_account_rights add -interp alias {} twapi::remove_account_rights {} twapi::_modify_account_rights remove - -# Return list of logon sesionss -proc twapi::find_logon_sessions {args} { - array set opts [parseargs args { - user.arg - type.arg - tssession.arg - } -maxleftover 0] - - set luids [LsaEnumerateLogonSessions] - if {! ([info exists opts(user)] || [info exists opts(type)] || - [info exists opts(tssession)])} { - return $luids - } - - - # Need to get the data for each session to see if it matches - set result [list ] - if {[info exists opts(user)]} { - set sid [map_account_to_sid $opts(user)] - } - if {[info exists opts(type)]} { - set logontypes [list ] - foreach logontype $opts(type) { - lappend logontypes [_logon_session_type_code $logontype] - } - } - - foreach luid $luids { - trap { - unset -nocomplain session - array set session [LsaGetLogonSessionData $luid] - - # For the local system account, no data is returned on some - # platforms - if {[array size session] == 0} { - set session(Sid) S-1-5-18; # SYSTEM - set session(Session) 0 - set session(LogonType) 0 - } - if {[info exists opts(user)] && $session(Sid) ne $sid} { - continue; # User id does not match - } - - if {[info exists opts(type)] && [lsearch -exact $logontypes $session(LogonType)] < 0} { - continue; # Type does not match - } - - if {[info exists opts(tssession)] && $session(Session) != $opts(tssession)} { - continue; # Term server session does not match - } - - lappend result $luid - - } onerror {TWAPI_WIN32 1312} { - # Session no longer exists. Just skip - continue - } - } - - return $result -} - - -# Return data for a logon session -proc twapi::get_logon_session_info {luid args} { - array set opts [parseargs args { - all - authpackage - dnsdomain - logondomain - logonid - logonserver - logontime - type - usersid - user - tssession - userprincipal - } -maxleftover 0] - - array set session [LsaGetLogonSessionData $luid] - - # Some fields may be missing on Win2K - foreach fld {LogonServer DnsDomainName Upn} { - if {![info exists session($fld)]} { - set session($fld) "" - } - } - - array set result [list ] - foreach {opt index} { - authpackage AuthenticationPackage - dnsdomain DnsDomainName - logondomain LogonDomain - logonid LogonId - logonserver LogonServer - logontime LogonTime - type LogonType - usersid Sid - user UserName - tssession Session - userprincipal Upn - } { - if {$opts(all) || $opts($opt)} { - set result(-$opt) $session($index) - } - } - - if {[info exists result(-type)]} { - set result(-type) [_logon_session_type_symbol $result(-type)] - } - - return [array get result] -} - - - - -# Set/reset the given bits in the usri3_flags field for a user account -# mask indicates the mask of bits to set. values indicates the values -# of those bits -proc twapi::_change_user_info_flags {username mask values args} { - array set opts [parseargs args { - system.arg - } -nulldefault -maxleftover 0] - - # Get current flags - set flags [USER_INFO_1 -flags [NetUserGetInfo $opts(system) $username 1]] - - # Turn off mask bits and write flags back - set flags [expr {$flags & (~ $mask)}] - # Set the specified bits - set flags [expr {$flags | ($values & $mask)}] - - # Write new flags back - Twapi_NetUserSetInfo 1008 $opts(system) $username $flags -} - -# Returns the logon session type value for a symbol -twapi::proc* twapi::_logon_session_type_code {type} { - variable _logon_session_type_map - # Variable that maps logon session type codes to integer values - # Position of each symbol gives its corresponding type value - # See ntsecapi.h for definitions - set _logon_session_type_map { - 0 - 1 - interactive - network - batch - service - proxy - unlockworkstation - networkclear - newcredentials - remoteinteractive - cachedinteractive - cachedremoteinteractive - cachedunlockworkstation - } -} { - variable _logon_session_type_map - - # Type may be an integer or a token - set code [lsearch -exact $_logon_session_type_map $type] - if {$code >= 0} { - return $code - } - - if {![string is integer -strict $type]} { - badargs! "Invalid logon session type '$type' specified" 3 - } - return $type -} - -# Returns the logon session type symbol for an integer value -proc twapi::_logon_session_type_symbol {code} { - variable _logon_session_type_map - _logon_session_type_code interactive; # Just to init _logon_session_type_map - set symbol [lindex $_logon_session_type_map $code] - if {$symbol eq ""} { - return $code - } else { - return $symbol - } -} - -proc twapi::_set_user_priv_level {username priv_level args} { - - array set opts [parseargs args {system.arg} -nulldefault] - - if {0} { - # FOr some reason NetUserSetInfo cannot change priv level - # Tried it separately with a simple C program. So this code - # is commented out and we use group membership to achieve - # the desired result - # Note: - latest MSDN confirms above - if {![info exists twapi::priv_level_map($priv_level)]} { - error "Invalid privilege level value '$priv_level' specified. Must be one of [join [array names twapi::priv_level_map] ,]" - } - set priv $twapi::priv_level_map($priv_level) - - Twapi_NetUserSetInfo_priv $opts(system) $username $priv - } else { - # Don't hardcode group names - reverse map SID's instead for - # non-English systems. Also note that since - # we might be lowering privilege level, we have to also - # remove from higher privileged groups - - switch -exact -- $priv_level { - guest { - # administrators users - set outgroups {S-1-5-32-544 S-1-5-32-545} - # guests - set ingroup S-1-5-32-546 - } - user { - # administrators - set outgroups {S-1-5-32-544} - # users - set ingroup S-1-5-32-545 - } - admin { - set outgroups {} - set ingroup S-1-5-32-544 - } - default {error "Invalid privilege level '$priv_level'. Must be one of 'guest', 'user' or 'admin'"} - } - # Remove from higher priv groups - foreach outgroup $outgroups { - # Get the potentially localized name of the group - set group [lookup_account_sid $outgroup -system $opts(system)] - # Catch since may not be member of that group - catch {remove_member_from_local_group $group $username -system $opts(system)} - } - - # Get the potentially localized name of the group to be added - set group [lookup_account_sid $ingroup -system $opts(system)] - add_member_to_local_group $group $username -system $opts(system) - } -} - -proc twapi::_map_userinfo_flags {flags} { - # UF_LOCKOUT -> 0x10, UF_ACCOUNTDISABLE -> 0x2 - if {$flags & 0x2} { - set status disabled - } elseif {$flags & 0x10} { - set status locked - } else { - set status enabled - } - - #define UF_TEMP_DUPLICATE_ACCOUNT 0x0100 - #define UF_NORMAL_ACCOUNT 0x0200 - #define UF_INTERDOMAIN_TRUST_ACCOUNT 0x0800 - #define UF_WORKSTATION_TRUST_ACCOUNT 0x1000 - #define UF_SERVER_TRUST_ACCOUNT 0x2000 - if {$flags & 0x0200} { - set type normal - } elseif {$flags & 0x0100} { - set type duplicate - } elseif {$flags & 0x0800} { - set type interdomain_trust - } elseif {$flags & 0x1000} { - set type workstation_trust - } elseif {$flags & 0x2000} { - set type server_trust - } else { - set type unknown - } - - set pw {} - #define UF_PASSWD_NOTREQD 0x0020 - if {$flags & 0x0020} { - lappend pw not_required - } - #define UF_PASSWD_CANT_CHANGE 0x0040 - if {$flags & 0x0040} { - lappend pw cannot_change - } - #define UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x0080 - if {$flags & 0x0080} { - lappend pw encrypted_text_allowed - } - #define UF_DONT_EXPIRE_PASSWD 0x10000 - if {$flags & 0x10000} { - lappend pw no_expiry - } - #define UF_SMARTCARD_REQUIRED 0x40000 - if {$flags & 0x40000} { - lappend pw smartcard_required - } - #define UF_PASSWORD_EXPIRED 0x800000 - if {$flags & 0x800000} { - lappend pw expired - } - - return [list -status $status -type $type -password_attrs $pw] -} - -twapi::proc* twapi::_define_user_modals {} { - struct _USER_MODALS_INFO_0 { - DWORD min_passwd_len; - DWORD max_passwd_age; - DWORD min_passwd_age; - DWORD force_logoff; - DWORD password_hist_len; - } - struct _USER_MODALS_INFO_1 { - DWORD role; - LPWSTR primary; - } - struct _USER_MODALS_INFO_2 { - LPWSTR domain_name; - PSID domain_id; - } - struct _USER_MODALS_INFO_3 { - DWORD lockout_duration; - DWORD lockout_observation_window; - DWORD lockout_threshold; - } - struct _USER_MODALS_INFO_1001 { - DWORD min_passwd_len; - } - struct _USER_MODALS_INFO_1002 { - DWORD max_passwd_age; - } - struct _USER_MODALS_INFO_1003 { - DWORD min_passwd_age; - } - struct _USER_MODALS_INFO_1004 { - DWORD force_logoff; - } - struct _USER_MODALS_INFO_1005 { - DWORD password_hist_len; - } - struct _USER_MODALS_INFO_1006 { - DWORD role; - } - struct _USER_MODALS_INFO_1007 { - LPWSTR primary; - } -} { -} - -twapi::proc* twapi::get_password_policy {{server_name ""}} { - _define_user_modals -} { - set result [NetUserModalsGet $server_name 0 [_USER_MODALS_INFO_0]] - dict with result { - if {$force_logoff == 4294967295 || $force_logoff == -1} { - set force_logoff never - } - if {$max_passwd_age == 4294967295 || $max_passwd_age == -1} { - set max_passwd_age none - } - } - return $result -} - -# TBD - doc & test -twapi::proc* twapi::get_system_role {{server_name ""}} { - _define_user_modals -} { - set result [NetUserModalsGet $server_name 1 [_USER_MODALS_INFO_1]] - dict set result role [dict* { - 0 standalone 1 member 2 backup 3 primary - } [dict get $result role]] - return $result -} - -# TBD - doc & test -twapi::proc* twapi::get_system_domain {{server_name ""}} { - _define_user_modals -} { - return [NetUserModalsGet $server_name 2 [_USER_MODALS_INFO_2]] -} - -twapi::proc* twapi::get_lockout_policy {{server_name ""}} { - _define_user_modals -} { - return [NetUserModalsGet $server_name 3 [_USER_MODALS_INFO_3]] -} - -twapi::proc* twapi::set_password_policy {name val {server_name ""}} { - _define_user_modals -} { - switch -exact $name { - min_passwd_len { - NetUserModalsSet $server_name 1001 [_USER_MODALS_INFO_1001 $val] - } - max_passwd_age { - if {$val eq "none"} { - set val 4294967295 - } - NetUserModalsSet $server_name 1002 [_USER_MODALS_INFO_1002 $val] - } - min_passwd_age { - NetUserModalsSet $server_name 1003 [_USER_MODALS_INFO_1003 $val] - } - force_logoff { - if {$val eq "never"} { - set val 4294967295 - } - NetUserModalsSet $server_name 1004 [_USER_MODALS_INFO_1004 $val] - } - password_hist_len { - NetUserModalsSet $server_name 1005 [_USER_MODALS_INFO_1005 $val] - } - } -} - -twapi::proc* twapi::set_lockout_policy {duration observe_window threshold {server_name ""}} { - _define_user_modals -} { - NetUserModalsSet $server_name 3 [_USER_MODALS_INFO_3 $duration $observe_window $threshold] -} +# +# Copyright (c) 2009-2015, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +package require twapi_security + +namespace eval twapi { + record USER_INFO_0 {-name} + record USER_INFO_1 [concat [USER_INFO_0] { + -password -password_age -priv -home_dir -comment -flags -script_path + }] + record USER_INFO_2 [concat [USER_INFO_1] { + -auth_flags -full_name -usr_comment -parms + -workstations -last_logon -last_logoff -acct_expires -max_storage + -units_per_week -logon_hours -bad_pw_count -num_logons + -logon_server -country_code -code_page + }] + record USER_INFO_3 [concat [USER_INFO_2] { + -user_id -primary_group_id -profile -home_dir_drive -password_expired + }] + record USER_INFO_4 [concat [USER_INFO_2] { + -sid -primary_group_id -profile -home_dir_drive -password_expired + }] + + record GROUP_INFO_0 {-name} + record GROUP_INFO_1 {-name -comment} + record GROUP_INFO_2 {-name -comment -group_id -attributes} + record GROUP_INFO_3 {-name -comment -sid -attributes} + + record NetEnumResult {moredata hresume totalentries entries} + +} + +# Add a new user account +proc twapi::new_user {username args} { + array set opts [parseargs args [list \ + system.arg \ + password.arg \ + comment.arg \ + [list priv.arg "user" [array names ::twapi::priv_level_map]] \ + home_dir.arg \ + script_path.arg \ + ] \ + -nulldefault] + + if {$opts(priv) ne "user"} { + error "Option -priv is deprecated and values other than 'user' are not allowed" + } + + # 1 -> priv level 'user'. NetUserAdd mandates this as only allowed value + NetUserAdd $opts(system) $username $opts(password) 1 \ + $opts(home_dir) $opts(comment) 0 $opts(script_path) + + + # Backward compatibility - add to 'Users' local group + # but only if -system is local + if {$opts(system) eq "" || + ([info exists ::env(COMPUTERNAME)] && + [string equal -nocase $opts(system) $::env(COMPUTERNAME)])} { + trap { + _set_user_priv_level $username $opts(priv) -system $opts(system) + } onerror {} { + # Remove the previously created user account + catch {delete_user $username -system $opts(system)} + rethrow + } + } +} + + +# Delete a user account +proc twapi::delete_user {username args} { + array set opts [parseargs args {system.arg} -nulldefault] + + # Remove the user from the LSA rights database. + _delete_rights $username $opts(system) + + NetUserDel $opts(system) $username +} + + +# Define various functions to set various user account fields +foreach twapi::_field_ { + {name 0} + {password 1003} + {home_dir 1006} + {comment 1007} + {script_path 1009} + {full_name 1011} + {country_code 1024} + {profile 1052} + {home_dir_drive 1053} +} { + proc twapi::set_user_[lindex $::twapi::_field_ 0] {username fieldval args} " + array set opts \[parseargs args { + system.arg + } -nulldefault \] + Twapi_NetUserSetInfo [lindex $::twapi::_field_ 1] \$opts(system) \$username \$fieldval" +} +unset twapi::_field_ + +# Set account expiry time +proc twapi::set_user_expiration {username time args} { + array set opts [parseargs args {system.arg} -nulldefault] + + if {![string is integer -strict $time]} { + if {[string equal $time "never"]} { + set time -1 + } else { + set time [clock scan $time] + } + } + Twapi_NetUserSetInfo 1017 $opts(system) $username $time +} + +# Unlock a user account +proc twapi::unlock_user {username args} { + # UF_LOCKOUT -> 0x10 + _change_user_info_flags $username 0x10 0 {*}$args +} + +# Enable a user account +proc twapi::enable_user {username args} { + # UF_ACCOUNTDISABLE -> 0x2 + _change_user_info_flags $username 0x2 0 {*}$args +} + +# Disable a user account +proc twapi::disable_user {username args} { + # UF_ACCOUNTDISABLE -> 0x2 + _change_user_info_flags $username 0x2 0x2 {*}$args +} + + +# Return the specified fields for a user account +proc twapi::get_user_account_info {account args} { + # Define each option, the corresponding field, and the + # information level at which it is returned + array set fields { + comment 1 + password_expired 4 + full_name 2 + parms 2 + units_per_week 2 + primary_group_id 4 + flags 1 + logon_server 2 + country_code 2 + home_dir 1 + password_age 1 + home_dir_drive 4 + num_logons 2 + acct_expires 2 + last_logon 2 + usr_comment 2 + bad_pw_count 2 + code_page 2 + logon_hours 2 + workstations 2 + last_logoff 2 + name 0 + script_path 1 + profile 4 + max_storage 2 + } + # Left out - auth_flags 2 + # Left out (always returned as NULL) - password {usri3_password 1} + # Note sid is available at level 4 as well but don't want to set + # level 4 just for that since we can get it by other means. Hence + # not listed above + + array set opts [parseargs args \ + [concat [array names fields] sid \ + internet_identity \ + status type password_attrs \ + [list local_groups global_groups system.arg all]] \ + -nulldefault] + + if {$opts(all)} { + set level 4 + set opts(local_groups) 1 + set opts(global_groups) 1 + } else { + # Based on specified fields, figure out what level info to ask for + set level -1 + foreach {opt optval} [array get opts] { + if {[info exists fields($opt)] && + $optval && + $fields($opt) > $level + } { + set level $fields($opt) + } + } + if {$opts(status) || $opts(type) || $opts(password_attrs)} { + # These fields are based on the flags field + if {$level < 1} { + set level 1 + } + } + } + + array set result [list ] + + if {$level > -1} { + set rawdata [NetUserGetInfo $opts(system) $account $level] + array set data [USER_INFO_$level $rawdata] + + # Extract the requested data + foreach opt [array names fields] { + if {$opts(all) || $opts($opt)} { + set result(-$opt) $data(-$opt) + } + } + if {$level == 4 && ($opts(all) || $opts(sid))} { + set result(-sid) $data(-sid) + } + + # Map internal values to more friendly formats + if {$opts(all) || $opts(status) || $opts(type) || $opts(password_attrs)} { + array set result [_map_userinfo_flags $data(-flags)] + if {! $opts(all)} { + if {! $opts(status)} {unset result(-status)} + if {! $opts(type)} {unset result(-type)} + if {! $opts(password_attrs)} {unset result(-password_attrs)} + } + } + + if {[info exists result(-logon_hours)]} { + binary scan $result(-logon_hours) b* result(-logon_hours) + } + + foreach time_field {-acct_expires -last_logon -last_logoff} { + if {[info exists result($time_field)]} { + if {$result($time_field) == -1 || $result($time_field) == 4294967295} { + set result($time_field) "never" + } elseif {$result($time_field) == 0} { + set result($time_field) "unknown" + } + } + } + } + + if {$opts(all) || $opts(internet_identity)} { + set result(-internet_identity) {} + if {[min_os_version 6 2]} { + set inet_ident [NetUserGetInfo $opts(system) $account 24] + if {[llength $inet_ident]} { + set result(-internet_identity) [twine { + internet_provider_name internet_principal_name sid + } [lrange $inet_ident 1 end]] + } + } + } + + # The Net* calls always return structures as lists even when the struct + # contains only one field so we need to lpick to extract the field + + if {$opts(local_groups)} { + set result(-local_groups) [lpick [NetEnumResult entries [NetUserGetLocalGroups $opts(system) $account 0 0]] 0] + } + + if {$opts(global_groups)} { + set result(-global_groups) [lpick [NetEnumResult entries [NetUserGetGroups $opts(system) $account 0]] 0] + } + + if {$opts(sid) && ! [info exists result(-sid)]} { + set result(-sid) [lookup_account_name $account -system $opts(system)] + } + + return [array get result] +} + +proc twapi::get_user_global_groups {account args} { + parseargs args { + system.arg + denyonly + all + } -nulldefault -maxleftover 0 -setvars + + set groups {} + foreach elem [NetEnumResult entries [NetUserGetGroups $system [map_account_to_name $account -system $system] 1]] { + # 0x10 -> SE_GROUP_USE_FOR_DENY_ONLY + set marked_denyonly [expr {[lindex $elem 1] & 0x10}] + if {$all || ($denyonly && $marked_denyonly) || !($denyonly || $marked_denyonly)} { + lappend groups [lindex $elem 0] + } + } + return $groups +} + +proc twapi::get_user_local_groups {account args} { + parseargs args { + system.arg + {recurse.bool 0} + } -nulldefault -maxleftover 0 -setvars + + # The Net* calls always return structures as lists even when the struct + # contains only one field so we need to lpick to extract the field + return [lpick [NetEnumResult entries [NetUserGetLocalGroups $system [map_account_to_name $account -system $system] 0 $recurse]] 0] +} + +proc twapi::get_user_local_groups_recursive {account args} { + return [get_user_local_groups $account {*}$args -recurse 1] +} + + +# Set the specified fields for a user account +proc twapi::set_user_account_info {account args} { + + # Define each option, the corresponding field, and the + # information level at which it is returned + array set opts [parseargs args { + {system.arg ""} + comment.arg + full_name.arg + country_code.arg + home_dir.arg + home_dir.arg + acct_expires.arg + name.arg + script_path.arg + profile.arg + }] + + # TBD - rewrite this to be atomic + + if {[info exists opts(comment)]} { + set_user_comment $account $opts(comment) -system $opts(system) + } + + if {[info exists opts(full_name)]} { + set_user_full_name $account $opts(full_name) -system $opts(system) + } + + if {[info exists opts(country_code)]} { + set_user_country_code $account $opts(country_code) -system $opts(system) + } + + if {[info exists opts(home_dir)]} { + set_user_home_dir $account $opts(home_dir) -system $opts(system) + } + + if {[info exists opts(home_dir_drive)]} { + set_user_home_dir_drive $account $opts(home_dir_drive) -system $opts(system) + } + + if {[info exists opts(acct_expires)]} { + set_user_expiration $account $opts(acct_expires) -system $opts(system) + } + + if {[info exists opts(name)]} { + set_user_name $account $opts(name) -system $opts(system) + } + + if {[info exists opts(script_path)]} { + set_user_script_path $account $opts(script_path) -system $opts(system) + } + + if {[info exists opts(profile)]} { + set_user_profile $account $opts(profile) -system $opts(system) + } +} + + +proc twapi::get_global_group_info {grpname args} { + array set opts [parseargs args { + {system.arg ""} + comment + name + members + sid + attributes + all + } -maxleftover 0] + + set result {} + if {[expr {$opts(comment) || $opts(name) || $opts(sid) || $opts(attributes) || $opts(all)}]} { + # 3 -> GROUP_INFO level 3 + lassign [NetGroupGetInfo $opts(system) $grpname 3] name comment sid attributes + if {$opts(all) || $opts(sid)} { + lappend result -sid $sid + } + if {$opts(all) || $opts(name)} { + lappend result -name $name + } + if {$opts(all) || $opts(comment)} { + lappend result -comment $comment + } + if {$opts(all) || $opts(attributes)} { + lappend result -attributes [map_token_group_attr $attributes] + } + } + + if {$opts(all) || $opts(members)} { + lappend result -members [get_global_group_members $grpname -system $opts(system)] + } + + return $result +} + +# Get info about a local or global group +proc twapi::get_local_group_info {name args} { + array set opts [parseargs args { + {system.arg ""} + comment + name + members + sid + all + } -maxleftover 0] + + set result [list ] + if {$opts(all) || $opts(sid)} { + lappend result -sid [lookup_account_name $name -system $opts(system)] + } + if {$opts(all) || $opts(comment) || $opts(name)} { + lassign [NetLocalGroupGetInfo $opts(system) $name 1] name comment + if {$opts(all) || $opts(name)} { + lappend result -name $name + } + if {$opts(all) || $opts(comment)} { + lappend result -comment $comment + } + } + if {$opts(all) || $opts(members)} { + lappend result -members [get_local_group_members $name -system $opts(system)] + } + return $result +} + +# Get list of users on a system +proc twapi::get_users {args} { + parseargs args { + level.int + } -setvars -ignoreunknown + + # TBD -allow user to specify filter + lappend args -filter 0 + if {[info exists level]} { + lappend args -level $level -fields [USER_INFO_$level] + } + return [_net_enum_helper NetUserEnum $args] +} + +proc twapi::get_global_groups {args} { + parseargs args { + level.int + } -setvars -ignoreunknown + + # TBD - level 3 returns an ERROR_INVALID_LEVEL even though + # MSDN says its valid for NetGroupEnum + + if {[info exists level]} { + lappend args -level $level -fields [GROUP_INFO_$level] + } + return [_net_enum_helper NetGroupEnum $args] +} + +proc twapi::get_local_groups {args} { + parseargs args { + level.int + } -setvars -ignoreunknown + + if {[info exists level]} { + lappend args -level $level -fields [dict get {0 {-name} 1 {-name -comment}} $level] + } + return [_net_enum_helper NetLocalGroupEnum $args] +} + +# Create a new global group +proc twapi::new_global_group {grpname args} { + array set opts [parseargs args { + system.arg + comment.arg + } -nulldefault] + + NetGroupAdd $opts(system) $grpname $opts(comment) +} + +# Create a new local group +proc twapi::new_local_group {grpname args} { + array set opts [parseargs args { + system.arg + comment.arg + } -nulldefault] + + NetLocalGroupAdd $opts(system) $grpname $opts(comment) +} + + +# Delete a global group +proc twapi::delete_global_group {grpname args} { + array set opts [parseargs args {system.arg} -nulldefault] + + # Remove the group from the LSA rights database. + _delete_rights $grpname $opts(system) + + NetGroupDel $opts(system) $grpname +} + +# Delete a local group +proc twapi::delete_local_group {grpname args} { + array set opts [parseargs args {system.arg} -nulldefault] + + # Remove the group from the LSA rights database. + _delete_rights $grpname $opts(system) + + NetLocalGroupDel $opts(system) $grpname +} + + +# Enumerate members of a global group +proc twapi::get_global_group_members {grpname args} { + parseargs args { + level.int + } -setvars -ignoreunknown + + if {[info exists level]} { + lappend args -level $level -fields [dict! {0 {-name} 1 {-name -attributes}} $level] + } + + lappend args -preargs [list $grpname] -namelevel 1 + return [_net_enum_helper NetGroupGetUsers $args] +} + +# Enumerate members of a local group +proc twapi::get_local_group_members {grpname args} { + parseargs args { + level.int + } -setvars -ignoreunknown + + if {[info exists level]} { + lappend args -level $level -fields [dict! {0 {-sid} 1 {-sid -sidusage -name} 2 {-sid -sidusage -domainandname} 3 {-domainandname}} $level] + } + + lappend args -preargs [list $grpname] -namelevel 1 -namefield 2 + return [_net_enum_helper NetLocalGroupGetMembers $args] +} + +# Add a user to a global group +proc twapi::add_user_to_global_group {grpname username args} { + array set opts [parseargs args {system.arg} -nulldefault] + + # No error if already member of the group + trap { + NetGroupAddUser $opts(system) $grpname $username + } onerror {TWAPI_WIN32 1320} { + # Ignore + } +} + + +# Remove a user from a global group +proc twapi::remove_user_from_global_group {grpname username args} { + array set opts [parseargs args {system.arg} -nulldefault] + + trap { + NetGroupDelUser $opts(system) $grpname $username + } onerror {TWAPI_WIN32 1321} { + # Was not in group - ignore + } +} + + +# Add a user to a local group +proc twapi::add_member_to_local_group {grpname username args} { + array set opts [parseargs args { + system.arg + {type.arg name} + } -nulldefault] + + # No error if already member of the group + trap { + Twapi_NetLocalGroupMembers 0 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] [list $username] + } onerror {TWAPI_WIN32 1378} { + # Ignore + } +} + +proc twapi::add_members_to_local_group {grpname accts args} { + array set opts [parseargs args { + system.arg + {type.arg name} + } -nulldefault] + + Twapi_NetLocalGroupMembers 0 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] $accts +} + + +# Remove a user from a local group +proc twapi::remove_member_from_local_group {grpname username args} { + array set opts [parseargs args { + system.arg + {type.arg name} + } -nulldefault] + + trap { + Twapi_NetLocalGroupMembers 1 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] [list $username] + } onerror {TWAPI_WIN32 1377} { + # Was not in group - ignore + } +} + +proc twapi::remove_members_from_local_group {grpname accts args} { + array set opts [parseargs args { + system.arg + {type.arg name} + } -nulldefault] + + Twapi_NetLocalGroupMembers 1 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] $accts +} + + +# Get rights for an account +proc twapi::get_account_rights {account args} { + array set opts [parseargs args { + {system.arg ""} + } -maxleftover 0] + + set sid [map_account_to_sid $account -system $opts(system)] + + trap { + set lsah [get_lsa_policy_handle -system $opts(system) -access policy_lookup_names] + return [Twapi_LsaEnumerateAccountRights $lsah $sid] + } onerror {TWAPI_WIN32 2} { + # No specific rights for this account + return [list ] + } finally { + if {[info exists lsah]} { + close_lsa_policy_handle $lsah + } + } +} + +# Get accounts having a specific right +proc twapi::find_accounts_with_right {right args} { + array set opts [parseargs args { + {system.arg ""} + name + } -maxleftover 0] + + trap { + set lsah [get_lsa_policy_handle \ + -system $opts(system) \ + -access { + policy_lookup_names + policy_view_local_information + }] + set accounts [list ] + foreach sid [Twapi_LsaEnumerateAccountsWithUserRight $lsah $right] { + if {$opts(name)} { + if {[catch {lappend accounts [lookup_account_sid $sid -system $opts(system)]}]} { + # No mapping for SID - can happen if account has been + # deleted but LSA policy not updated accordingly + lappend accounts $sid + } + } else { + lappend accounts $sid + } + } + return $accounts + } onerror {TWAPI_WIN32 259} { + # No accounts have this right + return [list ] + } finally { + if {[info exists lsah]} { + close_lsa_policy_handle $lsah + } + } + +} + +# Add/remove rights to an account +proc twapi::_modify_account_rights {operation account rights args} { + set switches { + system.arg + handle.arg + } + + switch -exact -- $operation { + add { + # Nothing to do + } + remove { + lappend switches all + } + default { + error "Invalid operation '$operation' specified" + } + } + + array set opts [parseargs args $switches -maxleftover 0] + + if {[info exists opts(system)] && [info exists opts(handle)]} { + error "Options -system and -handle may not be specified together" + } + + if {[info exists opts(handle)]} { + set lsah $opts(handle) + set sid $account + } else { + if {![info exists opts(system)]} { + set opts(system) "" + } + + set sid [map_account_to_sid $account -system $opts(system)] + # We need to open a policy handle ourselves. First try to open + # with max privileges in case the account needs to be created + # and then retry with lower privileges if that fails + catch { + set lsah [get_lsa_policy_handle \ + -system $opts(system) \ + -access { + policy_lookup_names + policy_create_account + }] + } + if {![info exists lsah]} { + set lsah [get_lsa_policy_handle \ + -system $opts(system) \ + -access policy_lookup_names] + } + } + + trap { + if {$operation == "add"} { + LsaAddAccountRights $lsah $sid $rights + } else { + LsaRemoveAccountRights $lsah $sid $opts(all) $rights + } + } finally { + # Close the handle if we opened it + if {! [info exists opts(handle)]} { + close_lsa_policy_handle $lsah + } + } +} + +interp alias {} twapi::add_account_rights {} twapi::_modify_account_rights add +interp alias {} twapi::remove_account_rights {} twapi::_modify_account_rights remove + +# Return list of logon sesionss +proc twapi::find_logon_sessions {args} { + array set opts [parseargs args { + user.arg + type.arg + tssession.arg + } -maxleftover 0] + + set luids [LsaEnumerateLogonSessions] + if {! ([info exists opts(user)] || [info exists opts(type)] || + [info exists opts(tssession)])} { + return $luids + } + + + # Need to get the data for each session to see if it matches + set result [list ] + if {[info exists opts(user)]} { + set sid [map_account_to_sid $opts(user)] + } + if {[info exists opts(type)]} { + set logontypes [list ] + foreach logontype $opts(type) { + lappend logontypes [_logon_session_type_code $logontype] + } + } + + foreach luid $luids { + trap { + unset -nocomplain session + array set session [LsaGetLogonSessionData $luid] + + # For the local system account, no data is returned on some + # platforms + if {[array size session] == 0} { + set session(Sid) S-1-5-18; # SYSTEM + set session(Session) 0 + set session(LogonType) 0 + } + if {[info exists opts(user)] && $session(Sid) ne $sid} { + continue; # User id does not match + } + + if {[info exists opts(type)] && [lsearch -exact $logontypes $session(LogonType)] < 0} { + continue; # Type does not match + } + + if {[info exists opts(tssession)] && $session(Session) != $opts(tssession)} { + continue; # Term server session does not match + } + + lappend result $luid + + } onerror {TWAPI_WIN32 1312} { + # Session no longer exists. Just skip + continue + } + } + + return $result +} + + +# Return data for a logon session +proc twapi::get_logon_session_info {luid args} { + array set opts [parseargs args { + all + authpackage + dnsdomain + logondomain + logonid + logonserver + logontime + type + usersid + user + tssession + userprincipal + } -maxleftover 0] + + array set session [LsaGetLogonSessionData $luid] + + # Some fields may be missing on Win2K + foreach fld {LogonServer DnsDomainName Upn} { + if {![info exists session($fld)]} { + set session($fld) "" + } + } + + array set result [list ] + foreach {opt index} { + authpackage AuthenticationPackage + dnsdomain DnsDomainName + logondomain LogonDomain + logonid LogonId + logonserver LogonServer + logontime LogonTime + type LogonType + usersid Sid + user UserName + tssession Session + userprincipal Upn + } { + if {$opts(all) || $opts($opt)} { + set result(-$opt) $session($index) + } + } + + if {[info exists result(-type)]} { + set result(-type) [_logon_session_type_symbol $result(-type)] + } + + return [array get result] +} + + + + +# Set/reset the given bits in the usri3_flags field for a user account +# mask indicates the mask of bits to set. values indicates the values +# of those bits +proc twapi::_change_user_info_flags {username mask values args} { + array set opts [parseargs args { + system.arg + } -nulldefault -maxleftover 0] + + # Get current flags + set flags [USER_INFO_1 -flags [NetUserGetInfo $opts(system) $username 1]] + + # Turn off mask bits and write flags back + set flags [expr {$flags & (~ $mask)}] + # Set the specified bits + set flags [expr {$flags | ($values & $mask)}] + + # Write new flags back + Twapi_NetUserSetInfo 1008 $opts(system) $username $flags +} + +# Returns the logon session type value for a symbol +twapi::proc* twapi::_logon_session_type_code {type} { + variable _logon_session_type_map + # Variable that maps logon session type codes to integer values + # Position of each symbol gives its corresponding type value + # See ntsecapi.h for definitions + set _logon_session_type_map { + 0 + 1 + interactive + network + batch + service + proxy + unlockworkstation + networkclear + newcredentials + remoteinteractive + cachedinteractive + cachedremoteinteractive + cachedunlockworkstation + } +} { + variable _logon_session_type_map + + # Type may be an integer or a token + set code [lsearch -exact $_logon_session_type_map $type] + if {$code >= 0} { + return $code + } + + if {![string is integer -strict $type]} { + badargs! "Invalid logon session type '$type' specified" 3 + } + return $type +} + +# Returns the logon session type symbol for an integer value +proc twapi::_logon_session_type_symbol {code} { + variable _logon_session_type_map + _logon_session_type_code interactive; # Just to init _logon_session_type_map + set symbol [lindex $_logon_session_type_map $code] + if {$symbol eq ""} { + return $code + } else { + return $symbol + } +} + +proc twapi::_set_user_priv_level {username priv_level args} { + + array set opts [parseargs args {system.arg} -nulldefault] + + if {0} { + # FOr some reason NetUserSetInfo cannot change priv level + # Tried it separately with a simple C program. So this code + # is commented out and we use group membership to achieve + # the desired result + # Note: - latest MSDN confirms above + if {![info exists twapi::priv_level_map($priv_level)]} { + error "Invalid privilege level value '$priv_level' specified. Must be one of [join [array names twapi::priv_level_map] ,]" + } + set priv $twapi::priv_level_map($priv_level) + + Twapi_NetUserSetInfo_priv $opts(system) $username $priv + } else { + # Don't hardcode group names - reverse map SID's instead for + # non-English systems. Also note that since + # we might be lowering privilege level, we have to also + # remove from higher privileged groups + + switch -exact -- $priv_level { + guest { + # administrators users + set outgroups {S-1-5-32-544 S-1-5-32-545} + # guests + set ingroup S-1-5-32-546 + } + user { + # administrators + set outgroups {S-1-5-32-544} + # users + set ingroup S-1-5-32-545 + } + admin { + set outgroups {} + set ingroup S-1-5-32-544 + } + default {error "Invalid privilege level '$priv_level'. Must be one of 'guest', 'user' or 'admin'"} + } + # Remove from higher priv groups + foreach outgroup $outgroups { + # Get the potentially localized name of the group + set group [lookup_account_sid $outgroup -system $opts(system)] + # Catch since may not be member of that group + catch {remove_member_from_local_group $group $username -system $opts(system)} + } + + # Get the potentially localized name of the group to be added + set group [lookup_account_sid $ingroup -system $opts(system)] + add_member_to_local_group $group $username -system $opts(system) + } +} + +proc twapi::_map_userinfo_flags {flags} { + # UF_LOCKOUT -> 0x10, UF_ACCOUNTDISABLE -> 0x2 + if {$flags & 0x2} { + set status disabled + } elseif {$flags & 0x10} { + set status locked + } else { + set status enabled + } + + #define UF_TEMP_DUPLICATE_ACCOUNT 0x0100 + #define UF_NORMAL_ACCOUNT 0x0200 + #define UF_INTERDOMAIN_TRUST_ACCOUNT 0x0800 + #define UF_WORKSTATION_TRUST_ACCOUNT 0x1000 + #define UF_SERVER_TRUST_ACCOUNT 0x2000 + if {$flags & 0x0200} { + set type normal + } elseif {$flags & 0x0100} { + set type duplicate + } elseif {$flags & 0x0800} { + set type interdomain_trust + } elseif {$flags & 0x1000} { + set type workstation_trust + } elseif {$flags & 0x2000} { + set type server_trust + } else { + set type unknown + } + + set pw {} + #define UF_PASSWD_NOTREQD 0x0020 + if {$flags & 0x0020} { + lappend pw not_required + } + #define UF_PASSWD_CANT_CHANGE 0x0040 + if {$flags & 0x0040} { + lappend pw cannot_change + } + #define UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x0080 + if {$flags & 0x0080} { + lappend pw encrypted_text_allowed + } + #define UF_DONT_EXPIRE_PASSWD 0x10000 + if {$flags & 0x10000} { + lappend pw no_expiry + } + #define UF_SMARTCARD_REQUIRED 0x40000 + if {$flags & 0x40000} { + lappend pw smartcard_required + } + #define UF_PASSWORD_EXPIRED 0x800000 + if {$flags & 0x800000} { + lappend pw expired + } + + return [list -status $status -type $type -password_attrs $pw] +} + +twapi::proc* twapi::_define_user_modals {} { + struct _USER_MODALS_INFO_0 { + DWORD min_passwd_len; + DWORD max_passwd_age; + DWORD min_passwd_age; + DWORD force_logoff; + DWORD password_hist_len; + } + struct _USER_MODALS_INFO_1 { + DWORD role; + LPWSTR primary; + } + struct _USER_MODALS_INFO_2 { + LPWSTR domain_name; + PSID domain_id; + } + struct _USER_MODALS_INFO_3 { + DWORD lockout_duration; + DWORD lockout_observation_window; + DWORD lockout_threshold; + } + struct _USER_MODALS_INFO_1001 { + DWORD min_passwd_len; + } + struct _USER_MODALS_INFO_1002 { + DWORD max_passwd_age; + } + struct _USER_MODALS_INFO_1003 { + DWORD min_passwd_age; + } + struct _USER_MODALS_INFO_1004 { + DWORD force_logoff; + } + struct _USER_MODALS_INFO_1005 { + DWORD password_hist_len; + } + struct _USER_MODALS_INFO_1006 { + DWORD role; + } + struct _USER_MODALS_INFO_1007 { + LPWSTR primary; + } +} { +} + +twapi::proc* twapi::get_password_policy {{server_name ""}} { + _define_user_modals +} { + set result [NetUserModalsGet $server_name 0 [_USER_MODALS_INFO_0]] + dict with result { + if {$force_logoff == 4294967295 || $force_logoff == -1} { + set force_logoff never + } + if {$max_passwd_age == 4294967295 || $max_passwd_age == -1} { + set max_passwd_age none + } + } + return $result +} + +# TBD - doc & test +twapi::proc* twapi::get_system_role {{server_name ""}} { + _define_user_modals +} { + set result [NetUserModalsGet $server_name 1 [_USER_MODALS_INFO_1]] + dict set result role [dict* { + 0 standalone 1 member 2 backup 3 primary + } [dict get $result role]] + return $result +} + +# TBD - doc & test +twapi::proc* twapi::get_system_domain {{server_name ""}} { + _define_user_modals +} { + return [NetUserModalsGet $server_name 2 [_USER_MODALS_INFO_2]] +} + +twapi::proc* twapi::get_lockout_policy {{server_name ""}} { + _define_user_modals +} { + return [NetUserModalsGet $server_name 3 [_USER_MODALS_INFO_3]] +} + +twapi::proc* twapi::set_password_policy {name val {server_name ""}} { + _define_user_modals +} { + switch -exact $name { + min_passwd_len { + NetUserModalsSet $server_name 1001 [_USER_MODALS_INFO_1001 $val] + } + max_passwd_age { + if {$val eq "none"} { + set val 4294967295 + } + NetUserModalsSet $server_name 1002 [_USER_MODALS_INFO_1002 $val] + } + min_passwd_age { + NetUserModalsSet $server_name 1003 [_USER_MODALS_INFO_1003 $val] + } + force_logoff { + if {$val eq "never"} { + set val 4294967295 + } + NetUserModalsSet $server_name 1004 [_USER_MODALS_INFO_1004 $val] + } + password_hist_len { + NetUserModalsSet $server_name 1005 [_USER_MODALS_INFO_1005 $val] + } + } +} + +twapi::proc* twapi::set_lockout_policy {duration observe_window threshold {server_name ""}} { + _define_user_modals +} { + NetUserModalsSet $server_name 3 [_USER_MODALS_INFO_3 $duration $observe_window $threshold] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/adsi.tcl b/src/vendorlib_tcl8/twapi-5.0b1/adsi.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/adsi.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/adsi.tcl index 77dd5122..ecdbcbdf 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/adsi.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/adsi.tcl @@ -1,28 +1,28 @@ -# -# Copyright (c) 2010-2012, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# ADSI routines - -# TBD - document -proc twapi::adsi_translate_name {name to {from 0}} { - set map { - unknown 0 fqdn 1 samcompatible 2 display 3 uniqueid 6 - canonical 7 userprincipal 8 canonicalex 9 serviceprincipal 10 - dnsdomain 12 - } - if {! [string is integer -strict $to]} { - set to [dict get $map $to] - if {$to == 0} { - error "'unknown' is not a valid target format." - } - } - - if {! [string is integer -strict $from]} { - set from [dict get $map $from] - } - - return [TranslateName $name $from $to] +# +# Copyright (c) 2010-2012, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# ADSI routines + +# TBD - document +proc twapi::adsi_translate_name {name to {from 0}} { + set map { + unknown 0 fqdn 1 samcompatible 2 display 3 uniqueid 6 + canonical 7 userprincipal 8 canonicalex 9 serviceprincipal 10 + dnsdomain 12 + } + if {! [string is integer -strict $to]} { + set to [dict get $map $to] + if {$to == 0} { + error "'unknown' is not a valid target format." + } + } + + if {! [string is integer -strict $from]} { + set from [dict get $map $from] + } + + return [TranslateName $name $from $to] } \ No newline at end of file diff --git a/src/vendorlib_tcl8/twapi4.7.2/apputil.tcl b/src/vendorlib_tcl8/twapi-5.0b1/apputil.tcl similarity index 91% rename from src/vendorlib_tcl8/twapi4.7.2/apputil.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/apputil.tcl index db008b66..93bc8720 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/apputil.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/apputil.tcl @@ -1,114 +1,114 @@ -# -# Copyright (c) 2003-2012, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi {} - -# Get the command line -proc twapi::get_command_line {} { - return [GetCommandLineW] -} - -# Parse the command line -proc twapi::get_command_line_args {cmdline} { - # Special check for empty line. CommandLinetoArgv returns process - # exe name in this case. - if {[string length $cmdline] == 0} { - return [list ] - } - return [CommandLineToArgv $cmdline] -} - -# Read an ini file int -proc twapi::read_inifile_key {section key args} { - array set opts [parseargs args { - {default.arg ""} - inifile.arg - } -maxleftover 0] - - if {[info exists opts(inifile)]} { - set values [read_inifile_section $section -inifile $opts(inifile)] - } else { - set values [read_inifile_section $section] - } - - # Cannot use kl_get or arrays here because we want case insensitive compare - foreach {k val} $values { - if {[string equal -nocase $key $k]} { - return $val - } - } - return $opts(default) -} - -# Write an ini file string -proc twapi::write_inifile_key {section key value args} { - array set opts [parseargs args { - inifile.arg - } -maxleftover 0] - - if {[info exists opts(inifile)]} { - WritePrivateProfileString $section $key $value $opts(inifile) - } else { - WriteProfileString $section $key $value - } -} - -# Delete an ini file string -proc twapi::delete_inifile_key {section key args} { - array set opts [parseargs args { - inifile.arg - } -maxleftover 0] - - if {[info exists opts(inifile)]} { - WritePrivateProfileString $section $key $twapi::nullptr $opts(inifile) - } else { - WriteProfileString $section $key $twapi::nullptr - } -} - -# Get names of the sections in an inifile -proc twapi::read_inifile_section_names {args} { - array set opts [parseargs args { - inifile.arg - } -nulldefault -maxleftover 0] - - return [GetPrivateProfileSectionNames $opts(inifile)] -} - -# Get keys and values in a section in an inifile -proc twapi::read_inifile_section {section args} { - array set opts [parseargs args { - inifile.arg - } -nulldefault -maxleftover 0] - - set result [list ] - foreach line [GetPrivateProfileSection $section $opts(inifile)] { - set pos [string first "=" $line] - if {$pos >= 0} { - lappend result [string range $line 0 [expr {$pos-1}]] [string range $line [incr pos] end] - } - } - return $result -} - - -# Delete an ini file section -proc twapi::delete_inifile_section {section args} { - variable nullptr - - array set opts [parseargs args { - inifile.arg - }] - - if {[info exists opts(inifile)]} { - WritePrivateProfileString $section $nullptr $nullptr $opts(inifile) - } else { - WriteProfileString $section $nullptr $nullptr - } -} - - - +# +# Copyright (c) 2003-2012, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi {} + +# Get the command line +proc twapi::get_command_line {} { + return [GetCommandLineW] +} + +# Parse the command line +proc twapi::get_command_line_args {cmdline} { + # Special check for empty line. CommandLinetoArgv returns process + # exe name in this case. + if {[string length $cmdline] == 0} { + return [list ] + } + return [CommandLineToArgv $cmdline] +} + +# Read an ini file int +proc twapi::read_inifile_key {section key args} { + array set opts [parseargs args { + {default.arg ""} + inifile.arg + } -maxleftover 0] + + if {[info exists opts(inifile)]} { + set values [read_inifile_section $section -inifile $opts(inifile)] + } else { + set values [read_inifile_section $section] + } + + # Cannot use kl_get or arrays here because we want case insensitive compare + foreach {k val} $values { + if {[string equal -nocase $key $k]} { + return $val + } + } + return $opts(default) +} + +# Write an ini file string +proc twapi::write_inifile_key {section key value args} { + array set opts [parseargs args { + inifile.arg + } -maxleftover 0] + + if {[info exists opts(inifile)]} { + WritePrivateProfileString $section $key $value $opts(inifile) + } else { + WriteProfileString $section $key $value + } +} + +# Delete an ini file string +proc twapi::delete_inifile_key {section key args} { + array set opts [parseargs args { + inifile.arg + } -maxleftover 0] + + if {[info exists opts(inifile)]} { + WritePrivateProfileString $section $key $::twapi::nullptr $opts(inifile) + } else { + WriteProfileString $section $key $::twapi::nullptr + } +} + +# Get names of the sections in an inifile +proc twapi::read_inifile_section_names {args} { + array set opts [parseargs args { + inifile.arg + } -nulldefault -maxleftover 0] + + return [GetPrivateProfileSectionNames $opts(inifile)] +} + +# Get keys and values in a section in an inifile +proc twapi::read_inifile_section {section args} { + array set opts [parseargs args { + inifile.arg + } -nulldefault -maxleftover 0] + + set result [list ] + foreach line [GetPrivateProfileSection $section $opts(inifile)] { + set pos [string first "=" $line] + if {$pos >= 0} { + lappend result [string range $line 0 [expr {$pos-1}]] [string range $line [incr pos] end] + } + } + return $result +} + + +# Delete an ini file section +proc twapi::delete_inifile_section {section args} { + variable nullptr + + array set opts [parseargs args { + inifile.arg + }] + + if {[info exists opts(inifile)]} { + WritePrivateProfileString $section $nullptr $nullptr $opts(inifile) + } else { + WriteProfileString $section $nullptr $nullptr + } +} + + + diff --git a/src/vendorlib_tcl8/twapi4.7.2/base.tcl b/src/vendorlib_tcl8/twapi-5.0b1/base.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/base.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/base.tcl index b227d2c0..6aeaba2a 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/base.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/base.tcl @@ -1,1873 +1,1876 @@ -# -# Copyright (c) 2012-2014, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Commands in twapi_base module - -namespace eval twapi { - # Map of Sid integer type to Sid type name - array set sid_type_names { - 1 user - 2 group - 3 domain - 4 alias - 5 wellknowngroup - 6 deletedaccount - 7 invalid - 8 unknown - 9 computer - 10 label - 11 logonsession - } - - # Cache mapping account names to SIDs. Dict keyed by system and name - variable _name_to_sid_cache {} - - # Cache mapping SIDs to account names. Dict keyed by system and SID - variable _sid_to_name_cache {} - - # Dictionary of FFI libraries to handles and back - variable _ffi_paths {} - variable _ffi_handles {} -} - - - -# Return major minor servicepack as a quad list -proc twapi::get_os_version {} { - array set verinfo [GetVersionEx] - return [list $verinfo(dwMajorVersion) $verinfo(dwMinorVersion) \ - $verinfo(wServicePackMajor) $verinfo(wServicePackMinor)] -} - -# Returns true if the OS version is at least $major.$minor.$sp -proc twapi::min_os_version {major {minor 0} {spmajor 0} {spminor 0}} { - lassign [twapi::get_os_version] osmajor osminor osspmajor osspminor - - if {$osmajor > $major} {return 1} - if {$osmajor < $major} {return 0} - if {$osminor > $minor} {return 1} - if {$osminor < $minor} {return 0} - if {$osspmajor > $spmajor} {return 1} - if {$osspmajor < $spmajor} {return 0} - if {$osspminor > $spminor} {return 1} - if {$osspminor < $spminor} {return 0} - - # Same version, ok - return 1 -} - -# Convert a LARGE_INTEGER time value (100ns since 1601) to a formatted date -# time -interp alias {} twapi::large_system_time_to_secs {} twapi::large_system_time_to_secs_since_1970 -proc twapi::large_system_time_to_secs_since_1970 {ns100 {fraction false}} { - # No. 100ns units between 1601 to 1970 = 116444736000000000 - set ns100_since_1970 [expr {$ns100-116444736000000000}] - - set secs_since_1970 [expr {$ns100_since_1970/10000000}] - if {$fraction} { - append secs_since_1970 .[string range $ns100 end-6 end] - } - return $secs_since_1970 -} - -proc twapi::secs_since_1970_to_large_system_time {secs} { - # No. 100ns units between 1601 to 1970 = 116444736000000000 - return [expr {($secs * 10000000) + 116444736000000000}] -} - -# Map a Windows error code to a string -proc twapi::map_windows_error {code} { - # Trim trailing CR/LF - return [string trimright [twapi::Twapi_MapWindowsErrorToString $code] "\r\n"] -} - -# Load given library -proc twapi::load_library {path args} { - array set opts [parseargs args { - dontresolverefs - datafile - alteredpath - }] - - set flags 0 - if {$opts(dontresolverefs)} { - setbits flags 1; # DONT_RESOLVE_DLL_REFERENCES - } - if {$opts(datafile)} { - setbits flags 2; # LOAD_LIBRARY_AS_DATAFILE - } - if {$opts(alteredpath)} { - setbits flags 8; # LOAD_WITH_ALTERED_SEARCH_PATH - } - - # LoadLibrary always wants backslashes - set path [file nativename $path] - return [LoadLibraryEx $path $flags] -} - -# Free library opened with load_library -proc twapi::free_library {libh} { - FreeLibrary $libh -} - -# Format message string - will raise exception if insufficient number -# of arguments -proc twapi::_unsafe_format_message {args} { - array set opts [parseargs args { - module.arg - fmtstring.arg - messageid.arg - langid.arg - params.arg - includesystem - ignoreinserts - width.int - } -nulldefault -maxleftover 0] - - set flags 0 - - if {$opts(module) == ""} { - if {$opts(fmtstring) == ""} { - # If neither -module nor -fmtstring specified, message is formatted - # from the system - set opts(module) NULL - setbits flags 0x1000; # FORMAT_MESSAGE_FROM_SYSTEM - } else { - setbits flags 0x400; # FORMAT_MESSAGE_FROM_STRING - if {$opts(includesystem) || $opts(messageid) != "" || $opts(langid) != ""} { - error "Options -includesystem, -messageid and -langid cannot be used with -fmtstring" - } - } - } else { - if {$opts(fmtstring) != ""} { - error "Options -fmtstring and -module cannot be used together" - } - setbits flags 0x800; # FORMAT_MESSAGE_FROM_HMODULE - if {$opts(includesystem)} { - # Also include system in search - setbits flags 0x1000; # FORMAT_MESSAGE_FROM_SYSTEM - } - } - - if {$opts(ignoreinserts)} { - setbits flags 0x200; # FORMAT_MESSAGE_IGNORE_INSERTS - } - - if {$opts(width) > 254} { - error "Invalid value for option -width. Must be -1, 0, or a positive integer less than 255" - } - if {$opts(width) < 0} { - # Negative width means no width restrictions - set opts(width) 255; # 255 -> no restrictions - } - incr flags $opts(width); # Width goes in low byte of flags - - if {$opts(fmtstring) != ""} { - return [FormatMessageFromString $flags $opts(fmtstring) $opts(params)] - } else { - if {![string is integer -strict $opts(messageid)]} { - error "Unspecified or invalid value for -messageid option. Must be an integer value" - } - if {$opts(langid) == ""} { set opts(langid) 0 } - if {![string is integer -strict $opts(langid)]} { - error "Unspecfied or invalid value for -langid option. Must be an integer value" - } - - # Check if $opts(module) is a file or module handle (pointer) - if {[pointer? $opts(module)]} { - return [FormatMessageFromModule $flags $opts(module) \ - $opts(messageid) $opts(langid) $opts(params)] - } else { - set hmod [load_library $opts(module) -datafile] - trap { - set message [FormatMessageFromModule $flags $hmod \ - $opts(messageid) $opts(langid) $opts(params)] - } finally { - free_library $hmod - } - return $message - } - } -} - -# Format message string -proc twapi::format_message {args} { - array set opts [parseargs args { - params.arg - fmtstring.arg - width.int - ignoreinserts - } -ignoreunknown] - - # TBD - document - if no params specified, different from params = {} - - # If a format string is specified, other options do not matter - # except for -width. In that case, we do not call FormatMessage - # at all - if {[info exists opts(fmtstring)]} { - # If -width specifed, call FormatMessage - if {[info exists opts(width)] && $opts(width)} { - set msg [_unsafe_format_message -ignoreinserts -fmtstring $opts(fmtstring) -width $opts(width) {*}$args] - } else { - set msg $opts(fmtstring) - } - } else { - # Not -fmtstring, retrieve from message file - if {[info exists opts(width)]} { - set msg [_unsafe_format_message -ignoreinserts -width $opts(width) {*}$args] - } else { - set msg [_unsafe_format_message -ignoreinserts {*}$args] - } - } - - # If we are told to ignore inserts, all done. Else replace them except - # that if no param list, do not replace placeholder. This is NOT - # the same as empty param list - if {$opts(ignoreinserts) || ![info exists opts(params)]} { - return $msg - } - - # TBD - cache fmtstring -> indices for performance - set placeholder_indices [regexp -indices -all -inline {%(?:.|(?:[1-9][0-9]?(?:![^!]+!)?))} $msg] - - if {[llength $placeholder_indices] == 0} { - # No placeholders. - return $msg - } - - # Use of * in format specifiers will change where the actual parameters - # are positioned - set num_asterisks 0 - set msg2 "" - set prev_end 0 - foreach placeholder $placeholder_indices { - lassign $placeholder start end - # Append the stuff between previous placeholder and this one - append msg2 [string range $msg $prev_end [expr {$start-1}]] - set spec [string range $msg $start+1 $end] - switch -exact -- [string index $spec 0] { - % { append msg2 % } - r { append msg2 \r } - n { append msg2 \n } - t { append msg2 \t } - 0 { - # No-op - %0 means to not add trailing newline - } - default { - if {! [string is integer -strict [string index $spec 0]]} { - # Not a insert parameter. Just append the character - append msg2 $spec - } else { - # Insert parameter - set fmt "" - scan $spec %d%s param_index fmt - # Note params are numbered starting with 1 - incr param_index -1 - # Format spec, if present, is enclosed in !. Get rid of them - set fmt [string trim $fmt "!"] - if {$fmt eq ""} { - # No fmt spec - } else { - # Since everything is a string in Tcl, we happily - # do not have to worry about type. However, the - # format spec could have * specifiers which will - # change the parameter indexing for subsequent - # arguments - incr num_asterisks [expr {[llength [split $fmt *]]-1}] - incr param_index $num_asterisks - } - # TBD - we ignore the actual format type - append msg2 [lindex $opts(params) $param_index] - } - } - } - set prev_end [incr end] - } - append msg2 [string range $msg $prev_end end] - return $msg2 -} - -# Revert to process token. In base package because used across many modules -proc twapi::revert_to_self {{opt ""}} { - RevertToSelf -} - -# For backward compatibility -interp alias {} twapi::expand_environment_strings {} twapi::expand_environment_vars - -proc twapi::_init_security_defs {} { - variable security_defs - - # NOTE : the access definitions for those types that are included here - # have been updated as of Windows 8. - array set security_defs { - - TOKEN_ASSIGN_PRIMARY 0x00000001 - TOKEN_DUPLICATE 0x00000002 - TOKEN_IMPERSONATE 0x00000004 - TOKEN_QUERY 0x00000008 - TOKEN_QUERY_SOURCE 0x00000010 - TOKEN_ADJUST_PRIVILEGES 0x00000020 - TOKEN_ADJUST_GROUPS 0x00000040 - TOKEN_ADJUST_DEFAULT 0x00000080 - TOKEN_ADJUST_SESSIONID 0x00000100 - - TOKEN_ALL_ACCESS_WINNT 0x000F00FF - TOKEN_ALL_ACCESS_WIN2K 0x000F01FF - TOKEN_ALL_ACCESS 0x000F01FF - TOKEN_READ 0x00020008 - TOKEN_WRITE 0x000200E0 - TOKEN_EXECUTE 0x00020000 - - SYSTEM_MANDATORY_LABEL_NO_WRITE_UP 0x1 - SYSTEM_MANDATORY_LABEL_NO_READ_UP 0x2 - SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP 0x4 - - ACL_REVISION 2 - ACL_REVISION_DS 4 - - ACCESS_MAX_MS_V2_ACE_TYPE 0x3 - ACCESS_MAX_MS_V3_ACE_TYPE 0x4 - ACCESS_MAX_MS_V4_ACE_TYPE 0x8 - ACCESS_MAX_MS_V5_ACE_TYPE 0x11 - - STANDARD_RIGHTS_REQUIRED 0x000F0000 - STANDARD_RIGHTS_READ 0x00020000 - STANDARD_RIGHTS_WRITE 0x00020000 - STANDARD_RIGHTS_EXECUTE 0x00020000 - STANDARD_RIGHTS_ALL 0x001F0000 - SPECIFIC_RIGHTS_ALL 0x0000FFFF - - GENERIC_READ 0x80000000 - GENERIC_WRITE 0x40000000 - GENERIC_EXECUTE 0x20000000 - GENERIC_ALL 0x10000000 - - SERVICE_QUERY_CONFIG 0x00000001 - SERVICE_CHANGE_CONFIG 0x00000002 - SERVICE_QUERY_STATUS 0x00000004 - SERVICE_ENUMERATE_DEPENDENTS 0x00000008 - SERVICE_START 0x00000010 - SERVICE_STOP 0x00000020 - SERVICE_PAUSE_CONTINUE 0x00000040 - SERVICE_INTERROGATE 0x00000080 - SERVICE_USER_DEFINED_CONTROL 0x00000100 - SERVICE_ALL_ACCESS 0x000F01FF - - SC_MANAGER_CONNECT 0x00000001 - SC_MANAGER_CREATE_SERVICE 0x00000002 - SC_MANAGER_ENUMERATE_SERVICE 0x00000004 - SC_MANAGER_LOCK 0x00000008 - SC_MANAGER_QUERY_LOCK_STATUS 0x00000010 - SC_MANAGER_MODIFY_BOOT_CONFIG 0x00000020 - SC_MANAGER_ALL_ACCESS 0x000F003F - - KEY_QUERY_VALUE 0x00000001 - KEY_SET_VALUE 0x00000002 - KEY_CREATE_SUB_KEY 0x00000004 - KEY_ENUMERATE_SUB_KEYS 0x00000008 - KEY_NOTIFY 0x00000010 - KEY_CREATE_LINK 0x00000020 - KEY_WOW64_32KEY 0x00000200 - KEY_WOW64_64KEY 0x00000100 - KEY_WOW64_RES 0x00000300 - KEY_READ 0x00020019 - KEY_WRITE 0x00020006 - KEY_EXECUTE 0x00020019 - KEY_ALL_ACCESS 0x000F003F - - POLICY_VIEW_LOCAL_INFORMATION 0x00000001 - POLICY_VIEW_AUDIT_INFORMATION 0x00000002 - POLICY_GET_PRIVATE_INFORMATION 0x00000004 - POLICY_TRUST_ADMIN 0x00000008 - POLICY_CREATE_ACCOUNT 0x00000010 - POLICY_CREATE_SECRET 0x00000020 - POLICY_CREATE_PRIVILEGE 0x00000040 - POLICY_SET_DEFAULT_QUOTA_LIMITS 0x00000080 - POLICY_SET_AUDIT_REQUIREMENTS 0x00000100 - POLICY_AUDIT_LOG_ADMIN 0x00000200 - POLICY_SERVER_ADMIN 0x00000400 - POLICY_LOOKUP_NAMES 0x00000800 - POLICY_NOTIFICATION 0x00001000 - POLICY_READ 0X00020006 - POLICY_WRITE 0X000207F8 - POLICY_EXECUTE 0X00020801 - POLICY_ALL_ACCESS 0X000F0FFF - - DESKTOP_READOBJECTS 0x0001 - DESKTOP_CREATEWINDOW 0x0002 - DESKTOP_CREATEMENU 0x0004 - DESKTOP_HOOKCONTROL 0x0008 - DESKTOP_JOURNALRECORD 0x0010 - DESKTOP_JOURNALPLAYBACK 0x0020 - DESKTOP_ENUMERATE 0x0040 - DESKTOP_WRITEOBJECTS 0x0080 - DESKTOP_SWITCHDESKTOP 0x0100 - - WINSTA_ENUMDESKTOPS 0x0001 - WINSTA_READATTRIBUTES 0x0002 - WINSTA_ACCESSCLIPBOARD 0x0004 - WINSTA_CREATEDESKTOP 0x0008 - WINSTA_WRITEATTRIBUTES 0x0010 - WINSTA_ACCESSGLOBALATOMS 0x0020 - WINSTA_EXITWINDOWS 0x0040 - WINSTA_ENUMERATE 0x0100 - WINSTA_READSCREEN 0x0200 - WINSTA_ALL_ACCESS 0x37f - - PROCESS_TERMINATE 0x0001 - PROCESS_CREATE_THREAD 0x0002 - PROCESS_SET_SESSIONID 0x0004 - PROCESS_VM_OPERATION 0x0008 - PROCESS_VM_READ 0x0010 - PROCESS_VM_WRITE 0x0020 - PROCESS_DUP_HANDLE 0x0040 - PROCESS_CREATE_PROCESS 0x0080 - PROCESS_SET_QUOTA 0x0100 - PROCESS_SET_INFORMATION 0x0200 - PROCESS_QUERY_INFORMATION 0x0400 - PROCESS_SUSPEND_RESUME 0x0800 - - THREAD_TERMINATE 0x00000001 - THREAD_SUSPEND_RESUME 0x00000002 - THREAD_GET_CONTEXT 0x00000008 - THREAD_SET_CONTEXT 0x00000010 - THREAD_SET_INFORMATION 0x00000020 - THREAD_QUERY_INFORMATION 0x00000040 - THREAD_SET_THREAD_TOKEN 0x00000080 - THREAD_IMPERSONATE 0x00000100 - THREAD_DIRECT_IMPERSONATION 0x00000200 - THREAD_SET_LIMITED_INFORMATION 0x00000400 - THREAD_QUERY_LIMITED_INFORMATION 0x00000800 - - EVENT_MODIFY_STATE 0x00000002 - EVENT_ALL_ACCESS 0x001F0003 - - SEMAPHORE_MODIFY_STATE 0x00000002 - SEMAPHORE_ALL_ACCESS 0x001F0003 - - MUTANT_QUERY_STATE 0x00000001 - MUTANT_ALL_ACCESS 0x001F0001 - - MUTEX_MODIFY_STATE 0x00000001 - MUTEX_ALL_ACCESS 0x001F0001 - - TIMER_QUERY_STATE 0x00000001 - TIMER_MODIFY_STATE 0x00000002 - TIMER_ALL_ACCESS 0x001F0003 - - FILE_READ_DATA 0x00000001 - FILE_LIST_DIRECTORY 0x00000001 - FILE_WRITE_DATA 0x00000002 - FILE_ADD_FILE 0x00000002 - FILE_APPEND_DATA 0x00000004 - FILE_ADD_SUBDIRECTORY 0x00000004 - FILE_CREATE_PIPE_INSTANCE 0x00000004 - FILE_READ_EA 0x00000008 - FILE_WRITE_EA 0x00000010 - FILE_EXECUTE 0x00000020 - FILE_TRAVERSE 0x00000020 - FILE_DELETE_CHILD 0x00000040 - FILE_READ_ATTRIBUTES 0x00000080 - FILE_WRITE_ATTRIBUTES 0x00000100 - - FILE_ALL_ACCESS 0x001F01FF - FILE_GENERIC_READ 0x00120089 - FILE_GENERIC_WRITE 0x00120116 - FILE_GENERIC_EXECUTE 0x001200A0 - - DELETE 0x00010000 - READ_CONTROL 0x00020000 - WRITE_DAC 0x00040000 - WRITE_OWNER 0x00080000 - SYNCHRONIZE 0x00100000 - - MAXIMUM_ALLOWED 0x02000000 - - COM_RIGHTS_EXECUTE 1 - COM_RIGHTS_EXECUTE_LOCAL 2 - COM_RIGHTS_EXECUTE_REMOTE 4 - COM_RIGHTS_ACTIVATE_LOCAL 8 - COM_RIGHTS_ACTIVATE_REMOTE 16 - } - - if {[min_os_version 6]} { - array set security_defs { - PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 - PROCESS_ALL_ACCESS 0x001fffff - THREAD_ALL_ACCESS 0x001fffff - } - } else { - array set security_defs { - PROCESS_ALL_ACCESS 0x001f0fff - THREAD_ALL_ACCESS 0x001f03ff - } - } - - # Make next call a no-op - proc _init_security_defs {} {} -} - -# Map a set of access right symbols to a flag. Concatenates -# all the arguments, and then OR's the individual elements. Each -# element may either be a integer or one of the access rights -proc twapi::_access_rights_to_mask {args} { - _init_security_defs - - proc _access_rights_to_mask args { - variable security_defs - set rights 0 - foreach right [concat {*}$args] { - # The mandatory label access rights are not in security_defs - # because we do not want them to mess up the int->name mapping - # for DACL's - set right [dict* { - no_write_up 1 - system_mandatory_label_no_write_up 1 - no_read_up 2 - system_mandatory_label_no_read_up 2 - no_execute_up 4 - system_mandatory_label_no_execute_up 4 - } $right] - if {![string is integer $right]} { - if {[catch {set right $security_defs([string toupper $right])}]} { - error "Invalid access right symbol '$right'" - } - } - set rights [expr {$rights | $right}] - } - return $rights - } - return [_access_rights_to_mask {*}$args] -} - - -# Map an access mask to a set of rights -proc twapi::_access_mask_to_rights {access_mask {type ""}} { - _init_security_defs - - proc _access_mask_to_rights {access_mask {type ""}} { - variable security_defs - - set rights [list ] - - if {$type eq "mandatory_label"} { - if {$access_mask & 1} { - lappend rights system_mandatory_label_no_write_up - } - if {$access_mask & 2} { - lappend rights system_mandatory_label_no_read_up - } - if {$access_mask & 4} { - lappend rights system_mandatory_label_no_execute_up - } - return $rights - } - - # The returned list will include rights that map to multiple bits - # as well as the individual bits. We first add the multiple bits - # and then the individual bits (since we clear individual bits - # after adding) - - # - # Check standard multiple bit masks - # - foreach x {STANDARD_RIGHTS_REQUIRED STANDARD_RIGHTS_READ STANDARD_RIGHTS_WRITE STANDARD_RIGHTS_EXECUTE STANDARD_RIGHTS_ALL SPECIFIC_RIGHTS_ALL} { - if {($security_defs($x) & $access_mask) == $security_defs($x)} { - lappend rights [string tolower $x] - } - } - - # - # Check type specific multiple bit masks. - # - - set type_mask_map { - file {FILE_ALL_ACCESS FILE_GENERIC_READ FILE_GENERIC_WRITE FILE_GENERIC_EXECUTE} - process {PROCESS_ALL_ACCESS} - pipe {FILE_ALL_ACCESS} - policy {POLICY_READ POLICY_WRITE POLICY_EXECUTE POLICY_ALL_ACCESS} - registry {KEY_READ KEY_WRITE KEY_EXECUTE KEY_ALL_ACCESS} - service {SERVICE_ALL_ACCESS} - thread {THREAD_ALL_ACCESS} - token {TOKEN_READ TOKEN_WRITE TOKEN_EXECUTE TOKEN_ALL_ACCESS} - desktop {} - winsta {WINSTA_ALL_ACCESS} - } - if {[dict exists $type_mask_map $type]} { - foreach x [dict get $type_mask_map $type] { - if {($security_defs($x) & $access_mask) == $security_defs($x)} { - lappend rights [string tolower $x] - } - } - } - - # - # OK, now map individual bits - - # First map the common bits - foreach x {DELETE READ_CONTROL WRITE_DAC WRITE_OWNER SYNCHRONIZE} { - if {$security_defs($x) & $access_mask} { - lappend rights [string tolower $x] - resetbits access_mask $security_defs($x) - } - } - - # Then the generic bits - foreach x {GENERIC_READ GENERIC_WRITE GENERIC_EXECUTE GENERIC_ALL} { - if {$security_defs($x) & $access_mask} { - lappend rights [string tolower $x] - resetbits access_mask $security_defs($x) - } - } - - # Then the type specific - set type_mask_map { - file { FILE_READ_DATA FILE_WRITE_DATA FILE_APPEND_DATA - FILE_READ_EA FILE_WRITE_EA FILE_EXECUTE - FILE_DELETE_CHILD FILE_READ_ATTRIBUTES - FILE_WRITE_ATTRIBUTES } - pipe { FILE_READ_DATA FILE_WRITE_DATA FILE_CREATE_PIPE_INSTANCE - FILE_READ_ATTRIBUTES FILE_WRITE_ATTRIBUTES } - service { SERVICE_QUERY_CONFIG SERVICE_CHANGE_CONFIG - SERVICE_QUERY_STATUS SERVICE_ENUMERATE_DEPENDENTS - SERVICE_START SERVICE_STOP SERVICE_PAUSE_CONTINUE - SERVICE_INTERROGATE SERVICE_USER_DEFINED_CONTROL } - registry { KEY_QUERY_VALUE KEY_SET_VALUE KEY_CREATE_SUB_KEY - KEY_ENUMERATE_SUB_KEYS KEY_NOTIFY KEY_CREATE_LINK - KEY_WOW64_32KEY KEY_WOW64_64KEY KEY_WOW64_RES } - policy { POLICY_VIEW_LOCAL_INFORMATION POLICY_VIEW_AUDIT_INFORMATION - POLICY_GET_PRIVATE_INFORMATION POLICY_TRUST_ADMIN - POLICY_CREATE_ACCOUNT POLICY_CREATE_SECRET - POLICY_CREATE_PRIVILEGE POLICY_SET_DEFAULT_QUOTA_LIMITS - POLICY_SET_AUDIT_REQUIREMENTS POLICY_AUDIT_LOG_ADMIN - POLICY_SERVER_ADMIN POLICY_LOOKUP_NAMES } - process { PROCESS_TERMINATE PROCESS_CREATE_THREAD - PROCESS_SET_SESSIONID PROCESS_VM_OPERATION - PROCESS_VM_READ PROCESS_VM_WRITE PROCESS_DUP_HANDLE - PROCESS_CREATE_PROCESS PROCESS_SET_QUOTA - PROCESS_SET_INFORMATION PROCESS_QUERY_INFORMATION - PROCESS_SUSPEND_RESUME} - thread { THREAD_TERMINATE THREAD_SUSPEND_RESUME - THREAD_GET_CONTEXT THREAD_SET_CONTEXT - THREAD_SET_INFORMATION THREAD_QUERY_INFORMATION - THREAD_SET_THREAD_TOKEN THREAD_IMPERSONATE - THREAD_DIRECT_IMPERSONATION - THREAD_SET_LIMITED_INFORMATION - THREAD_QUERY_LIMITED_INFORMATION } - token { TOKEN_ASSIGN_PRIMARY TOKEN_DUPLICATE TOKEN_IMPERSONATE - TOKEN_QUERY TOKEN_QUERY_SOURCE TOKEN_ADJUST_PRIVILEGES - TOKEN_ADJUST_GROUPS TOKEN_ADJUST_DEFAULT TOKEN_ADJUST_SESSIONID } - desktop { DESKTOP_READOBJECTS DESKTOP_CREATEWINDOW - DESKTOP_CREATEMENU DESKTOP_HOOKCONTROL - DESKTOP_JOURNALRECORD DESKTOP_JOURNALPLAYBACK - DESKTOP_ENUMERATE DESKTOP_WRITEOBJECTS DESKTOP_SWITCHDESKTOP } - windowstation { WINSTA_ENUMDESKTOPS WINSTA_READATTRIBUTES - WINSTA_ACCESSCLIPBOARD WINSTA_CREATEDESKTOP - WINSTA_WRITEATTRIBUTES WINSTA_ACCESSGLOBALATOMS - WINSTA_EXITWINDOWS WINSTA_ENUMERATE WINSTA_READSCREEN } - winsta { WINSTA_ENUMDESKTOPS WINSTA_READATTRIBUTES - WINSTA_ACCESSCLIPBOARD WINSTA_CREATEDESKTOP - WINSTA_WRITEATTRIBUTES WINSTA_ACCESSGLOBALATOMS - WINSTA_EXITWINDOWS WINSTA_ENUMERATE WINSTA_READSCREEN } - com { COM_RIGHTS_EXECUTE COM_RIGHTS_EXECUTE_LOCAL - COM_RIGHTS_EXECUTE_REMOTE COM_RIGHTS_ACTIVATE_LOCAL - COM_RIGHTS_ACTIVATE_REMOTE - } - } - - if {[min_os_version 6]} { - dict lappend type_mask_map process PROCESS_QUERY_LIMITED_INFORMATION - } - - if {[dict exists $type_mask_map $type]} { - foreach x [dict get $type_mask_map $type] { - if {$security_defs($x) & $access_mask} { - lappend rights [string tolower $x] - # Reset the bit so is it not included in unknown bits below - resetbits access_mask $security_defs($x) - } - } - } - - # Finally add left over bits if any - for {set i 0} {$i < 32} {incr i} { - set x [expr {1 << $i}] - if {$access_mask & $x} { - lappend rights [hex32 $x] - } - } - - return $rights - } - - return [_access_mask_to_rights $access_mask $type] -} - -# Map the symbolic CreateDisposition parameter of CreateFile to integer values -proc twapi::_create_disposition_to_code {sym} { - if {[string is integer -strict $sym]} { - return $sym - } - # CREATE_NEW 1 - # CREATE_ALWAYS 2 - # OPEN_EXISTING 3 - # OPEN_ALWAYS 4 - # TRUNCATE_EXISTING 5 - return [dict get { - create_new 1 - create_always 2 - open_existing 3 - open_always 4 - truncate_existing 5} $sym] -} - -# Wrapper around CreateFile -proc twapi::create_file {path args} { - array set opts [parseargs args { - {access.arg {generic_read}} - {share.arg {read write delete}} - {inherit.bool 0} - {secd.arg ""} - {createdisposition.arg open_always} - {flags.int 0} - {templatefile.arg NULL} - } -maxleftover 0] - - set access_mode [_access_rights_to_mask $opts(access)] - set share_mode [_share_mode_to_mask $opts(share)] - set create_disposition [_create_disposition_to_code $opts(createdisposition)] - return [CreateFile $path \ - $access_mode \ - $share_mode \ - [_make_secattr $opts(secd) $opts(inherit)] \ - $create_disposition \ - $opts(flags) \ - $opts(templatefile)] -} - -# Map a set of share mode symbols to a flag. Concatenates -# all the arguments, and then OR's the individual elements. Each -# element may either be a integer or one of the share modes -proc twapi::_share_mode_to_mask {modelist} { - # Values correspond to FILE_SHARE_* defines - return [_parse_symbolic_bitmask $modelist {read 1 write 2 delete 4}] -} - -# Construct a security attributes structure out of a security descriptor -# and inheritance. The command is here because we do not want to -# have to load the twapi_security package for the common case of -# null security attributes. -proc twapi::_make_secattr {secd inherit} { - if {$inherit} { - set sec_attr [list $secd 1] - } else { - if {[llength $secd] == 0} { - # If a security descriptor not specified, keep - # all security attributes as an empty list (ie. NULL) - set sec_attr [list ] - } else { - set sec_attr [list $secd 0] - } - } - return $sec_attr -} - -# Returns the sid, domain and type for an account -proc twapi::lookup_account_name {name args} { - variable _name_to_sid_cache - - # Fast path - no options specified and cached - if {[llength $args] == 0 && [dict exists $_name_to_sid_cache "" $name]} { - return [lindex [dict get $_name_to_sid_cache "" $name] 0] - } - - array set opts [parseargs args \ - [list all \ - sid \ - domain \ - type \ - [list system.arg ""]\ - ]] - - if {! [dict exists $_name_to_sid_cache $opts(system) $name]} { - dict set _name_to_sid_cache $opts(system) $name [LookupAccountName $opts(system) $name] - } - lassign [dict get $_name_to_sid_cache $opts(system) $name] sid domain type - - set result [list ] - if {$opts(all) || $opts(domain)} { - lappend result -domain $domain - } - if {$opts(all) || $opts(type)} { - if {[info exists twapi::sid_type_names($type)]} { - lappend result -type $twapi::sid_type_names($type) - } else { - # Could be the "logonid" dummy type we added above - lappend result -type $type - } - } - - if {$opts(all) || $opts(sid)} { - lappend result -sid $sid - } - - # If no options specified, only return the sid/name - if {[llength $result] == 0} { - return $sid - } - - return $result -} - - -# Returns the name, domain and type for an account -proc twapi::lookup_account_sid {sid args} { - variable _sid_to_name_cache - - # Fast path - no options specified and cached - if {[llength $args] == 0 && [dict exists $_sid_to_name_cache "" $sid]} { - return [lindex [dict get $_sid_to_name_cache "" $sid] 0] - } - - array set opts [parseargs args \ - [list all \ - name \ - domain \ - type \ - [list system.arg ""]\ - ]] - - if {[dict exists $_sid_to_name_cache $opts(system) $sid]} { - lassign [dict get $_sid_to_name_cache $opts(system) $sid] name domain type - } else { - # Not in cache. Need to look up - - trap { - set data [LookupAccountSid $opts(system) $sid] - lassign $data name domain type - } onerror {TWAPI_WIN32 1332} { - # Win10 resolves this, Win7 does not. Emulate Win10 - if {![string match -nocase "S-1-5-5-*" $sid]} { - rethrow - } - # Name is formed similar to how Win10 does it - set name "LogonSessionId_[string map {- _} [string range $sid 8 end]]" - set domain "NT AUTHORITY" - set type 11 - set data [list $name $domain $type] - } - dict set _sid_to_name_cache $opts(system) $sid $data - } - - - set result [list ] - if {$opts(all) || $opts(domain)} { - lappend result -domain $domain - } - if {$opts(all) || $opts(type)} { - if {[info exists twapi::sid_type_names($type)]} { - lappend result -type $twapi::sid_type_names($type) - } else { - lappend result -type $type - } - } - - if {$opts(all) || $opts(name)} { - lappend result -name $name - } - - # If no options specified, only return the sid/name - if {[llength $result] == 0} { - return $name - } - - return $result -} - -# Returns the sid for a account - may be given as a SID or name -proc twapi::map_account_to_sid {account args} { - array set opts [parseargs args {system.arg} -nulldefault] - - # Treat empty account as null SID (self) - if {[string length $account] == ""} { - return "" - } - - if {[is_valid_sid_syntax $account]} { - return $account - } else { - return [lookup_account_name $account -system $opts(system)] - } -} - - -# Returns the name for a account - may be given as a SID or name -proc twapi::map_account_to_name {account args} { - array set opts [parseargs args {system.arg} -nulldefault] - - if {[is_valid_sid_syntax $account]} { - return [lookup_account_sid $account -system $opts(system)] - } else { - # Verify whether a valid account by mapping to an sid - if {[catch {map_account_to_sid $account -system $opts(system)}]} { - # As a special case, change LocalSystem to SYSTEM. Some Windows - # API's (such as services) return LocalSystem which cannot be - # resolved by the security functions. This name is really the - # same a the built-in SYSTEM - if {$account == "LocalSystem"} { - return "SYSTEM" - } - error "Unknown account '$account'" - } - return $account - } -} - -# Return the user account for the current process -proc twapi::get_current_user {{format -samcompatible}} { - - set return_sid false - switch -exact -- $format { - -fullyqualifieddn {set format 1} - -samcompatible {set format 2} - -display {set format 3} - -uniqueid {set format 6} - -canonical {set format 7} - -userprincipal {set format 8} - -canonicalex {set format 9} - -serviceprincipal {set format 10} - -dnsdomain {set format 12} - -sid {set format 2 ; set return_sid true} - default { - error "Unknown user name format '$format'" - } - } - - set user [GetUserNameEx $format] - - if {$return_sid} { - return [map_account_to_sid $user] - } else { - return $user - } -} - -# Get a new uuid -proc twapi::new_uuid {{opt ""}} { - if {[string length $opt]} { - if {[string equal $opt "-localok"]} { - set local_ok 1 - } else { - error "Invalid or unknown argument '$opt'" - } - } else { - set local_ok 0 - } - return [UuidCreate $local_ok] -} -proc twapi::nil_uuid {} { - return [UuidCreateNil] -} - -proc twapi::new_guid {} { - return [canonicalize_guid [new_uuid]] -} - -# Get a handle to a LSA policy. TBD - document -proc twapi::get_lsa_policy_handle {args} { - array set opts [parseargs args { - {system.arg ""} - {access.arg policy_read} - } -maxleftover 0] - - set access [_access_rights_to_mask $opts(access)] - return [Twapi_LsaOpenPolicy $opts(system) $access] -} - -# Close a LSA policy handle. TBD - document -proc twapi::close_lsa_policy_handle {h} { - LsaClose $h - return -} - -# Eventlog stuff in the base package - -namespace eval twapi { - # Keep track of event log handles - values are "r" or "w" - variable eventlog_handles - array set eventlog_handles {} -} - -# Open an eventlog for reading or writing -proc twapi::eventlog_open {args} { - variable eventlog_handles - - array set opts [parseargs args { - system.arg - source.arg - file.arg - write - } -nulldefault -maxleftover 0] - if {$opts(source) == ""} { - # Source not specified - if {$opts(file) == ""} { - # No source or file specified, default to current event log - # using executable name as source - set opts(source) [file rootname [file tail [info nameofexecutable]]] - } else { - if {$opts(write)} { - error "Option -file may not be used with -write" - } - } - } else { - # Source explicitly specified - if {$opts(file) != ""} { - error "Option -file may not be used with -source" - } - } - - if {$opts(write)} { - set handle [RegisterEventSource $opts(system) $opts(source)] - set mode write - } else { - if {$opts(source) != ""} { - set handle [OpenEventLog $opts(system) $opts(source)] - } else { - set handle [OpenBackupEventLog $opts(system) $opts(file)] - } - set mode read - } - - set eventlog_handles($handle) $mode - return $handle -} - -# Close an event log opened for writing -proc twapi::eventlog_close {hevl} { - variable eventlog_handles - - if {[_eventlog_valid_handle $hevl read]} { - CloseEventLog $hevl - } else { - DeregisterEventSource $hevl - } - - unset eventlog_handles($hevl) -} - - -# Log an event -proc twapi::eventlog_write {hevl id args} { - _eventlog_valid_handle $hevl write raise - - array set opts [parseargs args { - {type.arg information {success error warning information auditsuccess auditfailure}} - {category.int 1} - loguser - params.arg - data.arg - } -nulldefault] - - - switch -exact -- $opts(type) { - success {set opts(type) 0} - error {set opts(type) 1} - warning {set opts(type) 2} - information {set opts(type) 4} - auditsuccess {set opts(type) 8} - auditfailure {set opts(type) 16} - default {error "Invalid value '$opts(type)' for option -type"} - } - - if {$opts(loguser)} { - set user [get_current_user -sid] - } else { - set user "" - } - - ReportEvent $hevl $opts(type) $opts(category) $id \ - $user $opts(params) $opts(data) -} - - -# Log a message -proc twapi::eventlog_log {message args} { - array set opts [parseargs args { - system.arg - source.arg - {type.arg information} - {category.int 0} - } -nulldefault] - - set hevl [eventlog_open -write -source $opts(source) -system $opts(system)] - - trap { - eventlog_write $hevl 1 -params [list $message] -type $opts(type) -category $opts(category) - } finally { - eventlog_close $hevl - } - return -} - -proc twapi::make_logon_identity {username password domain} { - if {[concealed? $password]} { - return [list $username $domain $password] - } else { - return [list $username $domain [conceal $password]] - } -} - -proc twapi::read_credentials {args} { - # DEPRECATED - array set opts [parseargs args { - target.arg - winerror.int - username.arg - password.arg - persist.bool - {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} - {forceui.bool 0 0x80} - {showsaveoption.bool true} - {expectconfirmation.bool 0 0x20000} - } -maxleftover 0 -nulldefault] - - if {$opts(persist) && ! $opts(expectconfirmation)} { - badargs! "Option -expectconfirmation must be specified as true if -persist is true" - } - - # 0x8 -> CREDUI_FLAGS_EXCLUDE_CERTIFICATES (needed for console) - set flags [expr {0x8 | $opts(forceui) | $opts(expectconfirmation)}] - - if {$opts(persist)} { - if {! $opts(showsaveoption)} { - incr flags 0x1000; # CREDUI_FLAGS_PERSIST - } - } else { - incr flags 0x2; # CREDUI_FLAGS_DO_NOT_PERSIST - if {$opts(showsaveoption)} { - incr flags 0x40; # CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX - } - } - - incr flags $opts(type) - - return [CredUICmdLinePromptForCredentials $opts(target) NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] -} - -proc twapi::credentials_dialog {args} { - # DEPRECATED - array set opts [parseargs args { - target.arg - winerror.int - username.arg - password.arg - persist.bool - {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} - {forceui.bool 0 0x80} - {showsaveoption.bool true} - {expectconfirmation.bool 0 0x20000} - {fillusername.bool 0 0x800} - {filllocaladmins.bool 0 0x4} - {notifyfail.bool 0 0x1} - {passwordonly.bool 0 0x200} - {requirecertificate.bool 0 0x10} - {requiresmartcard.bool 0 0x100} - {validateusername.bool 0 0x400} - {parent.arg NULL} - message.arg - caption.arg - {bitmap.arg NULL} - } -maxleftover 0 -nulldefault] - - if {$opts(persist) && ! $opts(expectconfirmation)} { - badargs! "Option -willconfirm must be specified as true if -persist is true" - } - - set flags [expr { 0x8 | $opts(forceui) | $opts(notifyfail) | $opts(expectconfirmation) | $opts(fillusername) | $opts(filllocaladmins)}] - - if {$opts(persist)} { - if {! $opts(showsaveoption)} { - incr flags 0x1000; # CREDUI_FLAGS_PERSIST - } - } else { - incr flags 0x2; # CREDUI_FLAGS_DO_NOT_PERSIST - if {$opts(showsaveoption)} { - incr flags 0x40; # CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX - } - } - - incr flags $opts(type) - - return [CredUIPromptForCredentials [list $opts(parent) $opts(message) $opts(caption) $opts(bitmap)] $opts(target) NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] -} - -proc twapi::confirm_credentials {target valid} { - # DEPRECATED - return [CredUIConfirmCredentials $target $valid] -} - - -proc twapi::_make_cred_persist_flags {persist showsave} { - # Use cases - - # (1) credentials to be persisted WITHOUT showing save option to user - # (2) credentials to be persisted AFTER showing save option to user - # (3) credentials NOT to be persisted, user not shown save option - # (4) credentials NOT to be persisted, but user shown save option - # In case (4), caller has to decide what to do with the credentials if - # user selects to save (e.g. save elsewhere) - # If credentials are to be persisted, caller MUST call cred_confirm later - - if {$persist} { - # Note CREDUI_FLAGS_EXPECT_CONFIRMATION (0x20000) must be specified - # whenever CREDUI_FLAGS_DO_NOT_PERSIST is not specified - if {$showsave} { - # (2) CREDUI_FLAGS_EXPECT_CONFIRMATION - return 0x20000 - } else { - # (1) CREDUI_FLAGS_PERSIST | CREDUI_FLAGS_EXPECT_CONFIRMATION - return 0x21000 - } - } else { - if {$showsave} { - # (4) CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX - return 0x42 - } else { - # (3) CREDUI_FLAGS_DO_NOT_PERSIST - return 0x02 - } - } -} - -proc twapi::cred_prompt_console {target args} { - # Not documented because Windows seems to ignore on Win10 at least - - # -password, -winerror - array set opts [parseargs args { - {forceui.bool 0 0x80} - password.arg - persist.bool - {showsaveoption.bool 0} - {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} - username.arg - winerror.int - } -maxleftover 0 -nulldefault] - - if {$target eq ""} { - error "Target must not be an empty string." - } - - if {$opts(forceui) && $opts(type) != 0x40000} { - error "The -forceui option can only be set if -type is \"generic\"." - } - - if {$opts(type) == 0x80000 && $opts(username) eq ""} { - # CredUIPromptForCredentials crashes - error "The -username option must not be an empty string if -type is \"runas\"." - } - - set flags [_make_cred_persist_flags $opts(persist) $opts(showsaveoption)] - - # 0x8 -> CREDUI_FLAGS_EXCLUDE_CERTIFICATES (needed for console) - set flags [expr {0x8 | $flags | $opts(type) | $opts(forceui)}] - - return [CredUICmdLinePromptForCredentials $target NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] -} - -proc twapi::cred_prompt_gui {target args} { - # Not documented because Windows seems to ignore on Win10 at least - - # -passwordonly, -notifyfail, -winerror - array set opts [parseargs args { - {bitmap.arg NULL} - caption.arg - {excludecertificates.bool 0 0x8} - {filllocaladmins.bool 0 0x4} - {completeusername.bool 0 0x800} - {forceui.bool 0 0x80} - {keepusername.bool 0 0x100000} - message.arg - {notifyfail.bool 0 0x1} - {parent.arg NULL} - password.arg - {passwordonly.bool 0 0x200} - persist.bool - {requirecertificate.bool 0 0x10} - {requiresmartcard.bool 0 0x100} - {showsaveoption.bool 0} - {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} - username.arg - {validateusername.bool 0 0x400} - winerror.int - } -maxleftover 0 -nulldefault] - - if {$target eq ""} { - error "Target must not be an empty string." - } - - if {$opts(forceui) && $opts(type) != 0x40000} { - error "The -forceui option can only be set if -type is \"generic\"." - } - - if {$opts(type) == 0x80000 && $opts(username) eq ""} { - # CredUIPromptForCredentials crashes - error "The -username option must not be an empty string if -type is \"runas\"." - } - - set flags [_make_cred_persist_flags $opts(persist) $opts(showsaveoption)] - set flags [expr { - $flags | - $opts(excludecertificates) | - $opts(filllocaladmins) | - $opts(completeusername) | - $opts(forceui) | - $opts(keepusername) | - $opts(notifyfail) | - $opts(passwordonly) | - $opts(requirecertificate) | - $opts(requiresmartcard) | - $opts(type) | - $opts(validateusername) - }] - - return [CredUIPromptForCredentials [list $opts(parent) $opts(message) $opts(caption) $opts(bitmap)] $target NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] -} - -proc twapi::cred_confirm {target valid} { - return [CredUIConfirmCredentials $target $valid] -} - -# Validate a handle for a mode. Always raises error if handle is invalid -# If handle valid but not for that mode, will raise error iff $raise_error -# is non-empty. Returns 1 if valid, 0 otherwise -proc twapi::_eventlog_valid_handle {hevl mode {raise_error ""}} { - variable eventlog_handles - if {![info exists eventlog_handles($hevl)]} { - error "Invalid event log handle '$hevl'" - } - - if {[string compare $eventlog_handles($hevl) $mode]} { - if {$raise_error != ""} { - error "Eventlog handle '$hevl' not valid for $mode" - } - return 0 - } else { - return 1 - } -} - -### Common disk related - -# Map bit mask to list of drive letters -proc twapi::_drivemask_to_drivelist {drivebits} { - set drives [list ] - set i 0 - foreach drive {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} { - if {$drivebits == 0} break - set drivemask [expr {1 << $i}] - if {[expr {$drivebits & $drivemask}]} { - lappend drives $drive: - set drivebits [expr {$drivebits & ~ $drivemask}] - } - incr i - } - return $drives -} - -### Type casts -proc twapi::tclcast {type val} { - # Only permit these because wideInt, for example, cannot be reliably - # converted -> it can return an int instead. - set types {"" empty null bstr int boolean double string list dict} - if {$type ni $types} { - badargs! "Bad cast to \"$type\". Must be one of: $types" - } - return [Twapi_InternalCast $type $val] -} - -if {[info commands ::lmap] eq "::lmap"} { - proc twapi::safearray {type l} { - set type [dict! { - variant "" - boolean boolean - bool boolean - int int - i4 int - double double - r8 double - string string - bstr string - } $type] - return [lmap val $l {tclcast $type $val}] - } -} else { - proc twapi::safearray {type l} { - set type [dict! { - variant "" - boolean boolean - bool boolean - int int - i4 int - double double - r8 double - string string - bstr string - } $type] - set l2 {} - foreach val $l { - lappend l2 [tclcast $type $val] - } - return $l2 - } -} - -namespace eval twapi::recordarray {} - -proc twapi::recordarray::size {ra} { - return [llength [lindex $ra 1]] -} - -proc twapi::recordarray::fields {ra} { - return [lindex $ra 0] -} - -proc twapi::recordarray::index {ra row args} { - set r [lindex $ra 1 $row] - if {[llength $r] == 0} { - return $r - } - ::twapi::parseargs args { - {format.arg list {list dict}} - slice.arg - } -setvars -maxleftover 0 - - set fields [lindex $ra 0] - if {[info exists slice]} { - set new_fields {} - set new_r {} - foreach field $slice { - set i [twapi::enum $fields $field] - lappend new_r [lindex $r $i] - lappend new_fields [lindex $fields $i] - } - set r $new_r - set fields $new_fields - } - - if {$format eq "list"} { - return $r - } else { - return [::twapi::twine $fields $r] - } -} - -proc twapi::recordarray::range {ra low high} { - return [list [lindex $ra 0] [lrange [lindex $ra 1] $low $high]] -} - -proc twapi::recordarray::column {ra field args} { - # TBD - time to see if a script loop would be faster - ::twapi::parseargs args { - filter.arg - } -nulldefault -maxleftover 0 -setvars - _recordarray -slice [list $field] -filter $filter -format flat $ra -} - -proc twapi::recordarray::cell {ra row field} { - return [lindex [lindex $ra 1 $row] [twapi::enum [lindex $ra 0] $field]] -} - -proc twapi::recordarray::get {ra args} { - ::twapi::parseargs args { - {format.arg list {list dict flat}} - key.arg - } -ignoreunknown -setvars - - # format & key are options just to stop them flowing down to _recordarray - # We do not pass it in - - return [_recordarray {*}$args $ra] -} - -proc twapi::recordarray::getlist {ra args} { - # key is an option just to stop in flowing down to _recordarray - # We do not pass it in - - if {[llength $args] == 0} { - return [lindex $ra 1] - } - - ::twapi::parseargs args { - {format.arg list {list dict flat}} - key.arg - } -ignoreunknown -setvars - - - return [_recordarray {*}$args -format $format $ra] -} - -proc twapi::recordarray::getdict {ra args} { - ::twapi::parseargs args { - {format.arg list {list dict}} - key.arg - } -ignoreunknown -setvars - - if {![info exists key]} { - set key [lindex $ra 0 0] - } - - # Note _recordarray has different (putting it politely) semantics - # of how -format and -key option are handled so the below might - # look a bit strange in that we pass -format as list and get - # back a dict - return [_recordarray {*}$args -format $format -key $key $ra] -} - -proc twapi::recordarray::iterate {arrayvarname ra args} { - - if {[llength $args] == 0} { - badargs! "No script supplied" - } - - set body [lindex $args end] - set args [lrange $args 0 end-1] - - upvar 1 $arrayvarname var - - # TBD - Can this be optimized by prepending a ::foreach to body - # and executing that in uplevel 1 ? - - foreach rec [getlist $ra {*}$args -format dict] { - array set var $rec - set code [catch {uplevel 1 $body} result] - switch -exact -- $code { - 0 {} - 1 { - return -errorinfo $::errorInfo -errorcode $::errorCode -code error $result - } - 3 { - return; # break - } - 4 { - # continue - } - default { - return -code $code $result - } - } - } - return -} - -proc twapi::recordarray::rename {ra renames} { - set new_fields {} - foreach field [lindex $ra 0] { - if {[dict exists $renames $field]} { - lappend new_fields [dict get $renames $field] - } else { - lappend new_fields $field - } - } - return [list $new_fields [lindex $ra 1]] -} - -proc twapi::recordarray::concat {args} { - if {[llength $args] == 0} { - return {} - } - set args [lassign $args ra] - set fields [lindex $ra 0] - set values [list [lindex $ra 1]] - set width [llength $fields] - foreach ra $args { - foreach fld1 $fields fld2 [lindex $ra 0] { - if {$fld1 ne $fld2} { - twapi::badargs! "Attempt to concat record arrays with different fields ([join $fields ,] versus [join [lindex $ra 0] ,])" - } - } - lappend values [lindex $ra 1] - } - - return [list $fields [::twapi::lconcat {*}$values]] -} - -namespace eval twapi::recordarray { - namespace export cell column concat fields get getdict getlist index iterate range rename size - namespace ensemble create -} - -proc twapi::_parse_ctype {def parse_mode} { - variable _struct_defs - - # parse_mode is "struct", "param" or "function" - - if {![regexp -expanded { - ^ - \s* - (.+[^[:alnum:]_]) # type - ([[:alnum:]_]+) # name - \s* - (\[.+\])? # array size - \s*$ - } $def -> type name array]} { - error "Invalid C type definition $def" - } - - set child {} - switch -regexp -matchvar matchvar -- [string trim $type] { - {^void$} { - if {$parse_mode ne "function"} { - error "Type void cannot be used for structs and parameters." - } - set type void - } - {^char$} {set type i1} - {^BYTE$} - - {^unsigned char$} {set type ui1} - {^short$} {set type i2} - {^WORD$} - - {^unsigned\s+short$} {set type ui2} - {^BOOLEAN$} {set type bool} - {^LONG$} - - {^int$} {set type i4} - {^UINT$} - - {^ULONG$} - - {^DWORD$} - - {^unsigned\s+int$} {set type ui4} - {^__int64$} {set type i8} - {^unsigned\s+__int64$} {set type ui8} - {^double$} {set type r8} - {^float$} {set type r4} - {^LPCSTR$} - - {^LPSTR$} - - {^char\s*\*$} {set type lpstr} - {^LPCWSTR$} - - {^LPWSTR$} - - {^WCHAR\s*\*$} {set type lpwstr} - {^HANDLE$} {set type handle} - {^PSID$} {set type psid} - {^struct\s+([[:alnum:]_]+)$} { - if {$parse_mode ne "struct"} { - error "Structure types not allowed for parameters and return values." - } - # Embedded struct. It should be defined already. Calling - # it with no args returns its definition but doing that - # to retrieve the definition could be a security hole - # (could be passed any Tcl command!) if unwary apps - # pass in input from unknown sources. So we explicitly - # remember definitions instead. - set child_name [lindex $matchvar 1] - if {![info exists _struct_defs($child_name)]} { - error "Unknown struct $child_name" - } - set child $_struct_defs($child_name) - set type struct - } - default {error "Unknown type $type"} - } - set count 0 - if {$array ne ""} { - set count [string trim [string range $array 1 end-1]] - if {![string is integer -strict $count]} { - error "Non-integer array size" - } - if {$parse_mode ne "struct"} { - error "Arrays not allowed for parameters and return values." - } - } - - if {[string equal -nocase $name "cbSize"] && - $type in {i4 ui4} && $count == 0} { - set type cbsize - } - - return [list $name $type $count $child] -} - -proc twapi::_parse_cproto {s} { - variable _struct_defs - - # Get rid of comments - regsub -all {(/\*.* \*/){1,1}?} $s {} s - regsub -line -all {//.*$} $s { } s - - if {![regexp -expanded { - ^ - \s* - (?:(_cdecl|_stdcall)\s+)? - ([[:alnum:]_][[:space:][:alnum:]_]*) # Function type and name - \s* - \( # Beginning of parameters - ([^\)]*) # Parameter definition string - \) # End of parameters - \s*$ # End of prototype - } $s -> callconv fntypeandname paramstr]} { - error "Invalid C prototype \"$s\"" - } - - regsub -all {\s+} $fntypeandname " " - set fntype [_parse_ctype $fntypeandname function] - set params {} - foreach def [split $paramstr ","] { - lappend params [_parse_ctype $def param] - } - - return [list $callconv $fntype [lindex $fntype 0] $params] -} - -# Return a suitable cstruct definition based on a C definition -proc twapi::struct {struct_name s} { - variable _struct_defs - - if {0} { - TBD - Commented out because nested structs do not currently - handle namespaces. However this means structs are effectively - global even if the corresponding command is not. - set struct_name [callerns $struct_name] - } - - regsub -all {(/\*.* \*/){1,1}?} $s {} s - regsub -line -all {//.*$} $s { } s - set l {} - foreach def [split $s ";"] { - set def [string trim $def] - if {$def eq ""} continue - lappend l [_parse_ctype $def struct] - } - - set proc_body [format { - set def %s - if {[llength $args] == 0} { - return $def - } else { - return [list $def $args] - } - } [list $l]] - uplevel 1 [list proc $struct_name args $proc_body] - set _struct_defs($struct_name) $l - return -} - - -proc twapi::ffi_load {path} { - variable _ffi_paths - variable _ffi_handles - - # Note we do NOT normalize path as we leave it to the OS to do so. - # We also do not canonicalize it (e.g. all lower case). - # This means there may be multiple handles for a single shared lib - # but that's ok. - - if {[dict exists $_ffi_paths $path]} { - set h [dict get $_ffi_paths $path] - if {![dict exists $_ffi_handles $h]} { - error "Internal error: Handle $h not found in FFI table." - } - dict with _ffi_handles $h { - if {$Path ne $path} { - error "Internal error: Handle $h not assigned to $path" - } - incr NRefs - } - } else { - set h [load_library $path] - dict set _ffi_paths $path $h - dict set _ffi_handles $h Path $path - dict set _ffi_handles $h NRefs 1 - } - return $h -} - -proc twapi::ffi_unload {h} { - variable _ffi_handles - variable _ffi_paths - - if {![dict exists $_ffi_handles $h]} { - error "FFI handle $h does not exist." - } - - dict with _ffi_handles $h { - if {[incr NRefs -1] <= 0} { - dict unset _ffi_paths $Path - dict unset _ffi_handles $h - } - } - - return -} - -proc twapi::ffi_cfuncs {dllh cprotos {ns ::}} { - variable _ffi_handles - - if {![dict exists $_ffi_handles $dllh]} { - # error "Unknown FFI handle \"$dllh\"." - } - - set l {} - foreach cproto [split $cprotos ";"] { - set cproto [string trim $cproto] - if {$cproto eq ""} continue - lappend l [_parse_cproto $cproto] - } - set cprotos $l - - set def { - proc %NAME% {%PARAMNAMES%} { - if {![dict exists $%TWAPINS%::_ffi_handles %DLLH%]} { - error "Attempt to call function in unloaded library." - } - %TWAPINS%::%CALL% %FNADDR% %FNTYPE% %PARAMS% [list %PARAMREFS%] - } - } - - if {$::tcl_platform(pointerSize) == 8} { - # Win64 has single calling convention - set callmap {"" ffi_call _cdecl ffi_call _stdcall ffi_call} - } else { - set callmap {"" ffi_call _cdecl ffi_call _stdcall ffi_stdcall} - } - - foreach cproto $cprotos { - lassign $cproto callconv fntype fnname params - set call [dict get $callmap $callconv] - - set fnaddr [GetProcAddress $dllh $fnname] - if {[pointer_null? $fnaddr]} { - error "Entry point $fnname not found in shared library." - } - set paramnames {} - set paramrefs {} - foreach arg $params { - set name [lindex $arg 0] - lappend paramnames $name - lappend paramrefs \$$name - } - - # Note that fntype is doubly listified because the C ffi expects - # it in same format as params, ie. a list of type definitions - # _parse_cproto however returns it as a single type definition - append defs [string map [list \ - %CALL% $call \ - %DLLH% [list $dllh] \ - %NAME% ${ns}::$fnname \ - %PARAMNAMES% [join $paramnames { }] \ - %PARAMREFS% [join $paramrefs { }] \ - %TWAPINS% [namespace current] \ - %FNADDR% [list $fnaddr] \ - %FNTYPE% [list [list $fntype]] \ - %PARAMS% [list $params]] \ - $def] \n - } - - uplevel 1 $defs -} - - -if {[twapi::min_os_version 6]} { - twapi::ffi_cfuncs [twapi::ffi_load kernel32.dll] { - UINT GetErrorMode(); - UINT SetErrorMode(UINT mode); - } ::twapi -} - +# +# Copyright (c) 2012-2014, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Commands in twapi_base module + +namespace eval twapi { + # Map of Sid integer type to Sid type name + variable sid_type_names + array set sid_type_names { + 1 user + 2 group + 3 domain + 4 alias + 5 wellknowngroup + 6 deletedaccount + 7 invalid + 8 unknown + 9 computer + 10 label + 11 logonsession + } + + # Cache mapping account names to SIDs. Dict keyed by system and name + variable _name_to_sid_cache {} + + # Cache mapping SIDs to account names. Dict keyed by system and SID + variable _sid_to_name_cache {} + + # Dictionary of FFI libraries to handles and back + variable _ffi_paths {} + variable _ffi_handles {} +} + + + +# Return major minor servicepack as a quad list +proc twapi::get_os_version {} { + array set verinfo [GetVersionEx] + return [list $verinfo(dwMajorVersion) $verinfo(dwMinorVersion) \ + $verinfo(wServicePackMajor) $verinfo(wServicePackMinor)] +} + +# Returns true if the OS version is at least $major.$minor.$sp +proc twapi::min_os_version {major {minor 0} {spmajor 0} {spminor 0}} { + lassign [twapi::get_os_version] osmajor osminor osspmajor osspminor + + if {$osmajor > $major} {return 1} + if {$osmajor < $major} {return 0} + if {$osminor > $minor} {return 1} + if {$osminor < $minor} {return 0} + if {$osspmajor > $spmajor} {return 1} + if {$osspmajor < $spmajor} {return 0} + if {$osspminor > $spminor} {return 1} + if {$osspminor < $spminor} {return 0} + + # Same version, ok + return 1 +} + +# Convert a LARGE_INTEGER time value (100ns since 1601) to a formatted date +# time +interp alias {} twapi::large_system_time_to_secs {} twapi::large_system_time_to_secs_since_1970 +proc twapi::large_system_time_to_secs_since_1970 {ns100 {fraction false}} { + # No. 100ns units between 1601 to 1970 = 116444736000000000 + set ns100_since_1970 [expr {$ns100-116444736000000000}] + + set secs_since_1970 [expr {$ns100_since_1970/10000000}] + if {$fraction} { + append secs_since_1970 .[string range $ns100 end-6 end] + } + return $secs_since_1970 +} + +proc twapi::secs_since_1970_to_large_system_time {secs} { + # No. 100ns units between 1601 to 1970 = 116444736000000000 + return [expr {($secs * 10000000) + 116444736000000000}] +} + +# Map a Windows error code to a string +proc twapi::map_windows_error {code} { + # Trim trailing CR/LF + return [string trimright [twapi::Twapi_MapWindowsErrorToString $code] "\r\n"] +} + +# Load given library +proc twapi::load_library {path args} { + array set opts [parseargs args { + dontresolverefs + datafile + alteredpath + }] + + set flags 0 + if {$opts(dontresolverefs)} { + setbits flags 1; # DONT_RESOLVE_DLL_REFERENCES + } + if {$opts(datafile)} { + setbits flags 2; # LOAD_LIBRARY_AS_DATAFILE + } + if {$opts(alteredpath)} { + setbits flags 8; # LOAD_WITH_ALTERED_SEARCH_PATH + } + + # LoadLibrary always wants backslashes + set path [file nativename $path] + return [LoadLibraryEx $path $flags] +} + +# Free library opened with load_library +proc twapi::free_library {libh} { + FreeLibrary $libh +} + +# Format message string - will raise exception if insufficient number +# of arguments +proc twapi::_unsafe_format_message {args} { + array set opts [parseargs args { + module.arg + fmtstring.arg + messageid.arg + langid.arg + params.arg + includesystem + ignoreinserts + width.int + } -nulldefault -maxleftover 0] + + set flags 0 + + if {$opts(module) == ""} { + if {$opts(fmtstring) == ""} { + # If neither -module nor -fmtstring specified, message is formatted + # from the system + set opts(module) NULL + setbits flags 0x1000; # FORMAT_MESSAGE_FROM_SYSTEM + } else { + setbits flags 0x400; # FORMAT_MESSAGE_FROM_STRING + if {$opts(includesystem) || $opts(messageid) != "" || $opts(langid) != ""} { + error "Options -includesystem, -messageid and -langid cannot be used with -fmtstring" + } + } + } else { + if {$opts(fmtstring) != ""} { + error "Options -fmtstring and -module cannot be used together" + } + setbits flags 0x800; # FORMAT_MESSAGE_FROM_HMODULE + if {$opts(includesystem)} { + # Also include system in search + setbits flags 0x1000; # FORMAT_MESSAGE_FROM_SYSTEM + } + } + + if {$opts(ignoreinserts)} { + setbits flags 0x200; # FORMAT_MESSAGE_IGNORE_INSERTS + } + + if {$opts(width) > 254} { + error "Invalid value for option -width. Must be -1, 0, or a positive integer less than 255" + } + if {$opts(width) < 0} { + # Negative width means no width restrictions + set opts(width) 255; # 255 -> no restrictions + } + incr flags $opts(width); # Width goes in low byte of flags + + if {$opts(fmtstring) != ""} { + return [FormatMessageFromString $flags $opts(fmtstring) $opts(params)] + } else { + if {![string is integer -strict $opts(messageid)]} { + error "Unspecified or invalid value for -messageid option. Must be an integer value" + } + if {$opts(langid) == ""} { set opts(langid) 0 } + if {![string is integer -strict $opts(langid)]} { + error "Unspecfied or invalid value for -langid option. Must be an integer value" + } + + # Check if $opts(module) is a file or module handle (pointer) + if {[pointer? $opts(module)]} { + return [FormatMessageFromModule $flags $opts(module) \ + $opts(messageid) $opts(langid) $opts(params)] + } else { + set hmod [load_library $opts(module) -datafile] + trap { + set message [FormatMessageFromModule $flags $hmod \ + $opts(messageid) $opts(langid) $opts(params)] + } finally { + free_library $hmod + } + return $message + } + } +} + +# Format message string +proc twapi::format_message {args} { + array set opts [parseargs args { + params.arg + fmtstring.arg + width.int + ignoreinserts + } -ignoreunknown] + + # TBD - document - if no params specified, different from params = {} + + # If a format string is specified, other options do not matter + # except for -width. In that case, we do not call FormatMessage + # at all + if {[info exists opts(fmtstring)]} { + # If -width specifed, call FormatMessage + if {[info exists opts(width)] && $opts(width)} { + set msg [_unsafe_format_message -ignoreinserts -fmtstring $opts(fmtstring) -width $opts(width) {*}$args] + } else { + set msg $opts(fmtstring) + } + } else { + # Not -fmtstring, retrieve from message file + if {[info exists opts(width)]} { + set msg [_unsafe_format_message -ignoreinserts -width $opts(width) {*}$args] + } else { + set msg [_unsafe_format_message -ignoreinserts {*}$args] + } + } + + # If we are told to ignore inserts, all done. Else replace them except + # that if no param list, do not replace placeholder. This is NOT + # the same as empty param list + if {$opts(ignoreinserts) || ![info exists opts(params)]} { + return $msg + } + + # TBD - cache fmtstring -> indices for performance + set placeholder_indices [regexp -indices -all -inline {%(?:.|(?:[1-9][0-9]?(?:![^!]+!)?))} $msg] + + if {[llength $placeholder_indices] == 0} { + # No placeholders. + return $msg + } + + # Use of * in format specifiers will change where the actual parameters + # are positioned + set num_asterisks 0 + set msg2 "" + set prev_end 0 + foreach placeholder $placeholder_indices { + lassign $placeholder start end + # Append the stuff between previous placeholder and this one + append msg2 [string range $msg $prev_end [expr {$start-1}]] + set spec [string range $msg $start+1 $end] + switch -exact -- [string index $spec 0] { + % { append msg2 % } + r { append msg2 \r } + n { append msg2 \n } + t { append msg2 \t } + 0 { + # No-op - %0 means to not add trailing newline + } + default { + if {! [string is integer -strict [string index $spec 0]]} { + # Not a insert parameter. Just append the character + append msg2 $spec + } else { + # Insert parameter + set fmt "" + scan $spec %d%s param_index fmt + # Note params are numbered starting with 1 + incr param_index -1 + # Format spec, if present, is enclosed in !. Get rid of them + set fmt [string trim $fmt "!"] + if {$fmt eq ""} { + # No fmt spec + } else { + # Since everything is a string in Tcl, we happily + # do not have to worry about type. However, the + # format spec could have * specifiers which will + # change the parameter indexing for subsequent + # arguments + incr num_asterisks [expr {[llength [split $fmt *]]-1}] + incr param_index $num_asterisks + } + # TBD - we ignore the actual format type + append msg2 [lindex $opts(params) $param_index] + } + } + } + set prev_end [incr end] + } + append msg2 [string range $msg $prev_end end] + return $msg2 +} + +# Revert to process token. In base package because used across many modules +proc twapi::revert_to_self {{opt ""}} { + RevertToSelf +} + +# For backward compatibility +interp alias {} twapi::expand_environment_strings {} twapi::expand_environment_vars + +proc twapi::_init_security_defs {} { + variable security_defs + + # NOTE : the access definitions for those types that are included here + # have been updated as of Windows 8. + array set security_defs { + + TOKEN_ASSIGN_PRIMARY 0x00000001 + TOKEN_DUPLICATE 0x00000002 + TOKEN_IMPERSONATE 0x00000004 + TOKEN_QUERY 0x00000008 + TOKEN_QUERY_SOURCE 0x00000010 + TOKEN_ADJUST_PRIVILEGES 0x00000020 + TOKEN_ADJUST_GROUPS 0x00000040 + TOKEN_ADJUST_DEFAULT 0x00000080 + TOKEN_ADJUST_SESSIONID 0x00000100 + + TOKEN_ALL_ACCESS_WINNT 0x000F00FF + TOKEN_ALL_ACCESS_WIN2K 0x000F01FF + TOKEN_ALL_ACCESS 0x000F01FF + TOKEN_READ 0x00020008 + TOKEN_WRITE 0x000200E0 + TOKEN_EXECUTE 0x00020000 + + SYSTEM_MANDATORY_LABEL_NO_WRITE_UP 0x1 + SYSTEM_MANDATORY_LABEL_NO_READ_UP 0x2 + SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP 0x4 + + ACL_REVISION 2 + ACL_REVISION_DS 4 + + ACCESS_MAX_MS_V2_ACE_TYPE 0x3 + ACCESS_MAX_MS_V3_ACE_TYPE 0x4 + ACCESS_MAX_MS_V4_ACE_TYPE 0x8 + ACCESS_MAX_MS_V5_ACE_TYPE 0x11 + + STANDARD_RIGHTS_REQUIRED 0x000F0000 + STANDARD_RIGHTS_READ 0x00020000 + STANDARD_RIGHTS_WRITE 0x00020000 + STANDARD_RIGHTS_EXECUTE 0x00020000 + STANDARD_RIGHTS_ALL 0x001F0000 + SPECIFIC_RIGHTS_ALL 0x0000FFFF + + GENERIC_READ 0x80000000 + GENERIC_WRITE 0x40000000 + GENERIC_EXECUTE 0x20000000 + GENERIC_ALL 0x10000000 + + SERVICE_QUERY_CONFIG 0x00000001 + SERVICE_CHANGE_CONFIG 0x00000002 + SERVICE_QUERY_STATUS 0x00000004 + SERVICE_ENUMERATE_DEPENDENTS 0x00000008 + SERVICE_START 0x00000010 + SERVICE_STOP 0x00000020 + SERVICE_PAUSE_CONTINUE 0x00000040 + SERVICE_INTERROGATE 0x00000080 + SERVICE_USER_DEFINED_CONTROL 0x00000100 + SERVICE_ALL_ACCESS 0x000F01FF + + SC_MANAGER_CONNECT 0x00000001 + SC_MANAGER_CREATE_SERVICE 0x00000002 + SC_MANAGER_ENUMERATE_SERVICE 0x00000004 + SC_MANAGER_LOCK 0x00000008 + SC_MANAGER_QUERY_LOCK_STATUS 0x00000010 + SC_MANAGER_MODIFY_BOOT_CONFIG 0x00000020 + SC_MANAGER_ALL_ACCESS 0x000F003F + + KEY_QUERY_VALUE 0x00000001 + KEY_SET_VALUE 0x00000002 + KEY_CREATE_SUB_KEY 0x00000004 + KEY_ENUMERATE_SUB_KEYS 0x00000008 + KEY_NOTIFY 0x00000010 + KEY_CREATE_LINK 0x00000020 + KEY_WOW64_32KEY 0x00000200 + KEY_WOW64_64KEY 0x00000100 + KEY_WOW64_RES 0x00000300 + KEY_READ 0x00020019 + KEY_WRITE 0x00020006 + KEY_EXECUTE 0x00020019 + KEY_ALL_ACCESS 0x000F003F + + POLICY_VIEW_LOCAL_INFORMATION 0x00000001 + POLICY_VIEW_AUDIT_INFORMATION 0x00000002 + POLICY_GET_PRIVATE_INFORMATION 0x00000004 + POLICY_TRUST_ADMIN 0x00000008 + POLICY_CREATE_ACCOUNT 0x00000010 + POLICY_CREATE_SECRET 0x00000020 + POLICY_CREATE_PRIVILEGE 0x00000040 + POLICY_SET_DEFAULT_QUOTA_LIMITS 0x00000080 + POLICY_SET_AUDIT_REQUIREMENTS 0x00000100 + POLICY_AUDIT_LOG_ADMIN 0x00000200 + POLICY_SERVER_ADMIN 0x00000400 + POLICY_LOOKUP_NAMES 0x00000800 + POLICY_NOTIFICATION 0x00001000 + POLICY_READ 0X00020006 + POLICY_WRITE 0X000207F8 + POLICY_EXECUTE 0X00020801 + POLICY_ALL_ACCESS 0X000F0FFF + + DESKTOP_READOBJECTS 0x0001 + DESKTOP_CREATEWINDOW 0x0002 + DESKTOP_CREATEMENU 0x0004 + DESKTOP_HOOKCONTROL 0x0008 + DESKTOP_JOURNALRECORD 0x0010 + DESKTOP_JOURNALPLAYBACK 0x0020 + DESKTOP_ENUMERATE 0x0040 + DESKTOP_WRITEOBJECTS 0x0080 + DESKTOP_SWITCHDESKTOP 0x0100 + + WINSTA_ENUMDESKTOPS 0x0001 + WINSTA_READATTRIBUTES 0x0002 + WINSTA_ACCESSCLIPBOARD 0x0004 + WINSTA_CREATEDESKTOP 0x0008 + WINSTA_WRITEATTRIBUTES 0x0010 + WINSTA_ACCESSGLOBALATOMS 0x0020 + WINSTA_EXITWINDOWS 0x0040 + WINSTA_ENUMERATE 0x0100 + WINSTA_READSCREEN 0x0200 + WINSTA_ALL_ACCESS 0x37f + + PROCESS_TERMINATE 0x0001 + PROCESS_CREATE_THREAD 0x0002 + PROCESS_SET_SESSIONID 0x0004 + PROCESS_VM_OPERATION 0x0008 + PROCESS_VM_READ 0x0010 + PROCESS_VM_WRITE 0x0020 + PROCESS_DUP_HANDLE 0x0040 + PROCESS_CREATE_PROCESS 0x0080 + PROCESS_SET_QUOTA 0x0100 + PROCESS_SET_INFORMATION 0x0200 + PROCESS_QUERY_INFORMATION 0x0400 + PROCESS_SUSPEND_RESUME 0x0800 + + THREAD_TERMINATE 0x00000001 + THREAD_SUSPEND_RESUME 0x00000002 + THREAD_GET_CONTEXT 0x00000008 + THREAD_SET_CONTEXT 0x00000010 + THREAD_SET_INFORMATION 0x00000020 + THREAD_QUERY_INFORMATION 0x00000040 + THREAD_SET_THREAD_TOKEN 0x00000080 + THREAD_IMPERSONATE 0x00000100 + THREAD_DIRECT_IMPERSONATION 0x00000200 + THREAD_SET_LIMITED_INFORMATION 0x00000400 + THREAD_QUERY_LIMITED_INFORMATION 0x00000800 + + EVENT_MODIFY_STATE 0x00000002 + EVENT_ALL_ACCESS 0x001F0003 + + SEMAPHORE_MODIFY_STATE 0x00000002 + SEMAPHORE_ALL_ACCESS 0x001F0003 + + MUTANT_QUERY_STATE 0x00000001 + MUTANT_ALL_ACCESS 0x001F0001 + + MUTEX_MODIFY_STATE 0x00000001 + MUTEX_ALL_ACCESS 0x001F0001 + + TIMER_QUERY_STATE 0x00000001 + TIMER_MODIFY_STATE 0x00000002 + TIMER_ALL_ACCESS 0x001F0003 + + FILE_READ_DATA 0x00000001 + FILE_LIST_DIRECTORY 0x00000001 + FILE_WRITE_DATA 0x00000002 + FILE_ADD_FILE 0x00000002 + FILE_APPEND_DATA 0x00000004 + FILE_ADD_SUBDIRECTORY 0x00000004 + FILE_CREATE_PIPE_INSTANCE 0x00000004 + FILE_READ_EA 0x00000008 + FILE_WRITE_EA 0x00000010 + FILE_EXECUTE 0x00000020 + FILE_TRAVERSE 0x00000020 + FILE_DELETE_CHILD 0x00000040 + FILE_READ_ATTRIBUTES 0x00000080 + FILE_WRITE_ATTRIBUTES 0x00000100 + + FILE_ALL_ACCESS 0x001F01FF + FILE_GENERIC_READ 0x00120089 + FILE_GENERIC_WRITE 0x00120116 + FILE_GENERIC_EXECUTE 0x001200A0 + + DELETE 0x00010000 + READ_CONTROL 0x00020000 + WRITE_DAC 0x00040000 + WRITE_OWNER 0x00080000 + SYNCHRONIZE 0x00100000 + + MAXIMUM_ALLOWED 0x02000000 + + COM_RIGHTS_EXECUTE 1 + COM_RIGHTS_EXECUTE_LOCAL 2 + COM_RIGHTS_EXECUTE_REMOTE 4 + COM_RIGHTS_ACTIVATE_LOCAL 8 + COM_RIGHTS_ACTIVATE_REMOTE 16 + } + + if {[min_os_version 6]} { + array set security_defs { + PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 + PROCESS_ALL_ACCESS 0x001fffff + THREAD_ALL_ACCESS 0x001fffff + } + } else { + array set security_defs { + PROCESS_ALL_ACCESS 0x001f0fff + THREAD_ALL_ACCESS 0x001f03ff + } + } + + # Make next call a no-op + proc _init_security_defs {} {} +} + +# Map a set of access right symbols to a flag. Concatenates +# all the arguments, and then OR's the individual elements. Each +# element may either be a integer or one of the access rights +proc twapi::_access_rights_to_mask {args} { + _init_security_defs + + proc _access_rights_to_mask args { + variable security_defs + set rights 0 + foreach right [concat {*}$args] { + # The mandatory label access rights are not in security_defs + # because we do not want them to mess up the int->name mapping + # for DACL's + set right [dict* { + no_write_up 1 + system_mandatory_label_no_write_up 1 + no_read_up 2 + system_mandatory_label_no_read_up 2 + no_execute_up 4 + system_mandatory_label_no_execute_up 4 + } $right] + if {![string is integer $right]} { + if {[catch {set right $security_defs([string toupper $right])}]} { + error "Invalid access right symbol '$right'" + } + } + set rights [expr {$rights | $right}] + } + return $rights + } + return [_access_rights_to_mask {*}$args] +} + + +# Map an access mask to a set of rights +proc twapi::_access_mask_to_rights {access_mask {type ""}} { + _init_security_defs + + proc _access_mask_to_rights {access_mask {type ""}} { + variable security_defs + + set rights [list ] + + if {$type eq "mandatory_label"} { + if {$access_mask & 1} { + lappend rights system_mandatory_label_no_write_up + } + if {$access_mask & 2} { + lappend rights system_mandatory_label_no_read_up + } + if {$access_mask & 4} { + lappend rights system_mandatory_label_no_execute_up + } + return $rights + } + + # The returned list will include rights that map to multiple bits + # as well as the individual bits. We first add the multiple bits + # and then the individual bits (since we clear individual bits + # after adding) + + # + # Check standard multiple bit masks + # + foreach x {STANDARD_RIGHTS_REQUIRED STANDARD_RIGHTS_READ STANDARD_RIGHTS_WRITE STANDARD_RIGHTS_EXECUTE STANDARD_RIGHTS_ALL SPECIFIC_RIGHTS_ALL} { + if {($security_defs($x) & $access_mask) == $security_defs($x)} { + lappend rights [string tolower $x] + } + } + + # + # Check type specific multiple bit masks. + # + + set type_mask_map { + file {FILE_ALL_ACCESS FILE_GENERIC_READ FILE_GENERIC_WRITE FILE_GENERIC_EXECUTE} + process {PROCESS_ALL_ACCESS} + pipe {FILE_ALL_ACCESS} + policy {POLICY_READ POLICY_WRITE POLICY_EXECUTE POLICY_ALL_ACCESS} + registry {KEY_READ KEY_WRITE KEY_EXECUTE KEY_ALL_ACCESS} + service {SERVICE_ALL_ACCESS} + thread {THREAD_ALL_ACCESS} + token {TOKEN_READ TOKEN_WRITE TOKEN_EXECUTE TOKEN_ALL_ACCESS} + desktop {} + winsta {WINSTA_ALL_ACCESS} + } + if {[dict exists $type_mask_map $type]} { + foreach x [dict get $type_mask_map $type] { + if {($security_defs($x) & $access_mask) == $security_defs($x)} { + lappend rights [string tolower $x] + } + } + } + + # + # OK, now map individual bits + + # First map the common bits + foreach x {DELETE READ_CONTROL WRITE_DAC WRITE_OWNER SYNCHRONIZE} { + if {$security_defs($x) & $access_mask} { + lappend rights [string tolower $x] + resetbits access_mask $security_defs($x) + } + } + + # Then the generic bits + foreach x {GENERIC_READ GENERIC_WRITE GENERIC_EXECUTE GENERIC_ALL} { + if {$security_defs($x) & $access_mask} { + lappend rights [string tolower $x] + resetbits access_mask $security_defs($x) + } + } + + # Then the type specific + set type_mask_map { + file { FILE_READ_DATA FILE_WRITE_DATA FILE_APPEND_DATA + FILE_READ_EA FILE_WRITE_EA FILE_EXECUTE + FILE_DELETE_CHILD FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES } + pipe { FILE_READ_DATA FILE_WRITE_DATA FILE_CREATE_PIPE_INSTANCE + FILE_READ_ATTRIBUTES FILE_WRITE_ATTRIBUTES } + service { SERVICE_QUERY_CONFIG SERVICE_CHANGE_CONFIG + SERVICE_QUERY_STATUS SERVICE_ENUMERATE_DEPENDENTS + SERVICE_START SERVICE_STOP SERVICE_PAUSE_CONTINUE + SERVICE_INTERROGATE SERVICE_USER_DEFINED_CONTROL } + registry { KEY_QUERY_VALUE KEY_SET_VALUE KEY_CREATE_SUB_KEY + KEY_ENUMERATE_SUB_KEYS KEY_NOTIFY KEY_CREATE_LINK + KEY_WOW64_32KEY KEY_WOW64_64KEY KEY_WOW64_RES } + policy { POLICY_VIEW_LOCAL_INFORMATION POLICY_VIEW_AUDIT_INFORMATION + POLICY_GET_PRIVATE_INFORMATION POLICY_TRUST_ADMIN + POLICY_CREATE_ACCOUNT POLICY_CREATE_SECRET + POLICY_CREATE_PRIVILEGE POLICY_SET_DEFAULT_QUOTA_LIMITS + POLICY_SET_AUDIT_REQUIREMENTS POLICY_AUDIT_LOG_ADMIN + POLICY_SERVER_ADMIN POLICY_LOOKUP_NAMES } + process { PROCESS_TERMINATE PROCESS_CREATE_THREAD + PROCESS_SET_SESSIONID PROCESS_VM_OPERATION + PROCESS_VM_READ PROCESS_VM_WRITE PROCESS_DUP_HANDLE + PROCESS_CREATE_PROCESS PROCESS_SET_QUOTA + PROCESS_SET_INFORMATION PROCESS_QUERY_INFORMATION + PROCESS_SUSPEND_RESUME} + thread { THREAD_TERMINATE THREAD_SUSPEND_RESUME + THREAD_GET_CONTEXT THREAD_SET_CONTEXT + THREAD_SET_INFORMATION THREAD_QUERY_INFORMATION + THREAD_SET_THREAD_TOKEN THREAD_IMPERSONATE + THREAD_DIRECT_IMPERSONATION + THREAD_SET_LIMITED_INFORMATION + THREAD_QUERY_LIMITED_INFORMATION } + token { TOKEN_ASSIGN_PRIMARY TOKEN_DUPLICATE TOKEN_IMPERSONATE + TOKEN_QUERY TOKEN_QUERY_SOURCE TOKEN_ADJUST_PRIVILEGES + TOKEN_ADJUST_GROUPS TOKEN_ADJUST_DEFAULT TOKEN_ADJUST_SESSIONID } + desktop { DESKTOP_READOBJECTS DESKTOP_CREATEWINDOW + DESKTOP_CREATEMENU DESKTOP_HOOKCONTROL + DESKTOP_JOURNALRECORD DESKTOP_JOURNALPLAYBACK + DESKTOP_ENUMERATE DESKTOP_WRITEOBJECTS DESKTOP_SWITCHDESKTOP } + windowstation { WINSTA_ENUMDESKTOPS WINSTA_READATTRIBUTES + WINSTA_ACCESSCLIPBOARD WINSTA_CREATEDESKTOP + WINSTA_WRITEATTRIBUTES WINSTA_ACCESSGLOBALATOMS + WINSTA_EXITWINDOWS WINSTA_ENUMERATE WINSTA_READSCREEN } + winsta { WINSTA_ENUMDESKTOPS WINSTA_READATTRIBUTES + WINSTA_ACCESSCLIPBOARD WINSTA_CREATEDESKTOP + WINSTA_WRITEATTRIBUTES WINSTA_ACCESSGLOBALATOMS + WINSTA_EXITWINDOWS WINSTA_ENUMERATE WINSTA_READSCREEN } + com { COM_RIGHTS_EXECUTE COM_RIGHTS_EXECUTE_LOCAL + COM_RIGHTS_EXECUTE_REMOTE COM_RIGHTS_ACTIVATE_LOCAL + COM_RIGHTS_ACTIVATE_REMOTE + } + } + + if {[min_os_version 6]} { + dict lappend type_mask_map process PROCESS_QUERY_LIMITED_INFORMATION + } + + if {[dict exists $type_mask_map $type]} { + foreach x [dict get $type_mask_map $type] { + if {$security_defs($x) & $access_mask} { + lappend rights [string tolower $x] + # Reset the bit so is it not included in unknown bits below + resetbits access_mask $security_defs($x) + } + } + } + + # Finally add left over bits if any + for {set i 0} {$i < 32} {incr i} { + set x [expr {1 << $i}] + if {$access_mask & $x} { + lappend rights [hex32 $x] + } + } + + return $rights + } + + return [_access_mask_to_rights $access_mask $type] +} + +# Map the symbolic CreateDisposition parameter of CreateFile to integer values +proc twapi::_create_disposition_to_code {sym} { + if {[string is integer -strict $sym]} { + return $sym + } + # CREATE_NEW 1 + # CREATE_ALWAYS 2 + # OPEN_EXISTING 3 + # OPEN_ALWAYS 4 + # TRUNCATE_EXISTING 5 + return [dict get { + create_new 1 + create_always 2 + open_existing 3 + open_always 4 + truncate_existing 5} $sym] +} + +# Wrapper around CreateFile +proc twapi::create_file {path args} { + array set opts [parseargs args { + {access.arg {generic_read}} + {share.arg {read write delete}} + {inherit.bool 0} + {secd.arg ""} + {createdisposition.arg open_always} + {flags.int 0} + {templatefile.arg NULL} + } -maxleftover 0] + + set access_mode [_access_rights_to_mask $opts(access)] + set share_mode [_share_mode_to_mask $opts(share)] + set create_disposition [_create_disposition_to_code $opts(createdisposition)] + return [CreateFile $path \ + $access_mode \ + $share_mode \ + [_make_secattr $opts(secd) $opts(inherit)] \ + $create_disposition \ + $opts(flags) \ + $opts(templatefile)] +} + +# Map a set of share mode symbols to a flag. Concatenates +# all the arguments, and then OR's the individual elements. Each +# element may either be a integer or one of the share modes +proc twapi::_share_mode_to_mask {modelist} { + # Values correspond to FILE_SHARE_* defines + return [_parse_symbolic_bitmask $modelist {read 1 write 2 delete 4}] +} + +# Construct a security attributes structure out of a security descriptor +# and inheritance. The command is here because we do not want to +# have to load the twapi_security package for the common case of +# null security attributes. +proc twapi::_make_secattr {secd inherit} { + if {$inherit} { + set sec_attr [list $secd 1] + } else { + if {[llength $secd] == 0} { + # If a security descriptor not specified, keep + # all security attributes as an empty list (ie. NULL) + set sec_attr [list ] + } else { + set sec_attr [list $secd 0] + } + } + return $sec_attr +} + +# Returns the sid, domain and type for an account +proc twapi::lookup_account_name {name args} { + variable _name_to_sid_cache + + # Fast path - no options specified and cached + if {[llength $args] == 0 && [dict exists $_name_to_sid_cache "" $name]} { + return [lindex [dict get $_name_to_sid_cache "" $name] 0] + } + + array set opts [parseargs args \ + [list all \ + sid \ + domain \ + type \ + [list system.arg ""]\ + ]] + + if {! [dict exists $_name_to_sid_cache $opts(system) $name]} { + dict set _name_to_sid_cache $opts(system) $name [LookupAccountName $opts(system) $name] + } + lassign [dict get $_name_to_sid_cache $opts(system) $name] sid domain type + + set result [list ] + if {$opts(all) || $opts(domain)} { + lappend result -domain $domain + } + if {$opts(all) || $opts(type)} { + variable sid_type_names + if {[info exists sid_type_names($type)]} { + lappend result -type $sid_type_names($type) + } else { + # Could be the "logonid" dummy type we added above + lappend result -type $type + } + } + + if {$opts(all) || $opts(sid)} { + lappend result -sid $sid + } + + # If no options specified, only return the sid/name + if {[llength $result] == 0} { + return $sid + } + + return $result +} + + +# Returns the name, domain and type for an account +proc twapi::lookup_account_sid {sid args} { + variable _sid_to_name_cache + + # Fast path - no options specified and cached + if {[llength $args] == 0 && [dict exists $_sid_to_name_cache "" $sid]} { + return [lindex [dict get $_sid_to_name_cache "" $sid] 0] + } + + array set opts [parseargs args \ + [list all \ + name \ + domain \ + type \ + [list system.arg ""]\ + ]] + + if {[dict exists $_sid_to_name_cache $opts(system) $sid]} { + lassign [dict get $_sid_to_name_cache $opts(system) $sid] name domain type + } else { + # Not in cache. Need to look up + + trap { + set data [LookupAccountSid $opts(system) $sid] + lassign $data name domain type + } onerror {TWAPI_WIN32 1332} { + # Win10 resolves this, Win7 does not. Emulate Win10 + if {![string match -nocase "S-1-5-5-*" $sid]} { + rethrow + } + # Name is formed similar to how Win10 does it + set name "LogonSessionId_[string map {- _} [string range $sid 8 end]]" + set domain "NT AUTHORITY" + set type 11 + set data [list $name $domain $type] + } + dict set _sid_to_name_cache $opts(system) $sid $data + } + + + set result [list ] + if {$opts(all) || $opts(domain)} { + lappend result -domain $domain + } + if {$opts(all) || $opts(type)} { + variable sid_type_names + if {[info exists sid_type_names($type)]} { + lappend result -type $sid_type_names($type) + } else { + lappend result -type $type + } + } + + if {$opts(all) || $opts(name)} { + lappend result -name $name + } + + # If no options specified, only return the sid/name + if {[llength $result] == 0} { + return $name + } + + return $result +} + +# Returns the sid for a account - may be given as a SID or name +proc twapi::map_account_to_sid {account args} { + array set opts [parseargs args {system.arg} -nulldefault] + + # Treat empty account as null SID (self) + if {[string length $account] == ""} { + return "" + } + + if {[is_valid_sid_syntax $account]} { + return $account + } else { + return [lookup_account_name $account -system $opts(system)] + } +} + + +# Returns the name for a account - may be given as a SID or name +proc twapi::map_account_to_name {account args} { + array set opts [parseargs args {system.arg} -nulldefault] + + if {[is_valid_sid_syntax $account]} { + return [lookup_account_sid $account -system $opts(system)] + } else { + # Verify whether a valid account by mapping to an sid + if {[catch {map_account_to_sid $account -system $opts(system)}]} { + # As a special case, change LocalSystem to SYSTEM. Some Windows + # API's (such as services) return LocalSystem which cannot be + # resolved by the security functions. This name is really the + # same a the built-in SYSTEM + if {$account == "LocalSystem"} { + return "SYSTEM" + } + error "Unknown account '$account'" + } + return $account + } +} + +# Return the user account for the current process +proc twapi::get_current_user {{format -samcompatible}} { + + set return_sid false + switch -exact -- $format { + -fullyqualifieddn {set format 1} + -samcompatible {set format 2} + -display {set format 3} + -uniqueid {set format 6} + -canonical {set format 7} + -userprincipal {set format 8} + -canonicalex {set format 9} + -serviceprincipal {set format 10} + -dnsdomain {set format 12} + -sid {set format 2 ; set return_sid true} + default { + error "Unknown user name format '$format'" + } + } + + set user [GetUserNameEx $format] + + if {$return_sid} { + return [map_account_to_sid $user] + } else { + return $user + } +} + +# Get a new uuid +proc twapi::new_uuid {{opt ""}} { + if {[string length $opt]} { + if {[string equal $opt "-localok"]} { + set local_ok 1 + } else { + error "Invalid or unknown argument '$opt'" + } + } else { + set local_ok 0 + } + return [UuidCreate $local_ok] +} +proc twapi::nil_uuid {} { + return [UuidCreateNil] +} + +proc twapi::new_guid {} { + return [canonicalize_guid [new_uuid]] +} + +# Get a handle to a LSA policy. TBD - document +proc twapi::get_lsa_policy_handle {args} { + array set opts [parseargs args { + {system.arg ""} + {access.arg policy_read} + } -maxleftover 0] + + set access [_access_rights_to_mask $opts(access)] + return [Twapi_LsaOpenPolicy $opts(system) $access] +} + +# Close a LSA policy handle. TBD - document +proc twapi::close_lsa_policy_handle {h} { + LsaClose $h + return +} + +# Eventlog stuff in the base package + +namespace eval twapi { + # Keep track of event log handles - values are "r" or "w" + variable eventlog_handles + array set eventlog_handles {} +} + +# Open an eventlog for reading or writing +proc twapi::eventlog_open {args} { + variable eventlog_handles + + array set opts [parseargs args { + system.arg + source.arg + file.arg + write + } -nulldefault -maxleftover 0] + if {$opts(source) == ""} { + # Source not specified + if {$opts(file) == ""} { + # No source or file specified, default to current event log + # using executable name as source + set opts(source) [file rootname [file tail [info nameofexecutable]]] + } else { + if {$opts(write)} { + error "Option -file may not be used with -write" + } + } + } else { + # Source explicitly specified + if {$opts(file) != ""} { + error "Option -file may not be used with -source" + } + } + + if {$opts(write)} { + set handle [RegisterEventSource $opts(system) $opts(source)] + set mode write + } else { + if {$opts(source) != ""} { + set handle [OpenEventLog $opts(system) $opts(source)] + } else { + set handle [OpenBackupEventLog $opts(system) $opts(file)] + } + set mode read + } + + set eventlog_handles($handle) $mode + return $handle +} + +# Close an event log opened for writing +proc twapi::eventlog_close {hevl} { + variable eventlog_handles + + if {[_eventlog_valid_handle $hevl read]} { + CloseEventLog $hevl + } else { + DeregisterEventSource $hevl + } + + unset eventlog_handles($hevl) +} + + +# Log an event +proc twapi::eventlog_write {hevl id args} { + _eventlog_valid_handle $hevl write raise + + array set opts [parseargs args { + {type.arg information {success error warning information auditsuccess auditfailure}} + {category.int 1} + loguser + params.arg + data.arg + } -nulldefault] + + + switch -exact -- $opts(type) { + success {set opts(type) 0} + error {set opts(type) 1} + warning {set opts(type) 2} + information {set opts(type) 4} + auditsuccess {set opts(type) 8} + auditfailure {set opts(type) 16} + default {error "Invalid value '$opts(type)' for option -type"} + } + + if {$opts(loguser)} { + set user [get_current_user -sid] + } else { + set user "" + } + + ReportEvent $hevl $opts(type) $opts(category) $id \ + $user $opts(params) $opts(data) +} + + +# Log a message +proc twapi::eventlog_log {message args} { + array set opts [parseargs args { + system.arg + source.arg + {type.arg information} + {category.int 0} + } -nulldefault] + + set hevl [eventlog_open -write -source $opts(source) -system $opts(system)] + + trap { + eventlog_write $hevl 1 -params [list $message] -type $opts(type) -category $opts(category) + } finally { + eventlog_close $hevl + } + return +} + +proc twapi::make_logon_identity {username password domain} { + if {[concealed? $password]} { + return [list $username $domain $password] + } else { + return [list $username $domain [conceal $password]] + } +} + +proc twapi::read_credentials {args} { + # DEPRECATED + array set opts [parseargs args { + target.arg + winerror.int + username.arg + password.arg + persist.bool + {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} + {forceui.bool 0 0x80} + {showsaveoption.bool true} + {expectconfirmation.bool 0 0x20000} + } -maxleftover 0 -nulldefault] + + if {$opts(persist) && ! $opts(expectconfirmation)} { + badargs! "Option -expectconfirmation must be specified as true if -persist is true" + } + + # 0x8 -> CREDUI_FLAGS_EXCLUDE_CERTIFICATES (needed for console) + set flags [expr {0x8 | $opts(forceui) | $opts(expectconfirmation)}] + + if {$opts(persist)} { + if {! $opts(showsaveoption)} { + incr flags 0x1000; # CREDUI_FLAGS_PERSIST + } + } else { + incr flags 0x2; # CREDUI_FLAGS_DO_NOT_PERSIST + if {$opts(showsaveoption)} { + incr flags 0x40; # CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX + } + } + + incr flags $opts(type) + + return [CredUICmdLinePromptForCredentials $opts(target) NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] +} + +proc twapi::credentials_dialog {args} { + # DEPRECATED + array set opts [parseargs args { + target.arg + winerror.int + username.arg + password.arg + persist.bool + {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} + {forceui.bool 0 0x80} + {showsaveoption.bool true} + {expectconfirmation.bool 0 0x20000} + {fillusername.bool 0 0x800} + {filllocaladmins.bool 0 0x4} + {notifyfail.bool 0 0x1} + {passwordonly.bool 0 0x200} + {requirecertificate.bool 0 0x10} + {requiresmartcard.bool 0 0x100} + {validateusername.bool 0 0x400} + {parent.arg NULL} + message.arg + caption.arg + {bitmap.arg NULL} + } -maxleftover 0 -nulldefault] + + if {$opts(persist) && ! $opts(expectconfirmation)} { + badargs! "Option -willconfirm must be specified as true if -persist is true" + } + + set flags [expr { 0x8 | $opts(forceui) | $opts(notifyfail) | $opts(expectconfirmation) | $opts(fillusername) | $opts(filllocaladmins)}] + + if {$opts(persist)} { + if {! $opts(showsaveoption)} { + incr flags 0x1000; # CREDUI_FLAGS_PERSIST + } + } else { + incr flags 0x2; # CREDUI_FLAGS_DO_NOT_PERSIST + if {$opts(showsaveoption)} { + incr flags 0x40; # CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX + } + } + + incr flags $opts(type) + + return [CredUIPromptForCredentials [list $opts(parent) $opts(message) $opts(caption) $opts(bitmap)] $opts(target) NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] +} + +proc twapi::confirm_credentials {target valid} { + # DEPRECATED + return [CredUIConfirmCredentials $target $valid] +} + + +proc twapi::_make_cred_persist_flags {persist showsave} { + # Use cases - + # (1) credentials to be persisted WITHOUT showing save option to user + # (2) credentials to be persisted AFTER showing save option to user + # (3) credentials NOT to be persisted, user not shown save option + # (4) credentials NOT to be persisted, but user shown save option + # In case (4), caller has to decide what to do with the credentials if + # user selects to save (e.g. save elsewhere) + # If credentials are to be persisted, caller MUST call cred_confirm later + + if {$persist} { + # Note CREDUI_FLAGS_EXPECT_CONFIRMATION (0x20000) must be specified + # whenever CREDUI_FLAGS_DO_NOT_PERSIST is not specified + if {$showsave} { + # (2) CREDUI_FLAGS_EXPECT_CONFIRMATION + return 0x20000 + } else { + # (1) CREDUI_FLAGS_PERSIST | CREDUI_FLAGS_EXPECT_CONFIRMATION + return 0x21000 + } + } else { + if {$showsave} { + # (4) CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX + return 0x42 + } else { + # (3) CREDUI_FLAGS_DO_NOT_PERSIST + return 0x02 + } + } +} + +proc twapi::cred_prompt_console {target args} { + # Not documented because Windows seems to ignore on Win10 at least - + # -password, -winerror + array set opts [parseargs args { + {forceui.bool 0 0x80} + password.arg + persist.bool + {showsaveoption.bool 0} + {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} + username.arg + winerror.int + } -maxleftover 0 -nulldefault] + + if {$target eq ""} { + error "Target must not be an empty string." + } + + if {$opts(forceui) && $opts(type) != 0x40000} { + error "The -forceui option can only be set if -type is \"generic\"." + } + + if {$opts(type) == 0x80000 && $opts(username) eq ""} { + # CredUIPromptForCredentials crashes + error "The -username option must not be an empty string if -type is \"runas\"." + } + + set flags [_make_cred_persist_flags $opts(persist) $opts(showsaveoption)] + + # 0x8 -> CREDUI_FLAGS_EXCLUDE_CERTIFICATES (needed for console) + set flags [expr {0x8 | $flags | $opts(type) | $opts(forceui)}] + + return [CredUICmdLinePromptForCredentials $target NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] +} + +proc twapi::cred_prompt_gui {target args} { + # Not documented because Windows seems to ignore on Win10 at least - + # -passwordonly, -notifyfail, -winerror + array set opts [parseargs args { + {bitmap.arg NULL} + caption.arg + {excludecertificates.bool 0 0x8} + {filllocaladmins.bool 0 0x4} + {completeusername.bool 0 0x800} + {forceui.bool 0 0x80} + {keepusername.bool 0 0x100000} + message.arg + {notifyfail.bool 0 0x1} + {parent.arg NULL} + password.arg + {passwordonly.bool 0 0x200} + persist.bool + {requirecertificate.bool 0 0x10} + {requiresmartcard.bool 0 0x100} + {showsaveoption.bool 0} + {type.sym generic {domain 0 generic 0x40000 runas 0x80000}} + username.arg + {validateusername.bool 0 0x400} + winerror.int + } -maxleftover 0 -nulldefault] + + if {$target eq ""} { + error "Target must not be an empty string." + } + + if {$opts(forceui) && $opts(type) != 0x40000} { + error "The -forceui option can only be set if -type is \"generic\"." + } + + if {$opts(type) == 0x80000 && $opts(username) eq ""} { + # CredUIPromptForCredentials crashes + error "The -username option must not be an empty string if -type is \"runas\"." + } + + set flags [_make_cred_persist_flags $opts(persist) $opts(showsaveoption)] + set flags [expr { + $flags | + $opts(excludecertificates) | + $opts(filllocaladmins) | + $opts(completeusername) | + $opts(forceui) | + $opts(keepusername) | + $opts(notifyfail) | + $opts(passwordonly) | + $opts(requirecertificate) | + $opts(requiresmartcard) | + $opts(type) | + $opts(validateusername) + }] + + return [CredUIPromptForCredentials [list $opts(parent) $opts(message) $opts(caption) $opts(bitmap)] $target NULL $opts(winerror) $opts(username) $opts(password) $opts(persist) $flags] +} + +proc twapi::cred_confirm {target valid} { + return [CredUIConfirmCredentials $target $valid] +} + +# Validate a handle for a mode. Always raises error if handle is invalid +# If handle valid but not for that mode, will raise error iff $raise_error +# is non-empty. Returns 1 if valid, 0 otherwise +proc twapi::_eventlog_valid_handle {hevl mode {raise_error ""}} { + variable eventlog_handles + if {![info exists eventlog_handles($hevl)]} { + error "Invalid event log handle '$hevl'" + } + + if {[string compare $eventlog_handles($hevl) $mode]} { + if {$raise_error != ""} { + error "Eventlog handle '$hevl' not valid for $mode" + } + return 0 + } else { + return 1 + } +} + +### Common disk related + +# Map bit mask to list of drive letters +proc twapi::_drivemask_to_drivelist {drivebits} { + set drives [list ] + set i 0 + foreach drive {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} { + if {$drivebits == 0} break + set drivemask [expr {1 << $i}] + if {[expr {$drivebits & $drivemask}]} { + lappend drives $drive: + set drivebits [expr {$drivebits & ~ $drivemask}] + } + incr i + } + return $drives +} + +### Type casts +proc twapi::tclcast {type val} { + # Only permit these because wideInt, for example, cannot be reliably + # converted -> it can return an int instead. + set types {"" empty null bstr int boolean double string list dict} + if {$type ni $types} { + badargs! "Bad cast to \"$type\". Must be one of: $types" + } + return [Twapi_InternalCast $type $val] +} + +if {[info commands ::lmap] eq "::lmap"} { + proc twapi::safearray {type l} { + set type [dict! { + variant "" + boolean boolean + bool boolean + int int + i4 int + double double + r8 double + string string + bstr string + } $type] + return [lmap val $l {tclcast $type $val}] + } +} else { + proc twapi::safearray {type l} { + set type [dict! { + variant "" + boolean boolean + bool boolean + int int + i4 int + double double + r8 double + string string + bstr string + } $type] + set l2 {} + foreach val $l { + lappend l2 [tclcast $type $val] + } + return $l2 + } +} + +namespace eval twapi::recordarray {} + +proc twapi::recordarray::size {ra} { + return [llength [lindex $ra 1]] +} + +proc twapi::recordarray::fields {ra} { + return [lindex $ra 0] +} + +proc twapi::recordarray::index {ra row args} { + set r [lindex $ra 1 $row] + if {[llength $r] == 0} { + return $r + } + ::twapi::parseargs args { + {format.arg list {list dict}} + slice.arg + } -setvars -maxleftover 0 + + set fields [lindex $ra 0] + if {[info exists slice]} { + set new_fields {} + set new_r {} + foreach field $slice { + set i [twapi::enum $fields $field] + lappend new_r [lindex $r $i] + lappend new_fields [lindex $fields $i] + } + set r $new_r + set fields $new_fields + } + + if {$format eq "list"} { + return $r + } else { + return [::twapi::twine $fields $r] + } +} + +proc twapi::recordarray::range {ra low high} { + return [list [lindex $ra 0] [lrange [lindex $ra 1] $low $high]] +} + +proc twapi::recordarray::column {ra field args} { + # TBD - time to see if a script loop would be faster + ::twapi::parseargs args { + filter.arg + } -nulldefault -maxleftover 0 -setvars + _recordarray -slice [list $field] -filter $filter -format flat $ra +} + +proc twapi::recordarray::cell {ra row field} { + return [lindex [lindex $ra 1 $row] [twapi::enum [lindex $ra 0] $field]] +} + +proc twapi::recordarray::get {ra args} { + ::twapi::parseargs args { + {format.arg list {list dict flat}} + key.arg + } -ignoreunknown -setvars + + # format & key are options just to stop them flowing down to _recordarray + # We do not pass it in + + return [_recordarray {*}$args $ra] +} + +proc twapi::recordarray::getlist {ra args} { + # key is an option just to stop in flowing down to _recordarray + # We do not pass it in + + if {[llength $args] == 0} { + return [lindex $ra 1] + } + + ::twapi::parseargs args { + {format.arg list {list dict flat}} + key.arg + } -ignoreunknown -setvars + + + return [_recordarray {*}$args -format $format $ra] +} + +proc twapi::recordarray::getdict {ra args} { + ::twapi::parseargs args { + {format.arg list {list dict}} + key.arg + } -ignoreunknown -setvars + + if {![info exists key]} { + set key [lindex $ra 0 0] + } + + # Note _recordarray has different (putting it politely) semantics + # of how -format and -key option are handled so the below might + # look a bit strange in that we pass -format as list and get + # back a dict + return [_recordarray {*}$args -format $format -key $key $ra] +} + +proc twapi::recordarray::iterate {arrayvarname ra args} { + + if {[llength $args] == 0} { + badargs! "No script supplied" + } + + set body [lindex $args end] + set args [lrange $args 0 end-1] + + upvar 1 $arrayvarname var + + # TBD - Can this be optimized by prepending a ::foreach to body + # and executing that in uplevel 1 ? + + foreach rec [getlist $ra {*}$args -format dict] { + array set var $rec + set code [catch {uplevel 1 $body} result] + switch -exact -- $code { + 0 {} + 1 { + return -errorinfo $::errorInfo -errorcode $::errorCode -code error $result + } + 3 { + return; # break + } + 4 { + # continue + } + default { + return -code $code $result + } + } + } + return +} + +proc twapi::recordarray::rename {ra renames} { + set new_fields {} + foreach field [lindex $ra 0] { + if {[dict exists $renames $field]} { + lappend new_fields [dict get $renames $field] + } else { + lappend new_fields $field + } + } + return [list $new_fields [lindex $ra 1]] +} + +proc twapi::recordarray::concat {args} { + if {[llength $args] == 0} { + return {} + } + set args [lassign $args ra] + set fields [lindex $ra 0] + set values [list [lindex $ra 1]] + set width [llength $fields] + foreach ra $args { + foreach fld1 $fields fld2 [lindex $ra 0] { + if {$fld1 ne $fld2} { + twapi::badargs! "Attempt to concat record arrays with different fields ([join $fields ,] versus [join [lindex $ra 0] ,])" + } + } + lappend values [lindex $ra 1] + } + + return [list $fields [::twapi::lconcat {*}$values]] +} + +namespace eval twapi::recordarray { + namespace export cell column concat fields get getdict getlist index iterate range rename size + namespace ensemble create +} + +proc twapi::_parse_ctype {def parse_mode} { + variable _struct_defs + + # parse_mode is "struct", "param" or "function" + + if {![regexp -expanded { + ^ + \s* + (.+[^[:alnum:]_]) # type + ([[:alnum:]_]+) # name + \s* + (\[.+\])? # array size + \s*$ + } $def -> type name array]} { + error "Invalid C type definition $def" + } + + set child {} + switch -regexp -matchvar matchvar -- [string trim $type] { + {^void$} { + if {$parse_mode ne "function"} { + error "Type void cannot be used for structs and parameters." + } + set type void + } + {^char$} {set type i1} + {^BYTE$} - + {^unsigned char$} {set type ui1} + {^short$} {set type i2} + {^WORD$} - + {^unsigned\s+short$} {set type ui2} + {^BOOLEAN$} {set type bool} + {^LONG$} - + {^int$} {set type i4} + {^UINT$} - + {^ULONG$} - + {^DWORD$} - + {^unsigned\s+int$} {set type ui4} + {^__int64$} {set type i8} + {^unsigned\s+__int64$} {set type ui8} + {^double$} {set type r8} + {^float$} {set type r4} + {^LPCSTR$} - + {^LPSTR$} - + {^char\s*\*$} {set type lpstr} + {^LPCWSTR$} - + {^LPWSTR$} - + {^WCHAR\s*\*$} {set type lpwstr} + {^HANDLE$} {set type handle} + {^PSID$} {set type psid} + {^struct\s+([[:alnum:]_]+)$} { + if {$parse_mode ne "struct"} { + error "Structure types not allowed for parameters and return values." + } + # Embedded struct. It should be defined already. Calling + # it with no args returns its definition but doing that + # to retrieve the definition could be a security hole + # (could be passed any Tcl command!) if unwary apps + # pass in input from unknown sources. So we explicitly + # remember definitions instead. + set child_name [lindex $matchvar 1] + if {![info exists _struct_defs($child_name)]} { + error "Unknown struct $child_name" + } + set child $_struct_defs($child_name) + set type struct + } + default {error "Unknown type $type"} + } + set count 0 + if {$array ne ""} { + set count [string trim [string range $array 1 end-1]] + if {![string is integer -strict $count]} { + error "Non-integer array size" + } + if {$parse_mode ne "struct"} { + error "Arrays not allowed for parameters and return values." + } + } + + if {[string equal -nocase $name "cbSize"] && + $type in {i4 ui4} && $count == 0} { + set type cbsize + } + + return [list $name $type $count $child] +} + +proc twapi::_parse_cproto {s} { + variable _struct_defs + + # Get rid of comments + regsub -all {(/\*.* \*/){1,1}?} $s {} s + regsub -line -all {//.*$} $s { } s + + if {![regexp -expanded { + ^ + \s* + (?:(_cdecl|_stdcall)\s+)? + ([[:alnum:]_][[:space:][:alnum:]_]*) # Function type and name + \s* + \( # Beginning of parameters + ([^\)]*) # Parameter definition string + \) # End of parameters + \s*$ # End of prototype + } $s -> callconv fntypeandname paramstr]} { + error "Invalid C prototype \"$s\"" + } + + regsub -all {\s+} $fntypeandname " " + set fntype [_parse_ctype $fntypeandname function] + set params {} + foreach def [split $paramstr ","] { + lappend params [_parse_ctype $def param] + } + + return [list $callconv $fntype [lindex $fntype 0] $params] +} + +# Return a suitable cstruct definition based on a C definition +proc twapi::struct {struct_name s} { + variable _struct_defs + + if {0} { + TBD - Commented out because nested structs do not currently + handle namespaces. However this means structs are effectively + global even if the corresponding command is not. + set struct_name [callerns $struct_name] + } + + regsub -all {(/\*.* \*/){1,1}?} $s {} s + regsub -line -all {//.*$} $s { } s + set l {} + foreach def [split $s ";"] { + set def [string trim $def] + if {$def eq ""} continue + lappend l [_parse_ctype $def struct] + } + + set proc_body [format { + set def %s + if {[llength $args] == 0} { + return $def + } else { + return [list $def $args] + } + } [list $l]] + uplevel 1 [list proc $struct_name args $proc_body] + set _struct_defs($struct_name) $l + return +} + + +proc twapi::ffi_load {path} { + variable _ffi_paths + variable _ffi_handles + + # Note we do NOT normalize path as we leave it to the OS to do so. + # We also do not canonicalize it (e.g. all lower case). + # This means there may be multiple handles for a single shared lib + # but that's ok. + + if {[dict exists $_ffi_paths $path]} { + set h [dict get $_ffi_paths $path] + if {![dict exists $_ffi_handles $h]} { + error "Internal error: Handle $h not found in FFI table." + } + dict with _ffi_handles $h { + if {$Path ne $path} { + error "Internal error: Handle $h not assigned to $path" + } + incr NRefs + } + } else { + set h [load_library $path] + dict set _ffi_paths $path $h + dict set _ffi_handles $h Path $path + dict set _ffi_handles $h NRefs 1 + } + return $h +} + +proc twapi::ffi_unload {h} { + variable _ffi_handles + variable _ffi_paths + + if {![dict exists $_ffi_handles $h]} { + error "FFI handle $h does not exist." + } + + dict with _ffi_handles $h { + if {[incr NRefs -1] <= 0} { + dict unset _ffi_paths $Path + dict unset _ffi_handles $h + } + } + + return +} + +proc twapi::ffi_cfuncs {dllh cprotos {ns ::}} { + variable _ffi_handles + + if {![dict exists $_ffi_handles $dllh]} { + # error "Unknown FFI handle \"$dllh\"." + } + + set l {} + foreach cproto [split $cprotos ";"] { + set cproto [string trim $cproto] + if {$cproto eq ""} continue + lappend l [_parse_cproto $cproto] + } + set cprotos $l + + set def { + proc %NAME% {%PARAMNAMES%} { + if {![dict exists $%TWAPINS%::_ffi_handles %DLLH%]} { + error "Attempt to call function in unloaded library." + } + %TWAPINS%::%CALL% %FNADDR% %FNTYPE% %PARAMS% [list %PARAMREFS%] + } + } + + if {$::tcl_platform(pointerSize) == 8} { + # Win64 has single calling convention + set callmap {"" ffi_call _cdecl ffi_call _stdcall ffi_call} + } else { + set callmap {"" ffi_call _cdecl ffi_call _stdcall ffi_stdcall} + } + + foreach cproto $cprotos { + lassign $cproto callconv fntype fnname params + set call [dict get $callmap $callconv] + + set fnaddr [GetProcAddress $dllh $fnname] + if {[pointer_null? $fnaddr]} { + error "Entry point $fnname not found in shared library." + } + set paramnames {} + set paramrefs {} + foreach arg $params { + set name [lindex $arg 0] + lappend paramnames $name + lappend paramrefs \$$name + } + + # Note that fntype is doubly listified because the C ffi expects + # it in same format as params, ie. a list of type definitions + # _parse_cproto however returns it as a single type definition + append defs [string map [list \ + %CALL% $call \ + %DLLH% [list $dllh] \ + %NAME% ${ns}::$fnname \ + %PARAMNAMES% [join $paramnames { }] \ + %PARAMREFS% [join $paramrefs { }] \ + %TWAPINS% [namespace current] \ + %FNADDR% [list $fnaddr] \ + %FNTYPE% [list [list $fntype]] \ + %PARAMS% [list $params]] \ + $def] \n + } + + uplevel 1 $defs +} + + +if {[twapi::min_os_version 6]} { + twapi::ffi_cfuncs [twapi::ffi_load kernel32.dll] { + UINT GetErrorMode(); + UINT SetErrorMode(UINT mode); + } ::twapi +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/clipboard.tcl b/src/vendorlib_tcl8/twapi-5.0b1/clipboard.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/clipboard.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/clipboard.tcl index 9fee98f8..d39038fa 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/clipboard.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/clipboard.tcl @@ -1,254 +1,254 @@ -# -# Copyright (c) 2004, 2008 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Clipboard related commands - -namespace eval twapi {} - -# Open the clipboard -# TBD - why no mechanism to pass window handle to OpenClipboard? -proc twapi::open_clipboard {} { - OpenClipboard 0 -} - -# Close the clipboard -proc twapi::close_clipboard {} { - catch {CloseClipboard} - return -} - -# Empty the clipboard -proc twapi::empty_clipboard {} { - EmptyClipboard -} - -proc twapi::_read_clipboard {fmt} { - # Always catch errors and close clipboard before passing exception on - # Also ensure memory unlocked - trap { - set h [GetClipboardData $fmt] - set p [GlobalLock $h] - set data [Twapi_ReadMemory 1 $p 0 [GlobalSize $h]] - } onerror {} { - catch {close_clipboard} - rethrow - } finally { - # If p exists, then we must have locked the handle - if {[info exists p]} { - GlobalUnlock $h - } - } - return $data -} - -proc twapi::read_clipboard {fmt} { - trap { - set data [_read_clipboard $fmt] - } onerror {TWAPI_WIN32 1418} { - # Caller did not have clipboard open. Do it on its behalf - open_clipboard - trap { - set data [_read_clipboard $fmt] - } finally { - catch {close_clipboard} - } - } - return $data -} - -# Read text data from the clipboard -proc twapi::read_clipboard_text {args} { - array set opts [parseargs args { - {raw.bool 0} - }] - - set bin [read_clipboard 13]; # 13 -> Unicode - # Decode Unicode and discard trailing nulls - set data [string trimright [encoding convertfrom unicode $bin] \0] - if {! $opts(raw)} { - set data [string map {"\r\n" "\n"} $data] - } - - return $data -} - -proc twapi::_write_clipboard {fmt data} { - # Always catch errors and close - # clipboard before passing exception on - trap { - # For byte arrays, string length does return correct size - # (DO NOT USE string bytelength - see Tcl docs!) - set len [string length $data] - - # Allocate global memory - set mem_h [GlobalAlloc 2 $len] - set mem_p [GlobalLock $mem_h] - - Twapi_WriteMemory 1 $mem_p 0 $len $data - - # The rest of this code just to ensure we do not free - # memory beyond this point irrespective of error/success - set h $mem_h - unset mem_p mem_h - GlobalUnlock $h - SetClipboardData $fmt $h - } onerror {} { - catch close_clipboard - rethrow - } finally { - if {[info exists mem_p]} { - GlobalUnlock $mem_h - } - if {[info exists mem_h]} { - GlobalFree $mem_h - } - } - return -} - -proc twapi::write_clipboard {fmt data} { - trap { - _write_clipboard $fmt $data - } onerror {TWAPI_WIN32 1418} { - # Caller did not have clipboard open. Do it on its behalf - open_clipboard - empty_clipboard - trap { - _write_clipboard $fmt $data - } finally { - catch close_clipboard - } - } - return -} - -# Write text to the clipboard -proc twapi::write_clipboard_text {data args} { - array set opts [parseargs args { - {raw.bool 0} - }] - - # Convert \n to \r\n leaving existing \r\n alone - if {! $opts(raw)} { - set data [regsub -all {(^|[^\r])\n} $data[set data ""] \\1\r\n] - } - append data \0 - write_clipboard 13 [encoding convertto unicode $data]; # 13 -> Unicode - return -} - -# Get current clipboard formats -proc twapi::get_clipboard_formats {} { - return [Twapi_EnumClipboardFormats] -} - -# Get registered clipboard format name. Clipboard does not have to be open -proc twapi::get_registered_clipboard_format_name {fmt} { - return [GetClipboardFormatName $fmt] -} - -# Register a clipboard format -proc twapi::register_clipboard_format {fmt_name} { - RegisterClipboardFormat $fmt_name -} - -# Returns 1/0 depending on whether a format is on the clipboard. Clipboard -# does not have to be open -proc twapi::clipboard_format_available {fmt} { - return [IsClipboardFormatAvailable $fmt] -} - -proc twapi::read_clipboard_paths {} { - set bin [read_clipboard 15] - # Extract the DROPFILES header - if {[binary scan $bin iiiii offset - - - unicode] != 5} { - error "Invalid or unsupported clipboard CF_DROP data." - } - # Sanity check - if {$offset >= [string length $bin]} { - error "Truncated clipboard data." - } - if {$unicode} { - set paths [encoding convertfrom unicode [string range $bin $offset end]] - } else { - set paths [encoding convertfrom ascii [string range $bin $offset end]] - } - set ret {} - foreach path [split $paths \0] { - if {[string length $path] == 0} break; # Empty string -> end of list - lappend ret [file join $path] - } - return $ret -} - -proc twapi::write_clipboard_paths {paths} { - # The header for a DROPFILES path list in hex - set fheader "1400000000000000000000000000000001000000" - set bin [binary format H* $fheader] - foreach path $paths { - # Note explicit \0 so the encoded binary includes the null terminator - append bin [encoding convertto unicode "[file nativename [file normalize $path]]\0"] - } - # A Unicode null char to terminate the list of paths - append bin [encoding convertto unicode \0] - write_clipboard 15 $bin -} - -# Start monitoring of the clipboard -proc twapi::_clipboard_handler {} { - variable _clipboard_monitors - - if {![info exists _clipboard_monitors] || - [llength $_clipboard_monitors] == 0} { - return; # Not an error, could have deleted while already queued - } - - foreach {id script} $_clipboard_monitors { - set code [catch {uplevel #0 $script} msg] - if {$code == 1} { - # Error - put in background but we do not abort - after 0 [list error $msg $::errorInfo $::errorCode] - } - } - return -} - -proc twapi::start_clipboard_monitor {script} { - variable _clipboard_monitors - - set id "clip#[TwapiId]" - if {![info exists _clipboard_monitors] || - [llength $_clipboard_monitors] == 0} { - # No clipboard monitoring in progress. Start it - Twapi_ClipboardMonitorStart - } - - lappend _clipboard_monitors $id $script - return $id -} - - - -# Stop monitoring of the clipboard -proc twapi::stop_clipboard_monitor {clipid} { - variable _clipboard_monitors - - if {![info exists _clipboard_monitors]} { - return; # Should we raise an error instead? - } - - set new_monitors {} - foreach {id script} $_clipboard_monitors { - if {$id ne $clipid} { - lappend new_monitors $id $script - } - } - - set _clipboard_monitors $new_monitors - if {[llength $_clipboard_monitors] == 0} { - Twapi_ClipboardMonitorStop - } -} +# +# Copyright (c) 2004, 2008 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Clipboard related commands + +namespace eval twapi {} + +# Open the clipboard +# TBD - why no mechanism to pass window handle to OpenClipboard? +proc twapi::open_clipboard {} { + OpenClipboard 0 +} + +# Close the clipboard +proc twapi::close_clipboard {} { + catch {CloseClipboard} + return +} + +# Empty the clipboard +proc twapi::empty_clipboard {} { + EmptyClipboard +} + +proc twapi::_read_clipboard {fmt} { + # Always catch errors and close clipboard before passing exception on + # Also ensure memory unlocked + trap { + set h [GetClipboardData $fmt] + set p [GlobalLock $h] + set data [Twapi_ReadMemory 1 $p 0 [GlobalSize $h]] + } onerror {} { + catch {close_clipboard} + rethrow + } finally { + # If p exists, then we must have locked the handle + if {[info exists p]} { + GlobalUnlock $h + } + } + return $data +} + +proc twapi::read_clipboard {fmt} { + trap { + set data [_read_clipboard $fmt] + } onerror {TWAPI_WIN32 1418} { + # Caller did not have clipboard open. Do it on its behalf + open_clipboard + trap { + set data [_read_clipboard $fmt] + } finally { + catch {close_clipboard} + } + } + return $data +} + +# Read text data from the clipboard +proc twapi::read_clipboard_text {args} { + array set opts [parseargs args { + {raw.bool 0} + }] + + set bin [read_clipboard 13]; # 13 -> Unicode + # Decode Unicode and discard trailing nulls + set data [string trimright [encoding convertfrom unicode $bin] \0] + if {! $opts(raw)} { + set data [string map {"\r\n" "\n"} $data] + } + + return $data +} + +proc twapi::_write_clipboard {fmt data} { + # Always catch errors and close + # clipboard before passing exception on + trap { + # For byte arrays, string length does return correct size + # (DO NOT USE string bytelength - see Tcl docs!) + set len [string length $data] + + # Allocate global memory + set mem_h [GlobalAlloc 2 $len] + set mem_p [GlobalLock $mem_h] + + Twapi_WriteMemory 1 $mem_p 0 $len $data + + # The rest of this code just to ensure we do not free + # memory beyond this point irrespective of error/success + set h $mem_h + unset mem_p mem_h + GlobalUnlock $h + SetClipboardData $fmt $h + } onerror {} { + catch close_clipboard + rethrow + } finally { + if {[info exists mem_p]} { + GlobalUnlock $mem_h + } + if {[info exists mem_h]} { + GlobalFree $mem_h + } + } + return +} + +proc twapi::write_clipboard {fmt data} { + trap { + _write_clipboard $fmt $data + } onerror {TWAPI_WIN32 1418} { + # Caller did not have clipboard open. Do it on its behalf + open_clipboard + empty_clipboard + trap { + _write_clipboard $fmt $data + } finally { + catch close_clipboard + } + } + return +} + +# Write text to the clipboard +proc twapi::write_clipboard_text {data args} { + array set opts [parseargs args { + {raw.bool 0} + }] + + # Convert \n to \r\n leaving existing \r\n alone + if {! $opts(raw)} { + set data [regsub -all {(^|[^\r])\n} $data[set data ""] \\1\r\n] + } + append data \0 + write_clipboard 13 [encoding convertto unicode $data]; # 13 -> Unicode + return +} + +# Get current clipboard formats +proc twapi::get_clipboard_formats {} { + return [Twapi_EnumClipboardFormats] +} + +# Get registered clipboard format name. Clipboard does not have to be open +proc twapi::get_registered_clipboard_format_name {fmt} { + return [GetClipboardFormatName $fmt] +} + +# Register a clipboard format +proc twapi::register_clipboard_format {fmt_name} { + RegisterClipboardFormat $fmt_name +} + +# Returns 1/0 depending on whether a format is on the clipboard. Clipboard +# does not have to be open +proc twapi::clipboard_format_available {fmt} { + return [IsClipboardFormatAvailable $fmt] +} + +proc twapi::read_clipboard_paths {} { + set bin [read_clipboard 15] + # Extract the DROPFILES header + if {[binary scan $bin iiiii offset - - - unicode] != 5} { + error "Invalid or unsupported clipboard CF_DROP data." + } + # Sanity check + if {$offset >= [string length $bin]} { + error "Truncated clipboard data." + } + if {$unicode} { + set paths [encoding convertfrom unicode [string range $bin $offset end]] + } else { + set paths [encoding convertfrom ascii [string range $bin $offset end]] + } + set ret {} + foreach path [split $paths \0] { + if {[string length $path] == 0} break; # Empty string -> end of list + lappend ret [file join $path] + } + return $ret +} + +proc twapi::write_clipboard_paths {paths} { + # The header for a DROPFILES path list in hex + set fheader "1400000000000000000000000000000001000000" + set bin [binary format H* $fheader] + foreach path $paths { + # Note explicit \0 so the encoded binary includes the null terminator + append bin [encoding convertto unicode "[file nativename [file normalize $path]]\0"] + } + # A Unicode null char to terminate the list of paths + append bin [encoding convertto unicode \0] + write_clipboard 15 $bin +} + +# Start monitoring of the clipboard +proc twapi::_clipboard_handler {} { + variable _clipboard_monitors + + if {![info exists _clipboard_monitors] || + [llength $_clipboard_monitors] == 0} { + return; # Not an error, could have deleted while already queued + } + + foreach {id script} $_clipboard_monitors { + set code [catch {uplevel #0 $script} msg] + if {$code == 1} { + # Error - put in background but we do not abort + after 0 [list error $msg $::errorInfo $::errorCode] + } + } + return +} + +proc twapi::start_clipboard_monitor {script} { + variable _clipboard_monitors + + set id "clip#[TwapiId]" + if {![info exists _clipboard_monitors] || + [llength $_clipboard_monitors] == 0} { + # No clipboard monitoring in progress. Start it + Twapi_ClipboardMonitorStart + } + + lappend _clipboard_monitors $id $script + return $id +} + + + +# Stop monitoring of the clipboard +proc twapi::stop_clipboard_monitor {clipid} { + variable _clipboard_monitors + + if {![info exists _clipboard_monitors]} { + return; # Should we raise an error instead? + } + + set new_monitors {} + foreach {id script} $_clipboard_monitors { + if {$id ne $clipid} { + lappend new_monitors $id $script + } + } + + set _clipboard_monitors $new_monitors + if {[llength $_clipboard_monitors] == 0} { + Twapi_ClipboardMonitorStop + } +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/com.tcl b/src/vendorlib_tcl8/twapi-5.0b1/com.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/com.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/com.tcl index 128a3458..4c5f7bd4 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/com.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/com.tcl @@ -1,4238 +1,4238 @@ -# -# Copyright (c) 2006-2018 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# TBD - tests comobj? works with derived classes of Automation - -# TBD - object identity comparison -# - see http://blogs.msdn.com/ericlippert/archive/2005/04/26/412199.aspx -# TBD - we seem to resolve UDT's every time a COM method is actually invoked. -# Optimize by doing it when prototype is stored or only the first time it -# is called. -# TBD - optimize by caching UDT's within a type library when the library -# is read. - -# TBD - optimize comobj unknown by caching previously resolved names -# - - -namespace eval twapi { - # Maps TYPEKIND data values to symbols - variable _typekind_map - array set _typekind_map { - 0 enum - 1 record - 2 module - 3 interface - 4 dispatch - 5 coclass - 6 alias - 7 union - } - - # Cache of Interface names - IID mappings - variable _name_to_iid_cache - array set _name_to_iid_cache { - iunknown {{00000000-0000-0000-C000-000000000046}} - idispatch {{00020400-0000-0000-C000-000000000046}} - idispatchex {{A6EF9860-C720-11D0-9337-00A0C90DCAA9}} - itypeinfo {{00020401-0000-0000-C000-000000000046}} - itypecomp {{00020403-0000-0000-C000-000000000046}} - ienumvariant {{00020404-0000-0000-C000-000000000046}} - iprovideclassinfo {{B196B283-BAB4-101A-B69C-00AA00341D07}} - - ipersist {{0000010c-0000-0000-C000-000000000046}} - ipersistfile {{0000010b-0000-0000-C000-000000000046}} - - iprovidetaskpage {{4086658a-cbbb-11cf-b604-00c04fd8d565}} - itasktrigger {{148BD52B-A2AB-11CE-B11F-00AA00530503}} - ischeduleworkitem {{a6b952f0-a4b1-11d0-997d-00aa006887ec}} - itask {{148BD524-A2AB-11CE-B11F-00AA00530503}} - ienumworkitems {{148BD528-A2AB-11CE-B11F-00AA00530503}} - itaskscheduler {{148BD527-A2AB-11CE-B11F-00AA00530503}} - imofcompiler {{6daf974e-2e37-11d2-aec9-00c04fb68820}} - } -} - -proc twapi::IUnknown_QueryInterface {ifc iid} { - set iidname void - catch {set iidname [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""]} - return [Twapi_IUnknown_QueryInterface $ifc $iid $iidname] -} - -proc twapi::CoGetObject {name bindopts iid} { - set iidname void - catch {set iidname [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""]} - return [Twapi_CoGetObject $name $bindopts $iid $iidname] -} - -proc twapi::progid_to_clsid {progid} { return [CLSIDFromProgID $progid] } -proc twapi::clsid_to_progid {progid} { return [ProgIDFromCLSID $progid] } - -proc twapi::com_security_blanket {args} { - # mutualauth.bool - docs for EOLE_AUTHENTICATION_CAPABILITIES. Learning - # DCOM says it is only for CoInitializeSecurity. Either way, - # that option is not applicable here - parseargs args { - {authenticationservice.arg default} - serverprincipal.arg - {authenticationlevel.arg default} - {impersonationlevel.arg default} - credentials.arg - cloaking.arg - } -maxleftover 0 -setvars - - set authenticationservice [_com_name_to_authsvc $authenticationservice] - set authenticationlevel [_com_name_to_authlevel $authenticationlevel] - set impersonationlevel [_com_name_to_impersonation $impersonationlevel] - - if {![info exists cloaking]} { - set eoac 0x800; # EOAC_DEFAULT - } else { - set eoac [dict! {none 0 static 0x20 dynamic 0x40} $cloaking] - } - - if {[info exists credentials]} { - # Credentials specified. Empty list -> NULL, ie use thread token - set creds_tag 1 - } else { - # Credentials not to be changed - set creds_tag 0 - set credentials {}; # Ignored - } - - if {[info exists serverprincipal]} { - if {$serverprincipal eq ""} { - set serverprincipaltag 0; # Default based on com_initialize_security - } else { - set serverprincipaltag 2 - } - } else { - set serverprincipaltag 1; # Unchanged server principal - set serverprincipal "" - } - - return [list $authenticationservice 0 $serverprincipaltag $serverprincipal $authenticationlevel $impersonationlevel $creds_tag $credentials $eoac] -} - -proc twapi::com_query_client_blanket {} { - lassign [CoQueryClientBlanket] authn authz server authlevel implevel client capabilities - if {$capabilities & 0x20} { - # EOAC_STATIC_CLOAKING - set cloaking static - } elseif {$capabilities & 0x40} { - set cloaking dynamic - } else { - set cloaking none - } - - # Note there is no implevel set as CoQueryClientBlanket does - # not return that information and implevel is a dummy value - return [list \ - -authenticationservice [_com_authsvc_to_name $authn] \ - -authorizationservice [dict* {0 none 1 name 2 dce} $authz] \ - -serverprincipal $server \ - -authenticationlevel [_com_authlevel_to_name $authlevel] \ - -clientprincipal $client \ - -cloaking $cloaking \ - ] -} - -# TBD - document -proc twapi::com_query_proxy_blanket {ifc} { - lassign [CoQueryProxyBlanket [lindex $args 0]] authn authz server authlevel implevel client capabilities - if {$capabilities & 0x20} { - # EOAC_STATIC_CLOAKING - set cloaking static - } elseif {$capabilities & 0x40} { - set cloaking dynamic - } else { - set cloaking none - } - - return [list \ - -authenticationservice [_com_authsvc_to_name $authn] \ - -authorizationservice [dict* {0 none 1 name 2 dce} $authz] \ - -serverprincipal $server \ - -authenticationlevel [_com_authlevel_to_name $authlevel] \ - -impersonationlevel [_com_impersonation_to_name $implevel] \ - -clientprincipal $client \ - -cloaking $cloaking \ - ] - -} - -proc twapi::com_initialize_security {args} { - # TBD - mutualauth? - # TBD - securerefs? - parseargs args { - {authenticationlevel.arg default} - {impersonationlevel.arg impersonate} - {cloaking.sym none {none 0 static 0x20 dynamic 0x40}} - secd.arg - appid.arg - authenticationservices.arg - } -maxleftover 0 -setvars - - if {[info exists secd] && [info exists appid]} { - badargs! "Only one of -secd and -appid can be specified." - } - - set impersonationlevel [_com_name_to_impersonation $impersonationlevel] - set authenticationlevel [_com_name_to_authlevel $authenticationlevel] - - set eoac $cloaking - if {[info exists appid]} { - incr eoac 8; # 8 -> EOAC_APPID - set secarg $appid - } else { - if {[info exists secd]} { - set secarg $secd - } else { - set secarg {} - } - } - - set authlist {} - if {[info exists authenticationservices]} { - foreach authsvc $authenticationservices { - lappend authlist [list [_com_name_to_authsvc [lindex $authsvc 0]] 0 [lindex $authsvc 1]] - } - } - - CoInitializeSecurity $secarg "" "" $authenticationlevel $impersonationlevel $authlist $eoac "" -} - -interp alias {} twapi::com_make_credentials {} twapi::make_logon_identity - -# TBD - document -proc twapi::com_create_instance {clsid args} { - array set opts [parseargs args { - {model.arg any} - download.bool - {disablelog.bool false} - enableaaa.bool - {nocustommarshal.bool false 0x1000} - {interface.arg IUnknown} - {authenticationservice.arg none} - {impersonationlevel.arg impersonate} - {credentials.arg {}} - {serverprincipal.arg {}} - {authenticationlevel.arg default} - {mutualauth.bool 0 0x1} - securityblanket.arg - system.arg - raw - } -maxleftover 0] - - set opts(authenticationservice) [_com_name_to_authsvc $opts(authenticationservice)] - set opts(authenticationlevel) [_com_name_to_authlevel $opts(authenticationlevel)] - set opts(impersonationlevel) [_com_name_to_impersonation $opts(impersonationlevel)] - - # CLSCTX_NO_CUSTOM_MARSHAL ? - set flags $opts(nocustommarshal) - - set model 0 - if {[info exists opts(model)]} { - foreach m $opts(model) { - switch -exact -- $m { - any {setbits model 23} - inprocserver {setbits model 1} - inprochandler {setbits model 2} - localserver {setbits model 4} - remoteserver {setbits model 16} - } - } - } - - setbits flags $model - - if {[info exists opts(download)]} { - if {$opts(download)} { - setbits flags 0x2000; # CLSCTX_ENABLE_CODE_DOWNLOAD - } else { - setbits flags 0x400; # CLSCTX_NO_CODE_DOWNLOAD - } - } - - if {$opts(disablelog)} { - setbits flags 0x4000; # CLSCTX_NO_FAILURE_LOG - } - - if {[info exists opts(enableaaa)]} { - if {$opts(enableaaa)} { - setbits flags 0x10000; # CLSCTX_ENABLE_AAA - } else { - setbits flags 0x8000; # CLSCTX_DISABLE_AAA - } - } - - if {[info exists opts(system)]} { - set coserverinfo [list 0 $opts(system) \ - [list $opts(authenticationservice) \ - 0 \ - $opts(serverprincipal) \ - $opts(authenticationlevel) \ - $opts(impersonationlevel) \ - $opts(credentials) \ - $opts(mutualauth) \ - ] \ - 0] - set activation_blanket \ - [com_security_blanket \ - -authenticationservice $opts(authenticationservice) \ - -serverprincipal $opts(serverprincipal) \ - -authenticationlevel $opts(authenticationlevel) \ - -impersonationlevel $opts(impersonationlevel) \ - -credentials $opts(credentials)] - } else { - set coserverinfo {} - } - - # If remote, set the specified security blanket on the proxy. Note - # that the blanket settings passed to CoCreateInstanceEx are used - # only for activation and do NOT get passed down to method calls - # If a remote component is activated with specific identity, we - # assume method calls require the same security settings. - - if {([info exists activation_blanket] || [llength $opts(credentials)]) && - ![info exists opts(securityblanket)]} { - if {[info exists activation_blanket]} { - set opts(securityblanket) $activation_blanket - } else { - set opts(securityblanket) [com_security_blanket -credentials $opts(credentials)] - } - } - - lassign [_resolve_iid $opts(interface)] iid iid_name - - # TBD - is all this OleRun still necessary or is there a check we can make - # before going down that path ? - # Microsoft Office (and maybe others) have some, uhhm, quirks. - # If they are loaded as inproc, all calls to retrieve an interface other - # than IUnknown fails. We have to get the IUnknown interface, - # call OleRun and then retrieve the desired interface. - # This does not happen if the localserver model was requested. - # We could check for a specific error code but no guarantee that - # the error is same in all versions so we catch and retry on all errors. - # 3rd element of each sublist is status. Non-0 -> Failure code - if {[catch {set ifcs [CoCreateInstanceEx $clsid NULL $flags $coserverinfo [list $iid]]}] || [lindex $ifcs 0 2] != 0} { - # Try through IUnknown - set ifcs [CoCreateInstanceEx $clsid NULL $flags $coserverinfo [list [_iid_iunknown]]] - - if {[lindex $ifcs 0 2] != 0} { - win32_error [lindex $ifcs 0 2] - } - set iunk [lindex $ifcs 0 1] - - # Need to set security blanket if specified before invoking any method - # else will get access denied - if {[info exists opts(securityblanket)]} { - trap { - CoSetProxyBlanket $iunk {*}$opts(securityblanket) - } onerror {} { - IUnknown_Release $iunk - rethrow - } - } - - trap { - # Wait for it to run, then get desired interface from it - twapi::OleRun $iunk - set ifc [Twapi_IUnknown_QueryInterface $iunk $iid $iid_name] - } finally { - IUnknown_Release $iunk - } - } else { - set ifc [lindex $ifcs 0 1] - } - - # All interfaces are returned typed as IUnknown by the C level - # even though they are actually the requested type. - set ifc [cast_handle $ifc $iid_name] - - if {[info exists activation_blanket]} { - # In order for servers to release objects properly, the IUnknown - # interface must have the same security settings as were used in - # the object creation - _com_set_iunknown_proxy $ifc $activation_blanket - } - - if {$opts(raw)} { - if {[info exists opts(securityblanket)]} { - trap { - CoSetProxyBlanket $ifc {*}$opts(securityblanket) - } onerror {} { - IUnknown_Release $ifc - rethrow - } - } - return $ifc - } else { - set proxy [make_interface_proxy $ifc] - if {[info exists opts(securityblanket)]} { - trap { - $proxy @SetSecurityBlanket $opts(securityblanket) - } onerror {} { - catch {$proxy Release} - rethrow - } - } - return $proxy - } -} - - -proc twapi::comobj_idispatch {ifc {addref 0} {objclsid ""} {lcid 0}} { - if {[pointer_null? $ifc]} { - return ::twapi::comobj_null - } - - if {[pointer? $ifc IDispatch]} { - if {$addref} { IUnknown_AddRef $ifc } - set proxyobj [IDispatchProxy new $ifc $objclsid] - } elseif {[pointer? $ifc IDispatchEx]} { - if {$addref} { IUnknown_AddRef $ifc } - set proxyobj [IDispatchExProxy new $ifc $objclsid] - } else { - error "'$ifc' does not reference an IDispatch interface" - } - - return [Automation new $proxyobj $lcid] -} - -# -# Create an object command for a COM object from a name -proc twapi::comobj_object {path args} { - array set opts [parseargs args { - progid.arg - {interface.arg IDispatch {IDispatch IDispatchEx}} - {lcid.int 0} - } -maxleftover 0] - - set clsid "" - if {[info exists opts(progid)]} { - # TBD - document once we have a test case for this - # Specify which app to use to open the file. - # See "Mapping Visual Basic to Automation" in SDK help - set clsid [_convert_to_clsid $opts(progid)] - set ipersistfile [com_create_instance $clsid -interface IPersistFile] - trap { - IPersistFile_Load $ipersistfile $path 0 - set idisp [Twapi_IUnknown_QueryInterface $ipersistfile [_iid_idispatch] IDispatch] - } finally { - IUnknown_Release $ipersistfile - } - } else { - # TBD - can we get the CLSID for this case - set idisp [::twapi::Twapi_CoGetObject $path {} [name_to_iid $opts(interface)] $opts(interface)] - } - - return [comobj_idispatch $idisp 0 $clsid $opts(lcid)] -} - -# -# Create a object command for a COM object IDispatch interface -# comid is either a CLSID or a PROGID -proc twapi::comobj {comid args} { - array set opts [parseargs args { - {interface.arg IDispatch {IDispatch IDispatchEx}} - active - {lcid.int 0} - } -ignoreunknown] - set clsid [_convert_to_clsid $comid] - if {$opts(active)} { - set iunk [GetActiveObject $clsid] - twapi::trap { - # TBD - do we need to deal with security blanket here? How do - # know what blanket is to be used on an already active object? - # Get the IDispatch interface - set idisp [IUnknown_QueryInterface $iunk {{00020400-0000-0000-C000-000000000046}}] - return [comobj_idispatch $idisp 0 $clsid $opts(lcid)] - } finally { - IUnknown_Release $iunk - } - } else { - set proxy [com_create_instance $clsid -interface $opts(interface) {*}$args] - $proxy @SetCLSID $clsid - return [Automation new $proxy $opts(lcid)] - } -} - -proc twapi::comobj_destroy args { - foreach arg $args { - catch {$arg -destroy} - } -} - -# Return an interface to a typelib -proc twapi::ITypeLibProxy_from_path {path args} { - array set opts [parseargs args { - {registration.arg none {none register default}} - } -maxleftover 0] - - return [make_interface_proxy [LoadTypeLibEx $path [kl_get {default 0 register 1 none 2} $opts(registration) $opts(registration)]]] -} - -# -# Return an interface to a typelib from the registry -proc twapi::ITypeLibProxy_from_guid {uuid major minor args} { - array set opts [parseargs args { - lcid.int - } -maxleftover 0 -nulldefault] - - return [make_interface_proxy [LoadRegTypeLib $uuid $major $minor $opts(lcid)]] -} - -# -# Unregister a typelib -proc twapi::unregister_typelib {uuid major minor args} { - array set opts [parseargs args { - lcid.int - } -maxleftover 0 -nulldefault] - - UnRegisterTypeLib $uuid $major $minor $opts(lcid) 1 -} - -# -# Returns the path to the typelib based on a guid -proc twapi::get_typelib_path_from_guid {guid major minor args} { - array set opts [parseargs args { - lcid.int - } -maxleftover 0 -nulldefault] - - - set path [variant_value [QueryPathOfRegTypeLib $guid $major $minor $opts(lcid)] 0 0 $opts(lcid)] - # At least some versions have a bug in that there is an extra \0 - # at the end. - if {[string equal [string index $path end] \0]} { - set path [string range $path 0 end-1] - } - return $path -} - -# -# Map interface name to IID -proc twapi::name_to_iid {iname} { - set iname [string tolower $iname] - - if {[info exists ::twapi::_name_to_iid_cache($iname)]} { - return $::twapi::_name_to_iid_cache($iname) - } - - # Look up the registry - set iids {} - foreach iid [registry keys HKEY_CLASSES_ROOT\\Interface] { - if {![catch { - set val [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""] - }]} { - if {[string equal -nocase $iname $val]} { - lappend iids $iid - } - } - } - - if {[llength $iids] == 1} { - return [set ::twapi::_name_to_iid_cache($iname) [lindex $iids 0]] - } elseif {[llength $iids]} { - error "Multiple interfaces found matching name $iname: [join $iids ,]" - } else { - return [set ::twapi::_name_to_iid_cache($iname) ""] - } -} - - -# -# Map interface IID to name -proc twapi::iid_to_name {iid} { - set iname "" - catch {set iname [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""]} - return $iname -} - -# -# Convert a variant time to a time list -proc twapi::variant_time_to_timelist {double} { - return [VariantTimeToSystemTime $double] -} - -# -# Convert a time list time to a variant time -proc twapi::timelist_to_variant_time {timelist} { - return [SystemTimeToVariantTime $timelist] -} - - -proc twapi::typelib_print {path args} { - array set opts [parseargs args { - type.arg - name.arg - output.arg - } -maxleftover 0 -nulldefault] - - - if {$opts(output) ne ""} { - if {[file exists $opts(output)]} { - error "File $opts(output) already exists." - } - set outfd [open $opts(output) a] - } else { - set outfd stdout - } - - trap { - set tl [ITypeLibProxy_from_path $path -registration none] - puts $outfd [$tl @Text -type $opts(type) -name $opts(name)] - } finally { - if {[info exists tl]} { - $tl Release - } - if {$outfd ne "stdout"} { - close $outfd - } - } - - return -} - -proc twapi::generate_code_from_typelib {path args} { - array set opts [parseargs args { - output.arg - } -ignoreunknown] - - if {[info exists opts(output)]} { - if {$opts(output) ne "stdout"} { - if {[file exists $opts(output)]} { - error "File $opts(output) already exists." - } - set outfd [open $opts(output) a] - } else { - set outfd stdout - } - } - - trap { - set tl [ITypeLibProxy_from_path $path -registration none] - set code [$tl @GenerateCode {*}$args] - if {[info exists outfd]} { - set libattr [$tl @GetLibAttr -all] - puts $outfd "# Automatically generated type library interface" - puts $outfd "# File: [file tail $path]" - puts $outfd "# Name: [$tl @GetName]" - puts $outfd "# GUID: [dict get $libattr -guid]" - puts $outfd "# Version: [dict get $libattr -majorversion].[dict get $libattr -minorversion]" - puts $outfd "# LCID: [dict get $libattr -lcid]" - - puts $outfd "\npackage require twapi_com" - puts $outfd $code - return - } else { - return $code - } - } finally { - if {[info exists tl]} { - $tl Release - } - if {[info exists outfd] && $outfd ne "stdout"} { - close $outfd - } - } -} - - - - -proc twapi::_interface_text {ti} { - # ti must be TypeInfo for an interface or module (or enum?) - TBD - set desc "" - array set attrs [$ti @GetTypeAttr -all] - set desc "Functions:\n" - for {set j 0} {$j < $attrs(-fncount)} {incr j} { - array set funcdata [$ti @GetFuncDesc $j -all] - if {$funcdata(-funckind) eq "dispatch"} { - set funckind "(dispid $funcdata(-memid))" - } else { - set funckind "(vtable $funcdata(-vtbloffset))" - } - append desc "\t$funckind [::twapi::_resolve_com_type_text $ti $funcdata(-datatype)] $funcdata(-name) $funcdata(-invkind) [::twapi::_resolve_com_params_text $ti $funcdata(-params) $funcdata(-paramnames)]\n" - } - append desc "Variables:\n" - for {set j 0} {$j < $attrs(-varcount)} {incr j} { - array set vardata [$ti @GetVarDesc $j -all] - set vardesc "($vardata(-memid)) $vardata(-varkind) [::twapi::_flatten_com_type [::twapi::_resolve_com_type_text $ti $vardata(-datatype)]] $vardata(-name)" - if {$attrs(-typekind) eq "enum" || $vardata(-varkind) eq "const"} { - append vardesc " = $vardata(-value)" - } else { - append vardesc " (offset $vardata(-value))" - } - append desc "\t$vardesc\n" - } - return $desc -} - -# -# Print methods in an interface, including inherited names -proc twapi::dispatch_print {di args} { - array set opts [parseargs args { - output.arg - } -maxleftover 0 -nulldefault] - - if {$opts(output) ne ""} { - if {[file exists $opts(output)]} { - error "File $opts(output) already exists." - } - set outfd [open $opts(output) a] - } else { - set outfd stdout - } - - trap { - set ti [$di @GetTypeInfo] - twapi::_dispatch_print_helper $ti $outfd - } finally { - if {[info exists ti]} { - $ti Release - } - if {$outfd ne "stdout"} { - close $outfd - } - } - - return -} - -proc twapi::_dispatch_print_helper {ti outfd {names_already_done ""}} { - set name [$ti @GetName] - if {$name in $names_already_done} { - # Already printed this - return $names_already_done - } - lappend names_already_done $name - - # Check for dual interfaces - we want to print both vtable and disp versions - set tilist [list $ti] - if {![catch {set ti2 [$ti @GetRefTypeInfoFromIndex $ti -1]}]} { - lappend tilist $ti2 - } - - trap { - foreach tifc $tilist { - puts $outfd $name - puts $outfd [_interface_text $tifc] - } - } finally { - if {[info exists ti2]} { - $ti2 Release - } - } - - # Now get any referenced typeinfos and print them - array set tiattrs [$ti GetTypeAttr] - for {set j 0} {$j < $tiattrs(cImplTypes)} {incr j} { - set ti2 [$ti @GetRefTypeInfoFromIndex $j] - trap { - set names_already_done [_dispatch_print_helper $ti2 $outfd $names_already_done] - } finally { - $ti2 Release - } - } - - return $names_already_done -} - - - -# -# Resolves references to parameter definition -proc twapi::_resolve_com_params_text {ti params paramnames} { - set result [list ] - foreach param $params paramname $paramnames { - set paramdesc [_flatten_com_type [_resolve_com_type_text $ti [lindex $param 0]]] - if {[llength $param] > 1 && [llength [lindex $param 1]] > 0} { - set paramdesc "\[[lindex $param 1]\] $paramdesc" - } - if {[llength $param] > 2} { - append paramdesc " [lrange $param 2 end]" - } - append paramdesc " $paramname" - lappend result $paramdesc - } - return "([join $result {, }])" -} - -# Flattens the output of _resolve_com_type_text -proc twapi::_flatten_com_type {com_type_desc} { - if {[llength $com_type_desc] < 2} { - return $com_type_desc - } - - if {[lindex $com_type_desc 0] eq "ptr"} { - return "[_flatten_com_type [lindex $com_type_desc 1]]*" - } else { - return "([lindex $com_type_desc 0] [_flatten_com_type [lindex $com_type_desc 1]])" - } -} - -# -# Resolves typedefs -proc twapi::_resolve_com_type_text {ti typedesc} { - - switch -exact -- [lindex $typedesc 0] { - 26 - - ptr { - # Recurse to resolve any inner types - set typedesc [list ptr [_resolve_com_type_text $ti [lindex $typedesc 1]]] - } - 29 - - userdefined { - set hreftype [lindex $typedesc 1] - set ti2 [$ti @GetRefTypeInfo $hreftype] - set typedesc "[$ti2 @GetName]" - $ti2 Release - } - default { - set typedesc [_vttype_to_string $typedesc] - } - } - - return $typedesc -} - - -# -# Given a COM type descriptor, resolved all user defined types (UDT) in it -# The descriptor must be in raw form as returned by the C code -proc twapi::_resolve_comtype {ti typedesc} { - - if {[lindex $typedesc 0] == 26} { - # VT_PTR - {26 INNER_TYPEDESC} - # If pointing to a UDT, convert to appropriate base type if possible - set inner [_resolve_comtype $ti [lindex $typedesc 1]] - set inner_type [lindex $inner 0] - if {$inner_type == 29} { - # When the referenced type is a UDT (29) which is actually - # a dispatch or other interface, replace the - # "pointer to UDT" with VT_DISPATCH/VT_INTERFACE - switch -exact -- [lindex $inner 1] { - dispatch {set typedesc [list 9]} - interface {set typedesc [list 13]} - coclass { - # Replace pointers to a user defined type that is - # a coclass having a default dispatch interface with - # the type for a dispatch interface - set idispatch_guid [coclass_idispatch_guid [lindex $inner 2]] - if {$idispatch_guid eq ""} { - # Coclass has no default dispatch interface - set typedesc [list 26 $inner] - } else { - # TBD - can we store idispatch_guid in param def so - # for return values we automatically convert to correct - # comobj type? - set typedesc [list 9]; # VT_DISPATCH - } - } - default { - # TBD - need to decode all the other types (record etc.) - set typedesc [list 26 $inner] - } - } - } else { - set typedesc [list 26 $inner] - } - } elseif {[lindex $typedesc 0] == 29} { - # VT_USERDEFINED - {29 HREFTYPE} - set ti2 [$ti @GetRefTypeInfo [lindex $typedesc 1]] - array set tattr [$ti2 @GetTypeAttr -guid -typekind] - switch -exact -- $tattr(-typekind) { - enum { - set typedesc [list 3]; # 3 -> i4 - } - alias { - set typedesc [_resolve_comtype $ti2 [kl_get [$ti2 GetTypeAttr] tdescAlias]] - } - default { - set typedesc [list 29 $tattr(-typekind) $tattr(-guid)] - } - } - $ti2 Release - } - - return $typedesc -} - -proc twapi::_resolve_params_for_prototype {ti paramdescs} { - set params {} - foreach paramdesc $paramdescs { - lappend params \ - [lreplace $paramdesc 0 0 [::twapi::_resolve_comtype $ti [lindex $paramdesc 0]]] - } - return $params -} - -proc twapi::_variant_values_from_safearray {sa ndims {raw false} {addref false} {lcid 0}} { - set result {} - if {[incr ndims -1] > 0} { - foreach elem $sa { - lappend result [_variant_values_from_safearray $elem $ndims $raw $addref $lcid] - } - } else { - foreach elem $sa { - lappend result [twapi::variant_value $elem $raw $addref $lcid] - } - } - return $result -} - -proc twapi::outvar {varname} { return [Twapi_InternalCast outvar $varname] } - -proc twapi::variant_value {variant raw addref {lcid 0}} { - # TBD - format appropriately depending on variant type for dates and - # currency - if {[llength $variant] == 0} { - return "" - } - set vt [lindex $variant 0] - - if {$vt & 0x2000} { - # VT_ARRAY - second element is {dimensions value} - if {[llength $variant] < 2} { - return [list ] - } - lassign [lindex $variant 1] dimensions values - set vt [expr {$vt & ~ 0x2000}] - if {$vt == 12} { - # Array of variants. Recursively convert values - return [_variant_values_from_safearray \ - $values \ - [expr {[llength $dimensions] / 2}] \ - $raw $addref $lcid] - } else { - return $values - } - } else { - if {$vt == 9} { - set idisp [lindex $variant 1]; # May be NULL! - if {$addref && ! [pointer_null? $idisp]} { - IUnknown_AddRef $idisp - } - if {$raw} { - return $idisp - } else { - # Note comobj_idispatch takes care of NULL - return [comobj_idispatch $idisp 0 "" $lcid] - } - } elseif {$vt == 13} { - set iunk [lindex $variant 1]; # May be NULL! - if {$addref && ! [pointer_null? $iunk]} { - IUnknown_AddRef $iunk - } - if {$raw} { - return $iunk - } else { - return [make_interface_proxy $iunk] - } - } - } - return [lindex $variant 1] -} - -proc twapi::variant_type {variant} { - return [lindex $variant 0] -} - -proc twapi::vt_null {} { - return [tclcast null ""] -} - -proc twapi::vt_empty {} { - return [tclcast empty ""] -} - -# -# General dispatcher for callbacks from event sinks. Invokes the actual -# registered script after mapping dispid's -proc twapi::_eventsink_callback {comobj script callee args} { - # Check if the comobj is still active - if {[llength [info commands $comobj]] == 0} { - if {$::twapi::log_config(twapi_com)} { - debuglog "COM event received for inactive object" - } - return; # Object has gone away, ignore - } - - set retcode [catch { - # We are invoked with cooked values so no need to call variant_value - uplevel #0 $script [list $callee] $args - } result] - - if {$::twapi::log_config(twapi_com) && $retcode} { - debuglog "Event sink callback error ($retcode): $result\n$::errorInfo" - } - - # $retcode is returned as HRESULT by the Invoke - return -code $retcode $result -} - -# -# Return clsid from a string. If $clsid is a valid CLSID - returns as is -# else tries to convert it from progid. An error is generated if neither -# works -proc twapi::_convert_to_clsid {comid} { - if {! [Twapi_IsValidGUID $comid]} { - return [progid_to_clsid $comid] - } - return $comid -} - -# -# Format a prototype definition for human consumption -# Proto is in the form {DISPID LCID INVOKEFLAGS RETTYPE PARAMTYPES PARAMNAMES} -proc twapi::_format_prototype {name proto} { - set dispid_lcid [lindex $proto 0]/[lindex $proto 1] - set ret_type [_vttype_to_string [lindex $proto 3]] - set invkind [_invkind_to_string [lindex $proto 2]] - # Distinguish between no parameters and parameters not known - set paramstr "" - if {[llength $proto] > 4} { - set params {} - foreach param [lindex $proto 4] paramname [lindex $proto 5] { - if {[string length $paramname]} { - set paramname " $paramname" - } - lassign $param type paramdesc - set type [_vttype_to_string $type] - set parammods [_paramflags_to_tokens [lindex $paramdesc 0]] - if {[llength [lindex $paramdesc 1]]} { - # Default specified - lappend parammods "default:[lindex [lindex $paramdesc 1] 1]" - } - lappend params "\[$parammods\] $type$paramname" - } - set paramstr " ([join $params {, }])" - } - return "$dispid_lcid $invkind $ret_type ${name}${paramstr}" -} - -# Convert parameter modifiers to string tokens. -# modifiers is list of integer flags or tokens. -proc twapi::_paramflags_to_tokens {modifiers} { - array set tokens {} - foreach mod $modifiers { - if {! [string is integer -strict $mod]} { - # mod is a token itself - set tokens($mod) "" - } else { - foreach tok [_make_symbolic_bitmask $mod { - in 1 - out 2 - lcid 4 - retval 8 - optional 16 - hasdefault 32 - hascustom 64 - }] { - set tokens($tok) "" - } - } - } - - # For cosmetic reasons, in/out should be first and remaining sorted - # Also (in,out) -> inout - if {[info exists tokens(in)]} { - if {[info exists tokens(out)]} { - set inout [list inout] - unset tokens(in) - unset tokens(out) - } else { - set inout [list in] - unset tokens(in) - } - } else { - if {[info exists tokens(out)]} { - set inout [list out] - unset tokens(out) - } - } - - if {[info exists inout]} { - return [linsert [lsort [array names tokens]] 0 $inout] - } else { - return [lsort [array names tokens]] - } -} - -# -# Map method invocation code to string -# Return code itself if no match -proc twapi::_invkind_to_string {code} { - return [kl_get { - 1 func - 2 propget - 4 propput - 8 propputref - } $code $code] -} - -# -# Map string method invocation symbol to code -# Error if no match and not an integer -proc twapi::_string_to_invkind {s} { - if {[string is integer $s]} { return $s } - return [kl_get { - func 1 - propget 2 - propput 4 - propputref 8 - } $s] -} - - -# -# Convert a VT typedef to a string -# vttype may be nested -proc twapi::_vttype_to_string {vttype} { - set vts [_vtcode_to_string [lindex $vttype 0]] - if {[llength $vttype] < 2} { - return $vts - } - - return [list $vts [_vttype_to_string [lindex $vttype 1]]] -} - -# -# Convert VT codes to strings -proc twapi::_vtcode_to_string {vt} { - return [kl_get { - 2 i2 - 3 i4 - 4 r4 - 5 r8 - 6 cy - 7 date - 8 bstr - 9 idispatch - 10 error - 11 bool - 12 variant - 13 iunknown - 14 decimal - 16 i1 - 17 ui1 - 18 ui2 - 19 ui4 - 20 i8 - 21 ui8 - 22 int - 23 uint - 24 void - 25 hresult - 26 ptr - 27 safearray - 28 carray - 29 userdefined - 30 lpstr - 31 lpwstr - 36 record - } $vt $vt] -} - -proc twapi::_string_to_base_vt {tok} { - # Only maps base VT tokens to numeric value - # TBD - record and userdefined? - return [dict get { - i2 2 - i4 3 - r4 4 - r8 5 - cy 6 - date 7 - bstr 8 - idispatch 9 - error 10 - bool 11 - iunknown 13 - decimal 14 - i1 16 - ui1 17 - ui2 18 - ui4 19 - i8 20 - ui8 21 - int 22 - uint 23 - hresult 25 - userdefined 29 - record 36 - } [string tolower $tok]] - -} - -# -# Get ADSI provider service -proc twapi::_adsi {{prov WinNT} {path {//.}}} { - return [comobj_object "${prov}:$path"] -} - -# Get cached IDispatch and IUNknown IID's -proc twapi::_iid_iunknown {} { - return $::twapi::_name_to_iid_cache(iunknown) -} -proc twapi::_iid_idispatch {} { - return $::twapi::_name_to_iid_cache(idispatch) -} - -# -# Return IID and name given a IID or name -proc twapi::_resolve_iid {name_or_iid} { - - # IID -> name mapping is more efficient so first assume it is - # an IID else we will unnecessarily trundle through the whole - # registry area looking for an IID when we already have it - # Assume it is a name - set other [iid_to_name $name_or_iid] - if {$other ne ""} { - # It was indeed the IID. Return the pair - return [list $name_or_iid $other] - } - - # Else resolve as a name - set other [name_to_iid $name_or_iid] - if {$other ne ""} { - # Yep - return [list $other $name_or_iid] - } - - win32_error 0x80004002 "Could not find IID $name_or_iid" -} - - -namespace eval twapi { - # Enable use of TclOO for new Tcl versions. To override setting - # applications should define and set before sourcing this file. - variable use_tcloo_for_com - if {![info exists use_tcloo_for_com]} { - set use_tcloo_for_com [package vsatisfies [package require Tcl] 8.6b2] - } - if {$use_tcloo_for_com} { - interp alias {} ::twapi::class {} ::oo::class - proc ::oo::define::twapi_exportall {} { - uplevel 1 export [info class methods [lindex [info level -1] 1] -private] - } - proc comobj? {cobj} { - # We do not want change the internal type so - # do not check for some types that - # could not be a comobj. In particular, - # if a list type, we do not even check - # because it cannot be a comobj and even checking - # will result in nested list types being - # destroyed which affects safearray type detection - # TBD - would it be faster to keep explicit track through - # a dictionary ? - if {[twapi::tcltype $cobj] in {bstr empty null bytecode TwapiOpaque list int double bytearray dict wideInt booleanString}} { - return 0 - } - set cobj [uplevel 1 [list namespace which -command $cobj]] - if {[info object isa object $cobj] && - [info object isa typeof $cobj ::twapi::Automation]} { - return 1 - } else { - return 0 - } - } - proc comobj_instances {} { - set comobj_classes [list ::twapi::Automation] - set objs {} - while {[llength $comobj_classes]} { - set comobj_classes [lassign $comobj_classes class] - lappend objs {*}[info class instances $class] - lappend comobj_classes {*}[info class subclasses $class] - } - # Get rid of dups which may occur if subclasses use - # multiple (diamond type) inheritance - return [lsort -unique $objs] - } - } else { - package require metoo - interp alias {} ::twapi::class {} ::metoo::class - namespace eval ::metoo::define { - proc twapi_exportall {args} { - # args is dummy to match metoo's class definition signature - # Nothing to do, all methods are metoo are public - } - } - proc comobj? {cobj} { - set cobj [uplevel 1 [list namespace which -command $cobj]] - return [metoo::introspect object isa $cobj ::twapi::Automation] - } - proc comobj_instances {} { - return [metoo::introspect object list ::twapi::Automation] - } - } - - # The prototype cache is indexed a composite key consisting of - # - the GUID of the interface, - # - the name of the function - # - the LCID - # - the invocation kind (as an integer) - # Each value contains the full prototype in a form - # that can be passed to IDispatch_Invoke. This is a list with the - # elements {DISPID LCID INVOKEFLAGS RETTYPE PARAMTYPES PARAMNAMES} - # Here PARAMTYPES is a list each element of which describes a - # parameter in the following format: - # {TYPE {FLAGS DEFAULT} NAMEDARGVALUE} where DEFAULT is optional - # and NAMEDARGVALUE only appears (optionally) when the prototype is - # passed to Invoke, not in the cached prototype itself. - # PARAMNAMES is list of parameter names in order and is - # only present if PARAMTYPES is also present. - - variable _dispatch_prototype_cache - array set _dispatch_prototype_cache {} -} - - -interp alias {} twapi::_dispatch_prototype_get {} twapi::dispatch_prototype_get -proc twapi::dispatch_prototype_get {guid name lcid invkind vproto} { - variable _dispatch_prototype_cache - set invkind [::twapi::_string_to_invkind $invkind] - if {[info exists _dispatch_prototype_cache($guid,$name,$lcid,$invkind)]} { - # Note this may be null if that name does not exist in the interface - upvar 1 $vproto proto - set proto $_dispatch_prototype_cache($guid,$name,$lcid,$invkind) - return 1 - } - return 0 -} - -# Update a prototype in cache. Note lcid and invkind cannot be -# picked up from prototype since it might be empty. -interp alias {} twapi::_dispatch_prototype_set {} twapi::dispatch_prototype_set -proc twapi::dispatch_prototype_set {guid name lcid invkind proto} { - # If the prototype does not contain the 5th element (params) - # it is a constructed prototype and we do NOT cache it as the - # disp id can change. Note empty prototypes are cached so - # we don't keep looking up something that does not exist - # Bug 130 - - if {[llength $proto] == 4} { - return - } - - variable _dispatch_prototype_cache - set invkind [_string_to_invkind $invkind] - set _dispatch_prototype_cache($guid,$name,$lcid,$invkind) $proto - return -} - -# Explicitly set prototypes for a guid -# protolist is a list of alternating name and prototype pairs. -# Each prototype must contain the LCID and invkind fields -proc twapi::_dispatch_prototype_load {guid protolist} { - foreach {name proto} $protolist { - dispatch_prototype_set $guid $name [lindex $proto 1] [lindex $proto 2] $proto - } -} - -proc twapi::coclass_idispatch_guid {coclass_guid} { - variable _coclass_idispatch_guids - if {[info exists _coclass_idispatch_guids($coclass_guid)]} { - return $_coclass_idispatch_guids($coclass_guid) - } - return "" -} - -proc twapi::_parse_dispatch_paramdef {paramdef} { - set errormsg "Invalid parameter or return type declaration '$paramdef'" - - set paramregex {^(\[[^\]]*\])?\s*(\w+)\s*(\[\s*\])?\s*([*]?)\s*(\w+)?$} - if {![regexp $paramregex [string trim $paramdef] def attrs paramtype safearray ptr paramname]} { - error $errormsg - } - - if {[string length $paramname]} { - lappend paramnames $paramname - } - # attrs can be in, out, opt separated by spaces - set paramflags 0 - foreach attr [string range $attrs 1 end-1] { - switch -exact -- $attr { - in {set paramflags [expr {$paramflags | 1}]} - out {set paramflags [expr {$paramflags | 2}]} - inout {set paramflags [expr {$paramflags | 3}]} - opt - - optional {set paramflags [expr {$paramflags | 16}]} - default {error "Unknown parameter attribute $attr"} - } - } - if {($paramflags & 3) == 0} { - set paramflags [expr {$paramflags | 1}]; # in param if unspecified - } - # Resolve parameter type. It can be - # - a safearray of base types or "variant"s (not pointers) - # - a pointer to a base type - # - a pointer to a safearray - # - a base type or "variant" - switch -exact -- $paramtype { - variant { set paramtype 12 } - void { set paramtype 24 } - default { set paramtype [_string_to_base_vt $paramtype] } - } - if {[string length $safearray]} { - if {$paramtype == 24} { - # Safearray of type void is an invalid type decl - error $errormsg - } - set paramtype [list 27 $paramtype] - } - if {[string length $ptr]} { - if {$paramtype == 24} { - # Pointer to type void is an invalid type - error $errormsg - } - set paramtype [list 26 $paramtype] - } - - return [list $paramflags $paramtype $paramname] -} - -proc twapi::define_dispatch_prototypes {guid protos args} { - array set opts [parseargs args { - {lcid.int 0} - } -maxleftover 0] - - set guid [canonicalize_guid $guid] - - set defregx {^\s*(\w+)\s+(\d+)\s+(\w[^\(]*)\(([^\)]*)\)(.*)$} - set parsed_protos {} - # Loop picking out one prototype in each interation - while {[regexp $defregx $protos -> membertype memid rettype paramstring protos]} { - set params {} - set paramnames {} - foreach paramdef [split $paramstring ,] { - lassign [_parse_dispatch_paramdef $paramdef] paramflags paramtype paramname - if {[string length $paramname]} { - lappend paramnames $paramname - } - lappend params [list $paramtype [list $paramflags]] - } - if {[llength $paramnames] && - [llength $params] != [llength $paramnames]} { - error "Missing parameter name in '$paramstring'. All parameter names must be specified or none at all." - } - - lassign [_parse_dispatch_paramdef $rettype] _ rettype name - set invkind [_string_to_invkind $membertype] - set proto [list $memid $opts(lcid) $invkind $rettype $params $paramnames] - lappend parsed_protos $name $proto - } - - set protos [string trim $protos] - if {[string length $protos]} { - error "Invalid dispatch prototype: '$protos'" - } - - _dispatch_prototype_load $guid $parsed_protos -} - -# Used to track when interface proxies are renamed/deleted -proc twapi::_interface_proxy_tracer {ifc oldname newname op} { - variable _interface_proxies - if {$op eq "rename"} { - if {$oldname eq $newname} return - set _interface_proxies($ifc) $newname - } else { - unset _interface_proxies($ifc) - } -} - - -# Return a COM interface proxy object for the specified interface. -# If such an object already exists, it is returned. Otherwise a new one -# is created. $ifc must be a valid COM Interface pointer for which -# the caller is holding a reference. Caller relinquishes ownership -# of the interface and must solely invoke operations through the -# returned proxy object. When done with the object, call the Release -# method on it, NOT destroy. -# TBD - how does this interact with security blankets ? -proc twapi::make_interface_proxy {ifc} { - variable _interface_proxies - - if {[info exists _interface_proxies($ifc)]} { - set proxy $_interface_proxies($ifc) - $proxy AddRef - if {! [pointer_null? $ifc]} { - # Release the caller's ref to the interface since we are holding - # one in the proxy object - ::twapi::IUnknown_Release $ifc - } - } else { - if {[pointer_null? $ifc]} { - set proxy [INullProxy new $ifc] - } else { - set ifcname [pointer_type $ifc] - set proxy [${ifcname}Proxy new $ifc] - } - set _interface_proxies($ifc) $proxy - trace add command $proxy {rename delete} [list ::twapi::_interface_proxy_tracer $ifc] - } - return $proxy -} - -# "Null" object - clones IUnknownProxy but will raise error on method calls -# We could have inherited but IUnknownProxy assumes non-null ifc so it -# and its inherited classes do not have to check for null in every method. -twapi::class create ::twapi::INullProxy { - constructor {ifc} { - my variable _ifc - # We keep the interface pointer because it encodes type information - if {! [::twapi::pointer_null? $ifc]} { - error "Attempt to create a INullProxy with non-NULL interface" - } - - set _ifc $ifc - - my variable _nrefs; # Internal ref count (held by app) - set _nrefs 1 - } - - method @Null? {} { return 1 } - method @Type {} { - my variable _ifc - return [::twapi::pointer_type $_ifc] - } - method @Type? {type} { - my variable _ifc - return [::twapi::pointer? $_ifc $type] - } - method AddRef {} { - my variable _nrefs - # We maintain our own ref counts. _ifc is null so do not - # call the COM AddRef ! - incr _nrefs - } - - method Release {} { - my variable _nrefs - if {[incr _nrefs -1] == 0} { - my destroy - } - } - - method DebugRefCounts {} { - my variable _nrefs - - # Return out internal ref as well as the COM ones - # Note latter is always 0 since _ifc is always NULL. - return [list $_nrefs 0] - } - - method QueryInterface {name_or_iid} { - error "Attempt to call QueryInterface called on NULL pointer" - } - - method @QueryInterface {name_or_iid} { - error "Attempt to call QueryInterface called on NULL pointer" - } - - # Parameter is for compatibility with IUnknownProxy - method @Interface {{addref 1}} { - my variable _ifc - return $_ifc - } - - twapi_exportall -} - -twapi::class create ::twapi::IUnknownProxy { - # Note caller must hold ref on the ifc. This ref is passed to - # the proxy object and caller must not make use of that ref - # unless it does an AddRef on it. - constructor {ifc {objclsid ""}} { - if {[::twapi::pointer_null? $ifc]} { - error "Attempt to register a NULL interface" - } - - my variable _ifc - set _ifc $ifc - - my variable _clsid - set _clsid $objclsid - - my variable _blanket; # Security blanket - set _blanket [list ] - - # We keep an internal reference count instead of explicitly - # calling out to the object's AddRef/Release every time. - # When the internal ref count goes to 0, we will invoke the - # object's "native" Release. - # - # Note the primary purpose of maintaining our internal reference counts - # is not efficiency by shortcutting the "native" AddRefs. It is to - # prevent crashes by bad application code; we can just generate an - # error instead by having the command go away. - my variable _nrefs; # Internal ref count (held by app) - - set _nrefs 1 - } - - destructor { - my variable _ifc - ::twapi::IUnknown_Release $_ifc - } - - method AddRef {} { - my variable _nrefs - # We maintain our own ref counts. Not pass it on to the actual object - incr _nrefs - } - - method Release {} { - my variable _nrefs - if {[incr _nrefs -1] == 0} { - my destroy - } - } - - method DebugRefCounts {} { - my variable _nrefs - my variable _ifc - - # Return out internal ref as well as the COM ones - # Note latter are unstable and only to be used for - # debugging - twapi::IUnknown_AddRef $_ifc - return [list $_nrefs [twapi::IUnknown_Release $_ifc]] - } - - method QueryInterface {name_or_iid} { - my variable _ifc - lassign [::twapi::_resolve_iid $name_or_iid] iid name - return [::twapi::Twapi_IUnknown_QueryInterface $_ifc $iid $name] - } - - # Same as QueryInterface except return "" instead of exception - # if interface not found and returns proxy object instead of interface - method @QueryInterface {name_or_iid {set_blanket 0}} { - my variable _blanket - ::twapi::trap { - set proxy [::twapi::make_interface_proxy [my QueryInterface $name_or_iid]] - if {$set_blanket && [llength $_blanket]} { - $proxy @SetSecurityBlanket $_blanket - } - return $proxy - } onerror {TWAPI_WIN32 0x80004002} { - # No such interface, return "", don't generate error - return "" - } onerror {} { - if {[info exists proxy]} { - catch {$proxy Release} - } - rethrow - } - } - - method @Type {} { - my variable _ifc - return [::twapi::pointer_type $_ifc] - } - - method @Type? {type} { - my variable _ifc - return [::twapi::pointer? $_ifc $type] - } - - method @Null? {} { - my variable _ifc - return [::twapi::pointer_null? $_ifc] - } - - # Returns raw interface. Caller must call IUnknown_Release on it - # iff addref is passed as true (default) - method @Interface {{addref 1}} { - my variable _ifc - if {$addref} { - ::twapi::IUnknown_AddRef $_ifc - } - return $_ifc - } - - # Returns out class id - old deprecated - use GetCLSID - method @Clsid {} { - my variable _clsid - return $_clsid - } - - method @GetCLSID {} { - my variable _clsid - return $_clsid - } - - method @SetCLSID {clsid} { - my variable _clsid - set _clsid $clsid - return - } - - method @SetSecurityBlanket blanket { - my variable _ifc _blanket - # In-proc components will not support IClientSecurity interface - # and will raise an error. That's the for the caller to be careful - # about. - twapi::CoSetProxyBlanket $_ifc {*}$blanket - set _blanket $blanket - return - } - - method @GetSecurityBlanket {} { - my variable _blanket - return $_blanket - } - - - twapi_exportall -} - -twapi::class create ::twapi::IDispatchProxy { - superclass ::twapi::IUnknownProxy - - destructor { - my variable _typecomp - if {[info exists _typecomp] && $_typecomp ne ""} { - $_typecomp Release - } - next - } - - method GetTypeInfoCount {} { - my variable _ifc - return [::twapi::IDispatch_GetTypeInfoCount $_ifc] - } - - # names is list - method name followed by parameter names - # Returns list of name dispid pairs - method GetIDsOfNames {names {lcid 0}} { - my variable _ifc - return [::twapi::IDispatch_GetIDsOfNames $_ifc $names $lcid] - } - - # Get dispid of a method (without parameter names) - method @GetIDOfOneName {name {lcid 0}} { - return [lindex [my GetIDsOfNames [list $name] $lcid] 1] - } - - method GetTypeInfo {{infotype 0} {lcid 0}} { - my variable _ifc - if {$infotype != 0} {error "Parameter infotype must be 0"} - return [::twapi::IDispatch_GetTypeInfo $_ifc $infotype $lcid] - } - - method @GetTypeInfo {{lcid 0}} { - return [::twapi::make_interface_proxy [my GetTypeInfo 0 $lcid]] - } - - method Invoke {prototype args} { - my variable _ifc - if {[llength $prototype] == 0 && [llength $args] == 0} { - # Treat as a property get DISPID_VALUE (default value) - # {dispid=0, lcid=0 cmd=propget(2) ret type=bstr(8) {} (no params)} - set prototype {0 0 2 8 {}} - } else { - # TBD - optimize by precomputing if a prototype needs this processing - # If any arguments are comobjs, may need to replace with the - # IDispatch interface. - # Moreover, we have to manage the reference counts for both - # IUnknown and IDispatch - - # - If the parameter is an IN parameter, ref counts do not need - # to change. - # - If the parameter is an OUT parameter, we are not passing - # an interface in, so nothing to do - # - If the parameter is an INOUT, we need to AddRef it since - # the COM method will Release it when storing a replacement - # HERE WE ONLY DO THE CHECK FOR COMOBJ. The AddRef checks are - # DONE IN THE C CODE (if necessary) - - set iarg -1 - set args2 {} - foreach arg $args { - incr iarg - # TBD - optimize this loop - set argtype [lindex $prototype 4 $iarg 0] - set argflags 0 - if {[llength [lindex $prototype 4 $iarg 1]]} { - set argflags [lindex $prototype 4 $iarg 1 0] - } - if {$argflags & 1} { - # IN param - if {$argflags & 2} { - # IN/OUT - # We currently do NOT handle a In/Out - skip for now TBD - # In the future we will have to check contents of - # the passed arg as a variable in the CALLER's context - } else { - # Pure IN param. Check if it is VT_DISPATCH or - # VT_VARIANT. Else nothing - # to do - if {[lindex $argtype 0] == 26} { - # Pointer, get base type - set argtype [lindex $argtype 1] - } - if {[lindex $argtype 0] == 9 || [lindex $argtype 0] == 12} { - # If a comobj was passed, need to extract the - # dispatch pointer. - if {[twapi::comobj? $arg]} { - # Note we do not addref when getting the interface - # (last param 0) because not necessary for IN - # params, AND it is the C code's responsibility - # anyways - set arg [$arg -interface 0] - } - } - } - - } else { - # Not an IN param. Nothing to be done - } - - lappend args2 $arg - } - set args $args2 - } - - # The uplevel is so that if some parameters are output, the varnames - # are resolved in caller - uplevel 1 [list ::twapi::IDispatch_Invoke $_ifc $prototype] $args - } - - # Methods are tried in the order specified by invkinds. - method @Invoke {name invkinds lcid params {namedargs {}}} { - if {$name eq ""} { - # Default method - return [uplevel 1 [list [self] Invoke {}] $params] - } - set nparams [llength $params] - - # We will try for each invkind to match. matches can be of - # different degrees, in descending priority - - # 1. prototype has parameter info and num params match exactly - # 2. prototype has parameter info and num params is greater - # than supplied arguments (assumes others have defaults) - # 3. prototype has no parameter information - # Within these classes, the order of invkinds determines - # priority - - if {$name eq "_NewEnum"} { - # Special case property to retrieve iterator. Some objects - # call it _NewEnum, others NewEnum. The disp id must always - # be -4 so we hard code that instead - # DISPID=-4 LCID=0 INVOKE=2(propget) RETTYPE=13(IUnknown) no parameters - set class1 [list {-4 0 2 13 {} {}}] - } else { - foreach invkind $invkinds { - set proto [my @Prototype $name $invkind $lcid] - if {[llength $proto]} { - if {[llength $proto] < 5} { - # No parameter information - lappend class3 $proto - } else { - if {[llength [lindex $proto 4]] == $nparams} { - lappend class1 $proto - break; # Class 1 match, no need to try others - } elseif {[llength [lindex $proto 4]] > $nparams} { - lappend class2 $proto - } else { - # Ignore - proto has fewer than supplied params - # Could not be a match - } - } - } - } - } - # For exact match (class1), we do not need the named - # arguments as positional arguments take priority. When - # number of passed parameters is fewer than those in - # prototype, check named arguments and use those - # values. If no parameter information, we can't use named - # arguments anyways. - - if {[info exists class1]} { - set matched_proto [lindex $class1 0] - } elseif {[info exists class2]} { - set matched_proto [lindex $class2 0] - # If we are passed named arguments AND the prototype also - # has parameter name information, replace the default values - # in the parameter definitions with the named arg value if - # it exists. - if {[llength $namedargs] && - [llength [set paramnames [lindex $matched_proto 5]]]} { - foreach {paramname paramval} $namedargs { - set paramindex [lsearch -nocase $paramnames $paramname] - if {$paramindex < 0} { - twapi::win32_error 0x80020004 "No parameter with name '$paramname' found for method '$name'" - } - - # Set the default value field of the - # appropriate parameter to the named arg value - set paramtype [lindex $matched_proto 4 $paramindex 0] - - # If parameter is VT_DISPATCH or VT_VARIANT, - # convert from comobj if necessary. - if {$paramtype == 9 || $paramtype == 12} { - if {[::twapi::comobj? $paramval]} { - # Note no AddRef when getting the interface - # (last param 0) because it is the C code's - # responsibility based on in/out direction - set paramval [$paramval -interface 0] - } - } - - # Replace the default value field for that param def - lset matched_proto 4 $paramindex [linsert [lrange [lindex $matched_proto 4 $paramindex] 0 1] 2 $paramval] - } - } - } elseif {[info exists class3]} { - set matched_proto [lindex $class3 0] - } - - if {[info exists matched_proto]} { - # Need uplevel so by-ref param vars are resolved correctly - return [uplevel 1 [list [self] Invoke $matched_proto] $params] - } - - # No prototype via typecomp / typeinfo available. - # No lcid worked. - # We have to use the last resort of GetIDsOfNames - set dispid [my @GetIDOfOneName [list $name] 0] - # TBD - should we cache result ? Probably not. - if {$dispid eq ""} { - twapi::win32_error 0x80020003 "No property or method found with name '$name'." - } - - # Try all invocation types except last in turn. If error is "Member not - # found" try the next prototype. - foreach invkind [lrange $invkinds 0 end-1] { - # Note params field (last) is missing signifying we do not - # know prototypes - set matched_proto [list $dispid 0 $invkind 8] - if {![catch { - uplevel 1 [list [self] Invoke $matched_proto] $params - } result ropts]} { - return $result - } - # If member not found error, keep going. Other errors, throw - lassign [dict get $ropts -errorcode] fac winerror - if {$fac ne "TWAPI_WIN32" && $winerror != -2147352573} { - # Some other error. - return -options $ropts $result - } - } - # Try the last one and hope for the best - set matched_proto [list $dispid 0 [lindex $invkinds end] 8] - return [uplevel 1 [list [self] Invoke $matched_proto] $params] - } - - # Get prototype that match the specified name - method @Prototype {name invkind lcid} { - my variable _ifc _guid _typecomp - - # Always need the GUID so get it we have not done so already - if {![info exists _guid]} { - my @InitTypeCompAndGuid - } - # Note above call may still have failed to init _guid - - # If we have been through here before and have our guid, - # check if a prototype exists and return it. - if {[info exists _guid] && $_guid ne "" && - [::twapi::_dispatch_prototype_get $_guid $name $lcid $invkind proto]} { - return $proto - } - - # Not in cache, have to look for it - # Use the ITypeComp for this interface if we do not - # already have it. We trap any errors because we will retry with - # different LCID's below. - set proto {} - if {![info exists _typecomp]} { - my @InitTypeCompAndGuid - } - if {$_typecomp ne ""} { - ::twapi::trap { - - set invkind [::twapi::_string_to_invkind $invkind] - set lhash [::twapi::LHashValOfName $lcid $name] - - if {![catch {$_typecomp Bind $name $lhash $invkind} binddata] && - [llength $binddata]} { - lassign $binddata type data ifc - if {$type eq "funcdesc" || - ($type eq "vardesc" && [::twapi::kl_get $data varkind] == 3)} { - set params {} - set bindti [::twapi::make_interface_proxy $ifc] - ::twapi::trap { - set params [::twapi::_resolve_params_for_prototype $bindti [::twapi::kl_get $data lprgelemdescParam]] - # Param names are needed for named arguments. Index 0 is method name so skip it - if {[catch {lrange [$bindti GetNames [twapi::kl_get $data memid]] 1 end} paramnames]} { - set paramnames {} - } - } finally { - $bindti Release - } - set proto [list [::twapi::kl_get $data memid] \ - $lcid \ - $invkind \ - [::twapi::kl_get $data elemdescFunc.tdesc] \ - $params $paramnames] - } else { - ::twapi::IUnknown_Release $ifc; # Don't need ifc but must release - twapi::debuglog "IDispatchProxy::@Prototype: Unexpected Bind type: $type, data: $data" - } - } - } onerror {} { - # Ignore and retry with other LCID's below - } - } - - - # If we do not have a guid return because even if we do not - # have a proto yet, falling through to try another lcid will not - # help and in fact will cause infinite recursion. - - if {$_guid eq ""} { - return $proto - } - - # We do have a guid, store the proto in cache (even if negative) - ::twapi::dispatch_prototype_set $_guid $name $lcid $invkind $proto - - # If we have the proto return it - if {[llength $proto]} { - return $proto - } - - # Could not find a matching prototype from the typeinfo/typecomp. - # We are not done yet. We will try and fall back to other lcid's - # Note we do this AFTER setting the prototype in the cache. That - # way we prevent (infinite) mutual recursion between lcid fallbacks. - # The fallback sequence is $lcid -> 0 -> 1033 - # (1033 is US English). Note lcid could itself be 1033 - # default and land up being checked twice times but that's - # ok since that's a one-time thing, and not very expensive either - # since the second go-around will hit the cache (negative). - # Note the time this is really useful is when the cache has - # been populated explicitly from a type library since in that - # case many interfaces land up with a US ENglish lcid (MSI being - # just one example) - - if {$lcid == 0} { - # Note this call may further recurse and return either a - # proto or empty (fail) - set proto [my @Prototype $name $invkind 1033] - } else { - set proto [my @Prototype $name $invkind 0] - } - - # Store it as *original* lcid. - ::twapi::dispatch_prototype_set $_guid $name $lcid $invkind $proto - - return $proto - } - - - # Initialize _typecomp and _guid. Not in constructor because may - # not always be required. Raises error if not available - method @InitTypeCompAndGuid {} { - my variable _guid _typecomp - - if {[info exists _typecomp]} { - # Based on code below, if _typecomp exists - # _guid also exists so no need to check for that - return - } - - ::twapi::trap { - set ti [my @GetTypeInfo 0] - } onerror {} { - # We do not raise an error because - # even without the _typecomp we can try invoking - # methods via IDispatch::GetIDsOfNames - twapi::debuglog "Could not ITypeInfo: [twapi::trapresult]" - if {![info exists _guid]} { - # Do not overwrite if already set thru @SetGuid or constructor - # Set to empty otherwise so we know we tried and failed - set _guid "" - } - set _typecomp "" - return - } - - ::twapi::trap { - # In case of dual interfaces, we need the typeinfo for the - # dispatch. Again, errors handled in try handlers - set attr [$ti GetTypeAttr] - switch -exact -- [::twapi::kl_get $attr typekind] { - 4 { - # Dispatch type, fine, just what we want - } - 3 { - # Interface type, Get the dispatch interface. If that fails, - # don't raise an error for the same reason as above - # if the interface itself is marked dispatchable - if {[catch { - $ti @GetRefTypeInfo [$ti GetRefTypeOfImplType -1] - } ti2 eropts]} { - # 4096 -> TYPEFLAG_FDISPATCHABLE - if {[::twapi::kl_get $attr wTypeFlags] & 4096} { - if {![info exists _guid]} { - # Do not overwrite if already set thru @SetGuid or constructor - # Set to empty otherwise so we know we tried and failed - # TBD - should we set _guid to [kl_get $attr guid] ? - set _guid "" - } - set _typecomp "" - return; # Note the finally clause will release $ti - } else { - # TBD - should we ignore errors even if dispatchable flag is not set? - return -options $eropts $ti2 - } - } - $ti Release - set ti $ti2 - } - default { - error "Interface is not a dispatch interface" - } - } - if {![info exists _guid]} { - # _guid might have already been valid, do not overwrite - set _guid [::twapi::kl_get [$ti GetTypeAttr] guid] - } - set _typecomp [$ti @GetTypeComp]; # ITypeComp - } finally { - $ti Release - } - } - - # Some COM objects like MSI do not have TypeInfo interfaces from - # where the GUID and TypeComp can be extracted. So we allow caller - # to explicitly set the GUID so we can look up methods in the - # dispatch prototype cache if it was populated directly by the - # application. If guid is not a valid GUID, an attempt is made - # to look it up as an IID name. - method @SetGuid {guid} { - my variable _guid - if {$guid eq ""} { - if {![info exists _guid]} { - my @InitTypeCompAndGuid - } - } else { - if {![::twapi::Twapi_IsValidGUID $guid]} { - set resolved_guid [::twapi::name_to_iid $guid] - if {$resolved_guid eq ""} { - error "Could not resolve $guid to a Interface GUID." - } - set guid $resolved_guid - } - - if {[info exists _guid] && $_guid ne ""} { - if {[string compare -nocase $guid $_guid]} { - error "Attempt to set the GUID to $guid when the dispatch proxy has already been initialized to $_guid" - } - } else { - set _guid $guid - } - } - - return $_guid - } - - method @GetCoClassTypeInfo {} { - my variable _ifc - - # We can get the typeinfo for the coclass in one of two ways: - # If the object supports IProvideClassInfo, we use it. Else - # we try the following: - # - from the idispatch, we get its typeinfo - # - from the typeinfo, we get the containing typelib - # - then we search the typelib for the coclass clsid - - ::twapi::trap { - set pci_ifc [my QueryInterface IProvideClassInfo] - set ti_ifc [::twapi::IProvideClassInfo_GetClassInfo $pci_ifc] - return [::twapi::make_interface_proxy $ti_ifc] - } onerror {} { - # Ignore - try the longer route if we were given the coclass clsid - } finally { - if {[info exists pci_ifc]} { - ::twapi::IUnknown_Release $pci_ifc - } - # Note - do not do anything with ti_ifc here, EVEN on error - } - - set co_clsid [my @Clsid] - if {$co_clsid eq ""} { - # E_FAIL - twapi::win32_error 0x80004005 "Could not get ITypeInfo for coclass: object does not support IProvideClassInfo and clsid not specified." - } - - set ti [my @GetTypeInfo] - ::twapi::trap { - set tl [lindex [$ti @GetContainingTypeLib] 0] - if {0} { - $tl @Foreach -guid $co_clsid -type coclass coti { - break - } - if {[info exists coti]} { - return $coti - } - } else { - return [$tl @GetTypeInfoOfGuid $co_clsid] - } - twapi::win32_error 0x80004005 "Could not find coclass."; # E_FAIL - } finally { - if {[info exists ti]} { - $ti Release - } - if {[info exists tl]} { - $tl Release - } - } - } - - twapi_exportall -} - - -twapi::class create ::twapi::IDispatchExProxy { - superclass ::twapi::IDispatchProxy - - method DeleteMemberByDispID {dispid} { - my variable _ifc - return [::twapi::IDispatchEx_DeleteMemberByDispID $_ifc $dispid] - } - - method DeleteMemberByName {name {lcid 0}} { - my variable _ifc - return [::twapi::IDispatchEx_DeleteMemberByName $_ifc $name $lcid] - } - - method GetDispID {name flags} { - my variable _ifc - return [::twapi::IDispatchEx_GetDispID $_ifc $name $flags] - } - - method GetMemberName {dispid} { - my variable _ifc - return [::twapi::IDispatchEx_GetMemberName $_ifc $dispid] - } - - method GetMemberProperties {dispid flags} { - my variable _ifc - return [::twapi::IDispatchEx_GetMemberProperties $_ifc $dispid $flags] - } - - # For some reason, order of args is different for this call! - method GetNextDispID {flags dispid} { - my variable _ifc - return [::twapi::IDispatchEx_GetNextDispID $_ifc $flags $dispid] - } - - method GetNameSpaceParent {} { - my variable _ifc - return [::twapi::IDispatchEx_GetNameSpaceParent $_ifc] - } - - method @GetNameSpaceParent {} { - return [::twapi::make_interface_proxy [my GetNameSpaceParent]] - } - - method @Prototype {name invkind {lcid 0}} { - set invkind [::twapi::_string_to_invkind $invkind] - - # First try IDispatch - ::twapi::trap { - set proto [next $name $invkind $lcid] - if {[llength $proto]} { - return $proto - } - # Note negative results ignored, as new members may be added/deleted - # to an IDispatchEx at any time. We will try below another way. - - } onerror {} { - # Ignore the error - we will try below using another method - } - - # Not a simple dispatch interface method. Could be expando - # type which is dynamically created. NOTE: The member is NOT - # created until the GetDispID call is made. - - # 10 -> case insensitive, create if required - set dispid [my GetDispID $name 10] - - # IMPORTANT : prototype retrieval results MUST NOT be cached since - # underlying object may add/delete members at any time. - - # No type information is available for dynamic members. - # TBD - is that really true? - - # Invoke kind - 1 (method), 2 (propget), 4 (propput) - if {$invkind == 1} { - # method - set flags 0x100 - } elseif {$invkind == 2} { - # propget - set flags 0x1 - } elseif {$invkind == 4} { - # propput - set flags 0x4 - } elseif {$invkind == 8 } { - # propputref - set flags 0x10 - } else { - error "Internal error: Invalid invkind value $invkind" - } - - # Try at least getting the invocation type but even that is not - # supported by all objects in which case we assume it can be invoked. - # TBD - in that case, why even bother doing GetMemberProperties? - if {! [catch { - set flags [expr {[my GetMemberProperties 0x115] & $flags}] - }]} { - if {! $flags} { - return {}; # EMpty proto -> no valid name for this invkind - } - } - - # Valid invkind or object does not support GetMemberProperties - # Return type is 8 (BSTR) but does not really matter as - # actual type will be set based on what is returned. - return [list $dispid $lcid $invkind 8] - } - - twapi_exportall -} - - -# ITypeInfo -#----------- - -twapi::class create ::twapi::ITypeInfoProxy { - superclass ::twapi::IUnknownProxy - - method GetRefTypeOfImplType {index} { - my variable _ifc - return [::twapi::ITypeInfo_GetRefTypeOfImplType $_ifc $index] - } - - method GetDocumentation {memid} { - my variable _ifc - return [::twapi::ITypeInfo_GetDocumentation $_ifc $memid] - } - - method GetImplTypeFlags {index} { - my variable _ifc - return [::twapi::ITypeInfo_GetImplTypeFlags $_ifc $index] - } - - method GetNames {index} { - my variable _ifc - return [::twapi::ITypeInfo_GetNames $_ifc $index] - } - - method GetTypeAttr {} { - my variable _ifc - return [::twapi::ITypeInfo_GetTypeAttr $_ifc] - } - - method GetFuncDesc {index} { - my variable _ifc - return [::twapi::ITypeInfo_GetFuncDesc $_ifc $index] - } - - method GetVarDesc {index} { - my variable _ifc - return [::twapi::ITypeInfo_GetVarDesc $_ifc $index] - } - - method GetIDsOfNames {names} { - my variable _ifc - return [::twapi::ITypeInfo_GetIDsOfNames $_ifc $names] - } - - method GetRefTypeInfo {hreftype} { - my variable _ifc - return [::twapi::ITypeInfo_GetRefTypeInfo $_ifc $hreftype] - } - - method @GetRefTypeInfo {hreftype} { - return [::twapi::make_interface_proxy [my GetRefTypeInfo $hreftype]] - } - - method GetTypeComp {} { - my variable _ifc - return [::twapi::ITypeInfo_GetTypeComp $_ifc] - } - - method @GetTypeComp {} { - return [::twapi::make_interface_proxy [my GetTypeComp]] - } - - method GetContainingTypeLib {} { - my variable _ifc - return [::twapi::ITypeInfo_GetContainingTypeLib $_ifc] - } - - method @GetContainingTypeLib {} { - lassign [my GetContainingTypeLib] itypelib index - return [list [::twapi::make_interface_proxy $itypelib] $index] - } - - method @GetRefTypeInfoFromIndex {index} { - return [my @GetRefTypeInfo [my GetRefTypeOfImplType $index]] - } - - # Friendlier version of GetTypeAttr - method @GetTypeAttr {args} { - - array set opts [::twapi::parseargs args { - all - guid - lcid - constructorid - destructorid - schema - instancesize - typekind - fncount - varcount - interfacecount - vtblsize - alignment - majorversion - minorversion - aliasdesc - flags - idldesc - memidmap - } -maxleftover 0] - - array set data [my GetTypeAttr] - set result [list ] - foreach {opt key} { - guid guid - lcid lcid - constructorid memidConstructor - destructorid memidDestructor - schema lpstrSchema - instancesize cbSizeInstance - fncount cFuncs - varcount cVars - interfacecount cImplTypes - vtblsize cbSizeVft - alignment cbAlignment - majorversion wMajorVerNum - minorversion wMinorVerNum - aliasdesc tdescAlias - } { - if {$opts(all) || $opts($opt)} { - lappend result -$opt $data($key) - } - } - - if {$opts(all) || $opts(typekind)} { - set typekind $data(typekind) - if {[info exists ::twapi::_typekind_map($typekind)]} { - set typekind $::twapi::_typekind_map($typekind) - } - lappend result -typekind $typekind - } - - if {$opts(all) || $opts(flags)} { - lappend result -flags [::twapi::_make_symbolic_bitmask $data(wTypeFlags) { - appobject 1 - cancreate 2 - licensed 4 - predeclid 8 - hidden 16 - control 32 - dual 64 - nonextensible 128 - oleautomation 256 - restricted 512 - aggregatable 1024 - replaceable 2048 - dispatchable 4096 - reversebind 8192 - proxy 16384 - }] - } - - if {$opts(all) || $opts(idldesc)} { - lappend result -idldesc [::twapi::_make_symbolic_bitmask $data(idldescType) { - in 1 - out 2 - lcid 4 - retval 8 - }] - } - - if {$opts(all) || $opts(memidmap)} { - set memidmap [list ] - for {set i 0} {$i < $data(cFuncs)} {incr i} { - array set fninfo [my @GetFuncDesc $i -memid -name] - lappend memidmap $fninfo(-memid) $fninfo(-name) - } - lappend result -memidmap $memidmap - } - - return $result - } - - # - # Get a variable description associated with a type - method @GetVarDesc {index args} { - # TBD - add support for retrieving elemdescVar.paramdesc fields - - array set opts [::twapi::parseargs args { - all - name - memid - schema - datatype - value - valuetype - varkind - flags - } -maxleftover 0] - - array set data [my GetVarDesc $index] - - set result [list ] - foreach {opt key} { - memid memid - schema lpstrSchema - datatype elemdescVar.tdesc - } { - if {$opts(all) || $opts($opt)} { - lappend result -$opt $data($key) - } - } - - - if {$opts(all) || $opts(value)} { - if {[info exists data(lpvarValue)]} { - # Const value - lappend result -value [lindex $data(lpvarValue) 1] - } else { - lappend result -value $data(oInst) - } - } - - if {$opts(all) || $opts(valuetype)} { - if {[info exists data(lpvarValue)]} { - lappend result -valuetype [lindex $data(lpvarValue) 0] - } else { - lappend result -valuetype int - } - } - - if {$opts(all) || $opts(varkind)} { - lappend result -varkind [::twapi::kl_get { - 0 perinstance - 1 static - 2 const - 3 dispatch - } $data(varkind) $data(varkind)] - } - - if {$opts(all) || $opts(flags)} { - lappend result -flags [::twapi::_make_symbolic_bitmask $data(wVarFlags) { - readonly 1 - source 2 - bindable 4 - requestedit 8 - displaybind 16 - defaultbind 32 - hidden 64 - restricted 128 - defaultcollelem 256 - uidefault 512 - nonbrowsable 1024 - replaceable 2048 - immediatebind 4096 - }] - } - - if {$opts(all) || $opts(name)} { - set result [concat $result [my @GetDocumentation $data(memid) -name]] - } - - return $result - } - - method @GetFuncDesc {index args} { - array set opts [::twapi::parseargs args { - all - name - memid - funckind - invkind - callconv - params - paramnames - flags - datatype - resultcodes - vtbloffset - } -maxleftover 0] - - array set data [my GetFuncDesc $index] - set result [list ] - - if {$opts(all) || $opts(paramnames)} { - lappend result -paramnames [lrange [my GetNames $data(memid)] 1 end] - } - foreach {opt key} { - memid memid - vtbloffset oVft - datatype elemdescFunc.tdesc - resultcodes lprgscode - } { - if {$opts(all) || $opts($opt)} { - lappend result -$opt $data($key) - } - } - - if {$opts(all) || $opts(funckind)} { - lappend result -funckind [::twapi::kl_get { - 0 virtual - 1 purevirtual - 2 nonvirtual - 3 static - 4 dispatch - } $data(funckind) $data(funckind)] - } - - if {$opts(all) || $opts(invkind)} { - lappend result -invkind [::twapi::_string_to_invkind $data(invkind)] - } - - if {$opts(all) || $opts(callconv)} { - lappend result -callconv [::twapi::kl_get { - 0 fastcall - 1 cdecl - 2 pascal - 3 macpascal - 4 stdcall - 5 fpfastcall - 6 syscall - 7 mpwcdecl - 8 mpwpascal - } $data(callconv) $data(callconv)] - } - - if {$opts(all) || $opts(flags)} { - lappend result -flags [::twapi::_make_symbolic_bitmask $data(wFuncFlags) { - restricted 1 - source 2 - bindable 4 - requestedit 8 - displaybind 16 - defaultbind 32 - hidden 64 - usesgetlasterror 128 - defaultcollelem 256 - uidefault 512 - nonbrowsable 1024 - replaceable 2048 - immediatebind 4096 - }] - } - - if {$opts(all) || $opts(params)} { - set params [list ] - foreach param $data(lprgelemdescParam) { - lassign $param paramtype paramdesc - set paramflags [::twapi::_paramflags_to_tokens [lindex $paramdesc 0]] - if {[llength $paramdesc] > 1} { - # There is a default value associated with the parameter - lappend params [list $paramtype $paramflags [lindex $paramdesc 1]] - } else { - lappend params [list $paramtype $paramflags] - } - } - lappend result -params $params - } - - if {$opts(all) || $opts(name)} { - set result [concat $result [my @GetDocumentation $data(memid) -name]] - } - - return $result - } - - # - # Get documentation for a element of a type - method @GetDocumentation {memid args} { - array set opts [::twapi::parseargs args { - all - name - docstring - helpctx - helpfile - } -maxleftover 0] - - lassign [my GetDocumentation $memid] name docstring helpctx helpfile - - set result [list ] - foreach opt {name docstring helpctx helpfile} { - if {$opts(all) || $opts($opt)} { - lappend result -$opt [set $opt] - } - } - return $result - } - - method @GetName {{memid -1}} { - return [lindex [my @GetDocumentation $memid -name] 1] - } - - method @GetImplTypeFlags {index} { - return [::twapi::_make_symbolic_bitmask \ - [my GetImplTypeFlags $index] \ - { - default 1 - source 2 - restricted 4 - defaultvtable 8 - }] - } - - # - # Get the typeinfo for the default source interface of a coclass - # This object must be the typeinfo of the coclass - method @GetDefaultSourceTypeInfo {} { - set count [lindex [my @GetTypeAttr -interfacecount] 1] - for {set i 0} {$i < $count} {incr i} { - set flags [my GetImplTypeFlags $i] - # default 0x1, source 0x2 - if {($flags & 3) == 3} { - # Our source interface implementation can only handle IDispatch - # so check if the source interface is that else keep looking. - # We even ignore dual interfaces because we cannot then - # assume caller will use the dispatch version - set ti [my @GetRefTypeInfoFromIndex $i] - array set typeinfo [$ti GetTypeAttr] - # typekind == 4 -> IDispatch, - # flags - 0x1000 -> dispatchable, 0x40 -> dual - if {$typeinfo(typekind) == 4 && - ($typeinfo(wTypeFlags) & 0x1000) && - !($typeinfo(wTypeFlags) & 0x40)} { - return $ti - } - $ti destroy - } - } - return "" - } - - twapi_exportall -} - - -# ITypeLib -#---------- - -twapi::class create ::twapi::ITypeLibProxy { - superclass ::twapi::IUnknownProxy - - method GetDocumentation {index} { - my variable _ifc - return [::twapi::ITypeLib_GetDocumentation $_ifc $index] - } - method GetTypeInfoCount {} { - my variable _ifc - return [::twapi::ITypeLib_GetTypeInfoCount $_ifc] - } - method GetTypeInfoType {index} { - my variable _ifc - return [::twapi::ITypeLib_GetTypeInfoType $_ifc $index] - } - method GetLibAttr {} { - my variable _ifc - return [::twapi::ITypeLib_GetLibAttr $_ifc] - } - method GetTypeInfo {index} { - my variable _ifc - return [::twapi::ITypeLib_GetTypeInfo $_ifc $index] - } - method @GetTypeInfo {index} { - return [::twapi::make_interface_proxy [my GetTypeInfo $index]] - } - method GetTypeInfoOfGuid {guid} { - my variable _ifc - return [::twapi::ITypeLib_GetTypeInfoOfGuid $_ifc $guid] - } - method @GetTypeInfoOfGuid {guid} { - return [::twapi::make_interface_proxy [my GetTypeInfoOfGuid $guid]] - } - method @GetTypeInfoType {index} { - set typekind [my GetTypeInfoType $index] - if {[info exists ::twapi::_typekind_map($typekind)]} { - set typekind $::twapi::_typekind_map($typekind) - } - return $typekind - } - - method @GetDocumentation {id args} { - array set opts [::twapi::parseargs args { - all - name - docstring - helpctx - helpfile - } -maxleftover 0] - - lassign [my GetDocumentation $id] name docstring helpctx helpfile - set result [list ] - foreach opt {name docstring helpctx helpfile} { - if {$opts(all) || $opts($opt)} { - lappend result -$opt [set $opt] - } - } - return $result - } - - method @GetName {} { - return [lindex [my GetDocumentation -1] 0] - } - - method @GetLibAttr {args} { - array set opts [::twapi::parseargs args { - all - guid - lcid - syskind - majorversion - minorversion - flags - } -maxleftover 0] - - array set data [my GetLibAttr] - set result [list ] - foreach {opt key} { - guid guid - lcid lcid - majorversion wMajorVerNum - minorversion wMinorVerNum - } { - if {$opts(all) || $opts($opt)} { - lappend result -$opt $data($key) - } - } - - if {$opts(all) || $opts(flags)} { - lappend result -flags [::twapi::_make_symbolic_bitmask $data(wLibFlags) { - restricted 1 - control 2 - hidden 4 - hasdiskimage 8 - }] - } - - if {$opts(all) || $opts(syskind)} { - lappend result -syskind [::twapi::kl_get { - 0 win16 - 1 win32 - 2 mac - } $data(syskind) $data(syskind)] - } - - return $result - } - - # - # Iterate through a typelib. Caller is responsible for releasing - # each ITypeInfo passed to it - # - method @Foreach {args} { - - array set opts [::twapi::parseargs args { - type.arg - name.arg - guid.arg - } -maxleftover 2 -nulldefault] - - if {[llength $args] != 2} { - error "Syntax error: Should be '[self] @Foreach ?options? VARNAME SCRIPT'" - } - - lassign $args varname script - upvar $varname varti - - set count [my GetTypeInfoCount] - for {set i 0} {$i < $count} {incr i} { - if {$opts(type) ne "" && $opts(type) ne [my @GetTypeInfoType $i]} { - continue; # Type does not match - } - if {$opts(name) ne "" && - [string compare -nocase $opts(name) [lindex [my @GetDocumentation $i -name] 1]]} { - continue; # Name does not match - } - set ti [my @GetTypeInfo $i] - if {$opts(guid) ne ""} { - if {[string compare -nocase [lindex [$ti @GetTypeAttr -guid] 1] $opts(guid)]} { - $ti Release - continue - } - } - set varti $ti - set ret [catch {uplevel 1 $script} result] - switch -exact -- $ret { - 1 { - error $result $::errorInfo $::errorCode - } - 2 { - return -code return $result; # TCL_RETURN - } - 3 { - set i $count; # TCL_BREAK - } - } - } - return - } - - method @Register {path {helppath ""}} { - my variable _ifc - ::twapi::RegisterTypeLib $_ifc $path $helppath - } - - method @LoadDispatchPrototypes {} { - set data [my @Read -type dispatch] - if {![dict exists $data dispatch]} { - return - } - - dict for {guid guiddata} [dict get $data dispatch] { - foreach type {methods properties} { - if {[dict exists $guiddata -$type]} { - dict for {name namedata} [dict get $guiddata -$type] { - dict for {lcid lciddata} $namedata { - dict for {invkind proto} $lciddata { - ::twapi::dispatch_prototype_set \ - $guid $name $lcid $invkind $proto - } - } - } - } - } - } - } - - method @Text {args} { - array set opts [::twapi::parseargs args { - type.arg - name.arg - } -maxleftover 0 -nulldefault] - - set text {} - my @Foreach -type $opts(type) -name $opts(name) ti { - ::twapi::trap { - array set attrs [$ti @GetTypeAttr -all] - set docs [$ti @GetDocumentation -1 -name -docstring] - set desc "[string totitle $attrs(-typekind)] [::twapi::kl_get $docs -name] $attrs(-guid) - [::twapi::kl_get $docs -docstring]\n" - switch -exact -- $attrs(-typekind) { - record - - union - - enum { - for {set j 0} {$j < $attrs(-varcount)} {incr j} { - array set vardata [$ti @GetVarDesc $j -all] - set vardesc "$vardata(-varkind) [::twapi::_resolve_com_type_text $ti $vardata(-datatype)] $vardata(-name)" - if {$attrs(-typekind) eq "enum"} { - append vardesc " = $vardata(-value) ([::twapi::_resolve_com_type_text $ti $vardata(-valuetype)])" - } else { - append vardesc " (offset $vardata(-value))" - } - append desc "\t$vardesc\n" - } - } - alias { - append desc "\ttypedef $attrs(-aliasdesc)\n" - } - module - - dispatch - - interface { - append desc [::twapi::_interface_text $ti] - } - coclass { - for {set j 0} {$j < $attrs(-interfacecount)} {incr j} { - set ti2 [$ti @GetRefTypeInfoFromIndex $j] - set idesc [$ti2 @GetName] - set iflags [$ti @GetImplTypeFlags $j] - if {[llength $iflags]} { - append idesc " ([join $iflags ,])" - } - append desc \t$idesc - $ti2 Release - unset ti2 - } - } - default { - append desc "Unknown typekind: $attrs(-typekind)\n" - } - } - append text \n$desc - } finally { - $ti Release - if {[info exists ti2]} { - $ti2 Release - } - } - } - return $text - } - - method @GenerateCode {args} { - array set opts [twapi::parseargs args { - namespace.arg - } -ignoreunknown] - - if {![info exists opts(namespace)]} { - set opts(namespace) [string tolower [my @GetName]] - } - - set data [my @Read {*}$args] - - set code {} - - # If namespace specfied as empty string (as opposed to unspecified) - # do not output a namespace - if {$opts(namespace) ne "" && - ([dict exists $data enum] || - [dict exists $data module] || - [dict exists $data coclass]) - } { - append code "\nnamespace eval $opts(namespace) \{\n" - append code "\n # Array mapping coclass names to their guids\n" - append code " variable _coclass_guids\n" - append code "\n # Array mapping dispatch interface names to their guids\n" - append code " variable _dispatch_guids\n" - append code { - # Returns the GUID for a coclass or empty string if not found - proc coclass_guid {coclass_name} { - variable _coclass_guids - if {[info exists _coclass_guids($coclass_name)]} { - return $_coclass_guids($coclass_name) - } - return "" - } - # Returns the GUID for a dispatch name or empty string if not found - proc dispatch_guid {dispatch_name} { - variable _dispatch_guids - if {[info exists _dispatch_guids($dispatch_name)]} { - return $_dispatch_guids($dispatch_name) - } - return "" - } - # Marks the specified object to be of a specific dispatch/coclass type - proc declare {typename comobj} { - # First check if it is the name of a dispatch interface - set guid [dispatch_guid $typename] - if {$guid ne ""} { - $comobj -interfaceguid $guid - return - } - - # If not, check if it is the name of a coclass with a dispatch interface - set guid [coclass_guid $typename] - if {$guid ne ""} { - if {[info exists ::twapi::_coclass_idispatch_guids($guid)]} { - $comobj -interfaceguid $::twapi::_coclass_idispatch_guids($guid) - return - } - } - - error "Could not resolve interface for $typename." - } - } - }; # append code... - - if {[dict exists $data module]} { - dict for {guid guiddata} [dict get $data module] { - # Some modules may not have constants (-values). - # We currently only output constants from modules, not functions - if {[dict exists $guiddata -values]} { - set module_name [dict get $guiddata -name] - append code "\n # Module $module_name ($guid)\n" - append code " [list array set $module_name [dict get $guiddata -values]]" - append code \n - } - } - } - - if {[dict exists $data enum]} { - dict for {name def} [dict get $data enum] { - append code "\n # Enum $name\n" - append code " [list array set $name [dict get $def -values]]" - append code \n - } - } - - if {[dict exists $data coclass]} { - dict for {guid def} [dict get $data coclass] { - append code "\n # Coclass [dict get $def -name]" - # Look for the default interface so we can remember its GUID. - # This is necessary for the cases where the Dispatch interface - # GUID is not available via a TypeInfo interface (e.g. - # a 64-bit COM component not registered with the 32-bit - # COM registry) - if {[dict exists $def -defaultdispatch]} { - set default_dispatch_guid [dict get $def -defaultdispatch] - append code "\n set ::twapi::_coclass_idispatch_guids($guid) \"$default_dispatch_guid\"\n" - } else { - set default_dispatch_guid "" - } - - # We assume here that coclass has a default interface - # which is dispatchable. Else an error will be generated - # at runtime. - append code [format { - set _coclass_guids(%1$s) "%2$s" - twapi::class create %1$s { - superclass ::twapi::Automation - constructor {args} { - set ifc [twapi::com_create_instance "%2$s" -interface IDispatch -raw {*}$args] - next [twapi::IDispatchProxy new $ifc "%2$s"] - if {[string length "%3$s"]} { - my -interfaceguid "%3$s" - } - } - }} [dict get $def -name] $guid $default_dispatch_guid] - append code \n - } - } - - if {$opts(namespace) ne "" && - ([dict exists $data enum] || - [dict exists $data module] || - [dict exists $data coclass]) - } { - append code "\}" - append code \n - } - - if {[dict exists $data dispatch]} { - dict for {guid guiddata} [dict get $data dispatch] { - set dispatch_name [dict get $guiddata -name] - append code "\n# Dispatch Interface $dispatch_name\n" - append code "set [set opts(namespace)]::_dispatch_guids($dispatch_name) \"$guid\"\n" - foreach type {methods properties} { - if {[dict exists $guiddata -$type]} { - append code "# $dispatch_name [string totitle $type]\n" - dict for {name namedata} [dict get $guiddata -$type] { - dict for {lcid lciddata} $namedata { - dict for {invkind proto} $lciddata { - append code [list ::twapi::dispatch_prototype_set \ - $guid $name $lcid $invkind $proto] - append code \n - } - } - } - } - } - } - } - - return $code - } - - method @Read {args} { - array set opts [::twapi::parseargs args { - type.arg - name.arg - } -maxleftover 0 -nulldefault] - - # Dictionary to contain result - set data [dict create] - - # Entries for coclasses and dispatch interfaces have a mutual - # dependency. Generation of dispatch interface method - # prototypes need to (potentially) resolve coclass names - # that map to dispatch interfaces. - # Conversely, that resolution requires a list of dispatch - # interface guids so gather that first. - - # List of dispatch guids - array set dispatch_guids {} - if {$opts(type) in {{} coclass dispatch}} { - # Collect dispatch guids. Note we do not collect other - # dispatch details since prototypes will need the coclass - # information which we do not have yet - my @Foreach -type dispatch ti { - ::twapi::trap { - set dispatch_guids([dict get [$ti GetTypeAttr] guid]) "" - } finally { - $ti Release - } - } - # Now that we have dispatch guids, collect coclass information - my @Foreach -type coclass ti { - ::twapi::trap { - array set attrs [$ti @GetTypeAttr -guid -lcid -varcount -fncount -interfacecount -typekind] - set name [lindex [$ti @GetDocumentation -1 -name] 1] - dict set data "coclass" $attrs(-guid) -name $name - for {set j 0} {$j < $attrs(-interfacecount)} {incr j} { - set ti2 [$ti @GetRefTypeInfoFromIndex $j] - set iflags [$ti GetImplTypeFlags $j] - set iguid [twapi::kl_get [$ti2 GetTypeAttr] guid] - set iname [$ti2 @GetName] - $ti2 Release - unset ti2; # So finally clause does not release again on error - - dict set data "coclass" $attrs(-guid) -interfaces $iguid -name $iname - dict set data "coclass" $attrs(-guid) -interfaces $iguid -flags $iflags - - # If this is a dispatch interface and the default interface - # for the coclass, add it to coclass default dispatch database. - # This will be used to resolve dispatch prototypes - if {$iflags == 1 && [info exists dispatch_guids($iguid)]} { - # This is used by the parameter resolution code in - # _resolve_comtype while building prototypes - set ::twapi::_coclass_idispatch_guids($attrs(-guid)) $iguid - dict set data "coclass" $attrs(-guid) -defaultdispatch $iguid - } - } - } finally { - if {[info exists ti2]} { - $i2 Release - } - $ti Release - } - } - } - - # If we were only looking for coclass information, we already have it - if {$opts(type) eq "coclass"} { - return $data - } - - my @Foreach -type $opts(type) -name $opts(name) ti { - ::twapi::trap { - array set attrs [$ti @GetTypeAttr -guid -lcid -varcount -fncount -interfacecount -typekind] - set name [lindex [$ti @GetDocumentation -1 -name] 1] - # dict set data $attrs(-typekind) $name {} - switch -exact -- $attrs(-typekind) { - record - - union - - enum { - # For consistency with the coclass and dispatch dict structure - # we have a separate key for 'name' even though it is the same - # as the dict key - dict set data $attrs(-typekind) $name -name $name - for {set j 0} {$j < $attrs(-varcount)} {incr j} { - array set vardata [$ti @GetVarDesc $j -name -value] - dict set data $attrs(-typekind) $name -values $vardata(-name) $vardata(-value) - } - } - alias { - # TBD - anything worth importing ? - } - dispatch { - # Load up the functions - dict set data $attrs(-typekind) $attrs(-guid) -name $name - for {set j 0} {$j < $attrs(-fncount)} {incr j} { - array set funcdata [$ti GetFuncDesc $j] - if {$funcdata(funckind) != 4} { - # Not a dispatch function (4), ignore - # TBD - what else could it be if already filtering - # typeinfo on dispatch - # Vtable set funckind "(vtable $funcdata(-oVft))" - ::twapi::debuglog "Unexpected funckind value '$funcdata(funckind)' ignored. funcdata: [array get funcdata]" - continue; - } - - set proto [list $funcdata(memid) \ - $attrs(-lcid) \ - $funcdata(invkind) \ - $funcdata(elemdescFunc.tdesc) \ - [::twapi::_resolve_params_for_prototype $ti $funcdata(lprgelemdescParam)]] - # Param names are needed for named arguments. Index 0 is method name so skip it - if {[catch {lappend proto [lrange [$ti GetNames $funcdata(memid)] 1 end]}]} { - # Could not get param names - lappend proto {} - } - - dict set data "$attrs(-typekind)" \ - $attrs(-guid) \ - -methods \ - [$ti @GetName $funcdata(memid)] \ - $attrs(-lcid) \ - $funcdata(invkind) \ - $proto - } - # Load up the properties - for {set j 0} {$j < $attrs(-varcount)} {incr j} { - array set vardata [$ti GetVarDesc $j] - # We will add both propput and propget. - # propget: - dict set data "$attrs(-typekind)" \ - $attrs(-guid) \ - -properties \ - [$ti @GetName $vardata(memid)] \ - $attrs(-lcid) \ - 2 \ - [list $vardata(memid) $attrs(-lcid) 2 $vardata(elemdescVar.tdesc) {} {}] - - # TBD - mock up the parameters for the property set - # Single parameter corresponding to return type of - # property. Param list is of the form - # {PARAM1 PARAM2} where PARAM is {TYPE {FLAGS ?DEFAULT}} - # So param list with one param is - # {{TYPE {FLAGS ?DEFAULT?}}} - # propput: - if {! ($vardata(wVarFlags) & 1)} { - # Not read-only - dict set data "$attrs(-typekind)" \ - $attrs(-guid) \ - -properties \ - [$ti @GetName $vardata(memid)] \ - $attrs(-lcid) \ - 4 \ - [list $vardata(memid) $attrs(-lcid) 4 24 [list [list $vardata(elemdescVar.tdesc) [list 1]]] {}] - } - } - } - - module { - dict set data $attrs(-typekind) $attrs(-guid) -name $name - # TBD - Load up the functions - - # Now load up the variables - for {set j 0} {$j < $attrs(-varcount)} {incr j} { - array set vardata [$ti @GetVarDesc $j -name -value] - dict set data $attrs(-typekind) $attrs(-guid) -values $vardata(-name) $vardata(-value) - } - } - - interface { - # TBD - } - - coclass { - # We have already collected this information before this loop - continue - } - default { - # TBD - } - } - } finally { - $ti Release - if {[info exists ti2]} { - $ti2 Release - } - } - } - - # Unless we are collecting coclass info, remove any related info - # that we might have gathered for dispatch prototypes - if {$opts(type) ni {{} coclass}} { - dict unset data "coclass" - } - return $data - } - - twapi_exportall -} - -# ITypeComp -#---------- -twapi::class create ::twapi::ITypeCompProxy { - superclass ::twapi::IUnknownProxy - - method Bind {name lhash flags} { - my variable _ifc - return [::twapi::ITypeComp_Bind $_ifc $name $lhash $flags] - } - - # Returns empty list if bind not found - method @Bind {name flags {lcid 0}} { - ::twapi::trap { - set binding [my Bind $name [::twapi::LHashValOfName $lcid $name] $flags] - } onerror {TWAPI_WIN32 0x80028ca0} { - # Found but type mismatch (flags not correct) - return {} - } - - lassign $binding type data tifc - return [list $type $data [::twapi::make_interface_proxy $tifc]] - } - - twapi_exportall -} - -# IEnumVARIANT -#------------- - -twapi::class create ::twapi::IEnumVARIANTProxy { - superclass ::twapi::IUnknownProxy - - method Next {count {value_only 0}} { - my variable _ifc - return [::twapi::IEnumVARIANT_Next $_ifc $count $value_only] - } - method Clone {} { - my variable _ifc - return [::twapi::IEnumVARIANT_Clone $_ifc] - } - method @Clone {} { - return [::twapi::make_interface_proxy [my Clone]] - } - method Reset {} { - my variable _ifc - return [::twapi::IEnumVARIANT_Reset $_ifc] - } - method Skip {count} { - my variable _ifc - return [::twapi::IEnumVARIANT_Skip $_ifc $count] - } - - twapi_exportall -} - -# Automation -#----------- -twapi::class create ::twapi::Automation { - - # Caller gives up ownership of proxy in all cases, even errors. - # $proxy will eventually be Release'ed. If caller wants to keep - # a reference to it, it must do an *additional* AddRef on it to - # keep it from going away when the Automation object releases it. - constructor {proxy {lcid 0}} { - my variable _proxy _lcid _sinks _connection_pts - - set type [$proxy @Type] - if {$type ne "IDispatch" && $type ne "IDispatchEx"} { - $proxy Release; # Even on error, responsible for releasing - error "Automation objects do not support interfaces of type '$type'" - } - if {$type eq "IDispatchEx"} { - my variable _have_dispex - # If _have_dispex variable - # - does not exist, have not tried to get IDispatchEx yet - # - is 0, have tried but failed - # - is 1, already have IDispatchEx - set _have_dispex 1 - } - - set _proxy $proxy - set _lcid $lcid - array set _sinks {} - array set _connection_pts {} - } - - destructor { - my variable _proxy _sinks - - # Release sinks, connection points - foreach sinkid [array names _sinks] { - my -unbind $sinkid - } - - if {[info exists _proxy]} { - $_proxy Release - } - return - } - - # Intended to be called only from another method. Not directly. - # Does an uplevel 2 to get to application context. - # On failures, retries with IDispatchEx interface - # TBD - get rid of this uplevel business by having internal - # callers to equivalent of "uplevel 1 my _invoke ... - method _invoke {name invkinds params args} { - my variable _proxy _lcid - - if {[$_proxy @Null?]} { - error "Attempt to invoke method $name on NULL COM object" - } - - array set opts [twapi::parseargs args { - raw.bool - namedargs.arg - } -nulldefault -maxleftover 0] - - ::twapi::trap { - set vtval [uplevel 2 [list $_proxy @Invoke $name $invkinds $_lcid $params $opts(namedargs)]] - if {$opts(raw)} { - return $vtval - } else { - return [::twapi::variant_value $vtval 0 0 $_lcid] - } - } onerror {} { - # TBD - should we only drop down below to check for IDispatchEx - # for specific error codes. Right now we do it for all. - set erinfo $::errorInfo - set ercode $::errorCode - set ermsg [::twapi::trapresult] - } - - # We plan on trying to get a IDispatchEx interface in case - # the method/property is the "expando" type - my variable _have_dispex - if {[info exists _have_dispex]} { - # We have already tried for IDispatchEx, either successfully - # or not. Either way, no need to try again - error $ermsg $erinfo $ercode - } - - # Try getting a IDispatchEx interface - if {[catch {$_proxy @QueryInterface IDispatchEx 1} proxy_ex] || - $proxy_ex eq ""} { - set _have_dispex 0 - error $ermsg $erinfo $ercode - } - - set _have_dispex 1 - $_proxy Release - set _proxy $proxy_ex - - # Retry with the IDispatchEx interface - set vtval [uplevel 2 [list $_proxy @Invoke $name $invkinds $_lcid $params $opts(namedargs)]] - if {$opts(raw)} { - return $vtval - } else { - return [::twapi::variant_value $vtval 0 0 $_lcid] - } - } - - method -get {name args} { - return [my _invoke $name [list 2] $args] - } - - method -put {name args} { - return [my _invoke $name [list 4] $args] - } - forward -set my -put - - method -putref {name args} { - return [my _invoke $name [list 8] $args] - } - - method -call {name args} { - return [my _invoke $name [list 1] $args] - } - - method -callnamedargs {name args} { - return [my _invoke $name [list 1] {} -namedargs $args] - } - - # Need a wrapper around _invoke in order for latter's uplevel 2 - # to work correctly - # TBD - document, test - method -invoke {name invkinds params args} { - return [my _invoke $name $invkinds $params {*}$args] - } - - method -destroy {} { - my destroy - } - - method -isnull {} { - my variable _proxy - return [$_proxy @Null?] - } - - method -default {} { - my variable _proxy _lcid - return [::twapi::variant_value [$_proxy Invoke ""] 0 0 $_lcid] - } - - # Caller must call release on the proxy - method -proxy {} { - my variable _proxy - $_proxy AddRef - return $_proxy - } - - # Only for debugging - method -proxyrefcounts {} { - my variable _proxy - return [$_proxy DebugRefCounts] - } - - # Returns the raw interface. Caller must call IUnknownRelease on it - # iff addref is passed as true (default) - method -interface {{addref 1}} { - my variable _proxy - return [$_proxy @Interface $addref] - } - - # Validates internal structures - method -validate {} { - twapi::ValidateIUnknown [my -interface 0] - } - - # Set/return the GUID for the interface - method -interfaceguid {{guid ""}} { - my variable _proxy - return [$_proxy @SetGuid $guid] - } - - # Sets the idispatch or coclass of the object - method -instanceof {coclass} { - # The coclass may be a GUID or the Tcl name - if {[::twapi::Twapi_IsValidGUID $coclass]} { - if {[info exists ::twapi::_coclass_idispatch_guids($coclass)]} { - $comobj -interfaceguid $::twapi::_coclass_idispatch_guids($coclass) - } - error "Could not resolve interface for coclass GUID $coclass." - } - # Check for corresponding Tcl class name generated from a type - # library - set ns [namespace qualifiers $coclass] - if {$ns eq ""} { - error "Coclass name must be qualified with name of containing namespace." - } - uplevel 1 [list ${ns}::declare [namespace tail $coclass] [self]] - } - - # Return the disp id for a method/property - method -dispid {name} { - my variable _proxy - return [$_proxy @GetIDOfOneName $name] - } - - # Prints methods in an interface - method -print {} { - my variable _proxy - ::twapi::dispatch_print $_proxy - } - - method -with {subobjlist args} { - # $obj -with SUBOBJECTPATHLIST arguments - # where SUBOBJECTPATHLIST is list each element of which is - # either a property or a method of the previous element in - # the list. The element may itself be a list in which case - # the first element is the property/method and remaining - # are passed to it - # - # Note that 'arguments' may themselves be comobj subcommands! - set next [self] - set releaselist [list ] - ::twapi::trap { - while {[llength $subobjlist]} { - set nextargs [lindex $subobjlist 0] - set subobjlist [lrange $subobjlist 1 end] - set next [uplevel 1 [list $next] $nextargs] - lappend releaselist $next - } - # We use uplevel here because again we want to run in caller - # context - return [uplevel 1 [list $next] $args] - } finally { - foreach next $releaselist { - $next -destroy - } - } - } - - method -iterate {args} { - my variable _lcid - - array set opts [::twapi::parseargs args { - cleanup - }] - - if {[llength $args] < 2} { - error "Syntax: COMOBJ -iterate ?options? VARNAME SCRIPT" - } - upvar 1 [lindex $args 0] var - set script [lindex $args 1] - - # First get IEnumVariant iterator using the _NewEnum method - # TBD - As per MS OLE Automation spec, it appears _NewEnum - # MUST have dispid -4. Can we use this information when - # this object does not have an associated interface guid or - # when no prototype is available ? - set enumerator [my -get _NewEnum] - # This gives us an IUnknown. - ::twapi::trap { - # Convert the IUnknown to IEnumVARIANT - set iter [$enumerator @QueryInterface IEnumVARIANT] - if {! [$iter @Null?]} { - set more 1 - while {$more} { - # Get the next item from iterator - set next [$iter Next 1] - lassign $next more values - if {[llength $values]} { - set var [::twapi::variant_value [lindex $values 0] 0 0 $_lcid] - set ret [catch {uplevel 1 $script} msg options] - switch -exact -- $ret { - 0 - - 4 { - # Body executed successfully, or invoked continue - if {$opts(cleanup)} { - $var destroy - } - } - 3 { - if {$opts(cleanup)} { - $var destroy - } - set more 0; # TCL_BREAK - } - 1 - - 2 - - default { - if {$opts(cleanup)} { - $var destroy - } - dict incr options -level - return -options $options $msg - } - - } - } - } - } - } finally { - $enumerator Release - if {[info exists iter] && ![$iter @Null?]} { - $iter Release - } - } - return - } - - method -bind {script} { - my variable _proxy _sinks _connection_pts - - # Get the coclass typeinfo and locate the source interface - # within it and retrieve disp id mappings - ::twapi::trap { - set coti [$_proxy @GetCoClassTypeInfo] - - # $coti is the coclass information. Get dispids for the default - # source interface for events and its guid - set srcti [$coti @GetDefaultSourceTypeInfo] - array set srcinfo [$srcti @GetTypeAttr -memidmap -guid] - - # TBD - implement IConnectionPointContainerProxy - # Now we need to get the actual connection point itself - set container [$_proxy QueryInterface IConnectionPointContainer] - set connpt_ifc [::twapi::IConnectionPointContainer_FindConnectionPoint $container $srcinfo(-guid)] - - # Finally, create our sink object - # TBD - need to make sure Automation object is not deleted or - # should the callback itself check? - # TBD - what guid should we be passing? CLSID or IID ? - set sink_ifc [::twapi::Twapi_ComServer $srcinfo(-guid) $srcinfo(-memidmap) [list ::twapi::_eventsink_callback [self] $script]] - - # OK, we finally have everything we need. Tell the event source - set sinkid [::twapi::IConnectionPoint_Advise $connpt_ifc $sink_ifc] - - set _sinks($sinkid) $sink_ifc - set _connection_pts($sinkid) $connpt_ifc - return $sinkid - } onerror {} { - # These are released only on error as otherwise they have - # to be kept until unbind time - foreach ifc {connpt_ifc sink_ifc} { - if {[info exists $ifc] && [set $ifc] ne ""} { - ::twapi::IUnknown_Release [set $ifc] - } - } - twapi::rethrow - } finally { - # In all cases, release any interfaces we created - # Note connpt_ifc and sink_ifc are released at unbind time except - # on error - foreach obj {coti srcti} { - if {[info exists $obj]} { - [set $obj] Release - } - } - if {[info exists container]} { - ::twapi::IUnknown_Release $container - } - } - } - - method -unbind {sinkid} { - my variable _proxy _sinks _connection_pts - - if {[info exists _connection_pts($sinkid)]} { - ::twapi::IConnectionPoint_Unadvise $_connection_pts($sinkid) $sinkid - unset _connection_pts($sinkid) - } - - if {[info exists _sinks($sinkid)]} { - ::twapi::IUnknown_Release $_sinks($sinkid) - unset _sinks($sinkid) - } - return - } - - method -securityblanket {args} { - my variable _proxy - if {[llength $args]} { - $_proxy @SetSecurityBlanket [lindex $args 0] - return - } else { - return [$_proxy @GetSecurityBlanket] - } - } - - method -lcid {{lcid ""}} { - my variable _lcid - if {$lcid ne ""} { - if {![string is integer -strict $lcid]} { - error "Invalid LCID $lcid" - } - set _lcid $lcid - } - return $_lcid - } - - method unknown {name args} { - # We have to figure out if it is a property get, property put - # or a method. We make a guess based on number of parameters. - # We specify an order to try based on this. The invoke will try - # all invocations in that order. - set nargs [llength $args] - if {$nargs == 0} { - # No arguments, cannot be propput*. Try propget and method - set invkinds [list 2 1] - } elseif {$nargs == 1} { - # One argument, likely propput, method, propget, propputref - # propputref is last as least likely - set invkinds [list 4 1 2 8] - } else { - # Multiple arguments, likely method, propput, propget, propputref - # propputref is last as least likely - set invkinds [list 1 4 2 8] - } - - return [my _invoke $name $invkinds $args] - } - - twapi_exportall -} - -# -# Singleton NULL comobj object. We want to override default destroy methods -# to prevent object from being destroyed. This is a backward compatibility -# hack and not fool proof since the command could just be renamed away. -twapi::class create twapi::NullAutomation { - superclass twapi::Automation - constructor {} { - next [twapi::make_interface_proxy {0 IDispatch}] - } - method -destroy {} { - # Silently ignore - } - method destroy {} { - # Silently ignore - } - twapi_exportall -} - -twapi::NullAutomation create twapi::comobj_null -# twapi::Automation create twapi::comobj_null [twapi::make_interface_proxy {0 IDispatch}] - -proc twapi::_comobj_cleanup {} { - foreach obj [comobj_instances] { - $obj destroy - } -} - -# In order for servers to release objects properly, the IUnknown interface -# must have the same security settings as were used in the object creation -# call. This is a helper for that. -proc twapi::_com_set_iunknown_proxy {ifc blanket} { - set iunk [Twapi_IUnknown_QueryInterface $ifc [_iid_iunknown] IUnknown] - trap { - CoSetProxyBlanket $iunk {*}$blanket - } finally { - IUnknown_Release $iunk - } -} - - -twapi::proc* twapi::_init_authnames {} { - variable _com_authsvc_to_name - variable _com_name_to_authsvc - variable _com_impersonation_to_name - variable _com_name_to_impersonation - variable _com_authlevel_to_name - variable _com_name_to_authlevel - - set _com_authsvc_to_name {0 none 9 negotiate 10 ntlm 14 schannel 16 kerberos 0xffffffff default} - set _com_name_to_authsvc [swapl $_com_authsvc_to_name] - set _com_name_to_impersonation {default 0 anonymous 1 identify 2 impersonate 3 delegate 4} - set _com_impersonation_to_name [swapl $_com_name_to_impersonation] - set _com_name_to_authlevel {default 0 none 1 connect 2 call 3 packet 4 packetintegrity 5 privacy 6} - set _com_authlevel_to_name [swapl $_com_name_to_authlevel] -} { -} - -twapi::proc* twapi::_com_authsvc_to_name {authsvc} { - _init_authnames -} { - variable _com_authsvc_to_name - return [dict* $_com_authsvc_to_name $authsvc] -} - -twapi::proc* twapi::_com_name_to_authsvc {name} { - _init_authnames -} { - variable _com_name_to_authsvc - if {[string is integer -strict $name]} { - return $name - } - return [dict! $_com_name_to_authsvc $name] -} - -twapi::proc* twapi::_com_authlevel_to_name {authlevel} { - _init_authnames -} { - variable _com_authlevel_to_name - return [dict* $_com_authlevel_to_name $authlevel] -} - -twapi::proc* twapi::_com_name_to_authlevel {name} { - _init_authnames -} { - variable _com_name_to_authlevel - if {[string is integer -strict $name]} { - return $name - } - return [dict! $_com_name_to_authlevel $name] -} - - -twapi::proc* twapi::_com_impersonation_to_name {imp} { - _init_authnames -} { - variable _com_impersonation_to_name - return [dict* $_com_impersonation_to_name $imp] -} - -twapi::proc* twapi::_com_name_to_impersonation {name} { - _init_authnames -} { - variable _com_name_to_impersonation - if {[string is integer -strict $name]} { - return $name - } - return [dict! $_com_name_to_impersonation $name] -} - -################################################################# -# COM server implementation -# WARNING: do not use any fancy TclOO features because it has to -# run under 8.5/metoo as well -# TBD - test scripts? - -twapi::class create twapi::ComFactory { - constructor {clsid member_map create_command_prefix} { - my variable _clsid _create_command_prefix _member_map _ifc - - set _clsid $clsid - set _member_map $member_map - set _create_command_prefix $create_command_prefix - - set _ifc [twapi::Twapi_ClassFactory $_clsid [list [self] _create_instance]] - } - - destructor { - # TBD - what happens if factory is destroyed while objects still - # exist ? - # App MUST explicitly destroy objects before exiting - my variable _class_registration_id - if {[info exists _class_registration_id]} { - twapi::CoRevokeClassObject $_class_registration_id - } - } - - # Called from Twapi_ClassFactory_CreateInstance to create a new object - # Should not be called from elsewhere - method _create_instance {iid} { - my variable _create_command_prefix _member_map - # Note [list {*}$foo] != $foo - consider when foo contains a ";" - set obj_prefix [uplevel #0 [list {*}$_create_command_prefix]] - twapi::trap { - # Since we are not holding on to this interface ourselves, - # we can pass it on without AddRef'ing it - return [twapi::Twapi_ComServer $iid $_member_map $obj_prefix] - } onerror {} { - $obj_prefix destroy - twapi::rethrow - } - } - - method register {args} { - my variable _clsid _create_command_prefix _member_map _ifc _class_registration_id - twapi::parseargs args { - {model.arg any} - } -setvars -maxleftover 0 - set model_flags 0 - foreach m $model { - switch -exact -- $m { - any {twapi::setbits model_flags 20} - localserver {twapi::setbits model_flags 4} - remoteserver {twapi::setbits model_flags 16} - default {twapi::badargs! "Invalid COM class model '$m'"} - } - } - - # 0x6 -> REGCLS_MULTI_SEPARATE | REGCLS_SUSPENDED - set _class_registration_id [twapi::CoRegisterClassObject $_clsid $_ifc $model_flags 0x6] - return - } - - export _create_instance -} - -proc twapi::comserver_factory {clsid member_map command_prefix {name {}}} { - if {$name ne ""} { - uplevel 1 [list [namespace current]::ComFactory create $name $clsid $member_map $command_prefix] - } else { - uplevel 1 [list [namespace current]::ComFactory new $clsid $member_map $command_prefix] - } -} - -proc twapi::start_factories {{cmd {}}} { - # TBD - what if no class objects ? - CoResumeClassObjects - - if {[llength $cmd]} { - # TBD - normalize $cmd so to run in right namespace etc. - trace add variable [namspace current]::com_shutdown_signal write $cmd - return - } - - # This is set from the C code when we are not serving up any - # COM objects (either event callbacks or com servers) - vwait [namespace current]::com_shutdown_signal -} - -proc twapi::suspend_factories {} { - CoSuspendClassObjects -} - -proc twapi::resume_factories {} { - CoResumeClassObjects -} - -proc twapi::install_coclass_script {progid clsid version script_path args} { - # Need to extract params so we can prefix script name - set saved_args $args - array set opts [parseargs args { - params.arg - } -ignoreunknown] - - set script_path [file normalize $script_path] - - # Try to locate the wish executable to run the component - if {[info commands wm] eq ""} { - set dir [file dirname [info nameofexecutable]] - set wishes [glob -nocomplain -directory $dir wish*.exe] - if {[llength $wishes] == 0} { - error "Could not locate wish program." - } - set wish [lindex $wishes 0] - } else { - # We are running wish already - set wish [info nameofexecutable] - } - - set exe_path [file nativename [file attributes $wish -shortname]] - - set params "\"$script_path\"" - if {[info exists opts(params)]} { - append params " $params" - } - return [install_coclass $progid $clsid $version $exe_path {*}$args -outproc -params $params] -} - -proc twapi::install_coclass {progid clsid version path args} { - array set opts [twapi::parseargs args { - {scope.arg user {user system}} - appid.arg - appname.arg - inproc - outproc - service - params.arg - name.arg - } -maxleftover 0] - - switch [tcl::mathop::+ $opts(inproc) $opts(outproc) $opts(service)] { - 0 { - # Need to figure out the type - switch [file extension $path] { - .exe { set opts(outproc) 1 } - .ocx - - .dll { set opts(inproc) 1 } - default { set opts(service) 1 } - } - } - 1 {} - default { - badargs! "Only one of -inproc, -outproc or -service may be specified" - } - } - - if {(! [string is integer -strict $version]) || $version <= 0} { - twapi::badargs! "Invalid version '$version'. Must be a positive integer" - } - if {![regexp {^[[:alpha:]][[:alnum:]]*\.[[:alpha:]][[:alnum:]]*$} $progid]} { - badargs! "Invalid PROGID syntax '$progid'" - } - set clsid [canonicalize_guid $clsid] - if {![info exists opts(appid)]} { - # This is what dcomcnfg and oleview do - default to the CLSID - set opts(appid) $clsid - } else { - set opts(appid) [canonicalize_guid $opts(appid)] - } - - if {$opts(scope) eq "user"} { - if {$opts(service)} { - twapi::badargs! "Option -service cannot be specified if -scope is \"user\"" - } - set regtop HKEY_CURRENT_USER - } else { - set regtop HKEY_LOCAL_MACHINE - } - - set progid_path "$regtop\\Software\\Classes\\$progid" - set clsid_path "$regtop\\Software\\Classes\\CLSID\\$clsid" - set appid_path "$regtop\\Software\\Classes\\AppID\\$opts(appid)" - - if {$opts(service)} { - # TBD - badargs! "Option -service is not implemented" - } elseif {$opts(outproc)} { - if {[info exists opts(params)]} { - registry set "$clsid_path\\LocalServer32" "" "\"[file nativename [file normalize $path]]\" $opts(params)" - } else { - registry set "$clsid_path\\LocalServer32" "" "\"[file nativename [file normalize $path]]\"" - } - # TBD - We do not quote path for ServerExecutable, should we ? - registry set "$clsid_path\\LocalServer32" "ServerExecutable" [file nativename [file normalize $path]] - } else { - # TBD - We do not quote path here either, should we ? - registry set "$clsid_path\\InprocServer32" "" [file nativename [file normalize $path]] - } - - registry set "$clsid_path\\ProgID" "" "$progid.$version" - registry set "$clsid_path\\VersionIndependentProgID" "" $progid - - # Set the registry under the progid and progid.version - registry set "$progid_path\\CLSID" "" $clsid - registry set "$progid_path\\CurVer" "" "$progid.$version" - if {[info exists opts(name)]} { - registry set $progid_path "" $opts(name) - } - - append progid_path ".$version" - registry set "$progid_path\\CLSID" "" $clsid - if {[info exists opts(name)]} { - registry set $progid_path "" $opts(name) - } - - registry set $clsid_path "AppID" $opts(appid) - registry set $appid_path; # Always create the key even if nothing below - if {[info exists opts(appname)]} { - registry set $appid_path "" $opts(appname) - } - - if {$opts(service)} { - registry set $appid_path "LocalService" $path - if {[info exists opts(params)]} { - registry set $appid_path "ServiceParameters" $opts(params) - } - } - - return -} - -proc twapi::uninstall_coclass {progid args} { - # Note "CLSID" itself is a valid ProgID (it has a CLSID key below it) - # Also we want to protect against horrible errors that blow away - # entire branches if progid is empty, wrong value, etc. - # So only work with keys of the form X.X - if {![regexp {^[[:alpha:]][[:alnum:]]*\.[[:alpha:]][[:alnum:]]*$} $progid]} { - badargs! "Invalid PROGID syntax '$progid'" - } - - # Do NOT want to delete the CLSID key by mistake. Note below checks - # will not protect against this since they will return a valid value - # if progid is "CLSID" since that has a CLSID key below it as well. - if {[string equal -nocase $progid CLSID]} { - badargs! "Attempt to delete protected key 'CLSID'" - } - - array set opts [twapi::parseargs args { - {scope.arg user {user system}} - keepappid - } -maxleftover 0] - - switch -exact -- $opts(scope) { - user { set regtop HKEY_CURRENT_USER } - system { set regtop HKEY_LOCAL_MACHINE } - default { - badargs! "Invalid class registration scope '$opts(scope)'. Must be 'user' or 'system'" - } - } - - if {0} { - # Do NOT use this. If running under elevated, it will ignore - # HKEY_CURRENT_USER. - set clsid [progid_to_clsid $progid]; # Also protects against bogus progids - } else { - set clsid [registry get "$regtop\\Software\\Classes\\$progid\\CLSID" ""] - } - - # Should not be empty at this point but do not want to delete the - # whole Classes tree in case progid or clsid are empty strings - # because of some bug! That would be an epic disaster so try and - # protect. - if {$clsid eq ""} { - badargs! "CLSID corresponding to PROGID '$progid' is empty" - } - - # See if we need to delete the linked current version - if {! [catch { - registry get "$regtop\\Software\\Classes\\$progid\\CurVer" "" - } curver]} { - if {[string match -nocase ${progid}.* $curver]} { - registry delete "$regtop\\Software\\Classes\\$curver" - } - } - - # See if we need to delete the APPID - if {! $opts(keepappid)} { - if {! [catch { - registry get "$regtop\\Software\\Classes\\CLSID\\$clsid" "AppID" - } appid]} { - # Validate it is a real GUID - if {![catch {canonicalize_guid $appid}]} { - registry delete "$regtop\\Software\\Classes\\AppID\\$appid" - } - } - } - - # Finally delete the keys and hope we have not trashed the system - registry delete "$regtop\\Software\\Classes\\CLSID\\$clsid" - registry delete "$regtop\\Software\\Classes\\$progid" - - return -} - - +# +# Copyright (c) 2006-2018 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# TBD - tests comobj? works with derived classes of Automation + +# TBD - object identity comparison +# - see http://blogs.msdn.com/ericlippert/archive/2005/04/26/412199.aspx +# TBD - we seem to resolve UDT's every time a COM method is actually invoked. +# Optimize by doing it when prototype is stored or only the first time it +# is called. +# TBD - optimize by caching UDT's within a type library when the library +# is read. + +# TBD - optimize comobj unknown by caching previously resolved names +# + + +namespace eval twapi { + # Maps TYPEKIND data values to symbols + variable _typekind_map + array set _typekind_map { + 0 enum + 1 record + 2 module + 3 interface + 4 dispatch + 5 coclass + 6 alias + 7 union + } + + # Cache of Interface names - IID mappings + variable _name_to_iid_cache + array set _name_to_iid_cache { + iunknown {{00000000-0000-0000-C000-000000000046}} + idispatch {{00020400-0000-0000-C000-000000000046}} + idispatchex {{A6EF9860-C720-11D0-9337-00A0C90DCAA9}} + itypeinfo {{00020401-0000-0000-C000-000000000046}} + itypecomp {{00020403-0000-0000-C000-000000000046}} + ienumvariant {{00020404-0000-0000-C000-000000000046}} + iprovideclassinfo {{B196B283-BAB4-101A-B69C-00AA00341D07}} + + ipersist {{0000010c-0000-0000-C000-000000000046}} + ipersistfile {{0000010b-0000-0000-C000-000000000046}} + + iprovidetaskpage {{4086658a-cbbb-11cf-b604-00c04fd8d565}} + itasktrigger {{148BD52B-A2AB-11CE-B11F-00AA00530503}} + ischeduleworkitem {{a6b952f0-a4b1-11d0-997d-00aa006887ec}} + itask {{148BD524-A2AB-11CE-B11F-00AA00530503}} + ienumworkitems {{148BD528-A2AB-11CE-B11F-00AA00530503}} + itaskscheduler {{148BD527-A2AB-11CE-B11F-00AA00530503}} + imofcompiler {{6daf974e-2e37-11d2-aec9-00c04fb68820}} + } +} + +proc twapi::IUnknown_QueryInterface {ifc iid} { + set iidname void + catch {set iidname [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""]} + return [Twapi_IUnknown_QueryInterface $ifc $iid $iidname] +} + +proc twapi::CoGetObject {name bindopts iid} { + set iidname void + catch {set iidname [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""]} + return [Twapi_CoGetObject $name $bindopts $iid $iidname] +} + +proc twapi::progid_to_clsid {progid} { return [CLSIDFromProgID $progid] } +proc twapi::clsid_to_progid {progid} { return [ProgIDFromCLSID $progid] } + +proc twapi::com_security_blanket {args} { + # mutualauth.bool - docs for EOLE_AUTHENTICATION_CAPABILITIES. Learning + # DCOM says it is only for CoInitializeSecurity. Either way, + # that option is not applicable here + parseargs args { + {authenticationservice.arg default} + serverprincipal.arg + {authenticationlevel.arg default} + {impersonationlevel.arg default} + credentials.arg + cloaking.arg + } -maxleftover 0 -setvars + + set authenticationservice [_com_name_to_authsvc $authenticationservice] + set authenticationlevel [_com_name_to_authlevel $authenticationlevel] + set impersonationlevel [_com_name_to_impersonation $impersonationlevel] + + if {![info exists cloaking]} { + set eoac 0x800; # EOAC_DEFAULT + } else { + set eoac [dict! {none 0 static 0x20 dynamic 0x40} $cloaking] + } + + if {[info exists credentials]} { + # Credentials specified. Empty list -> NULL, ie use thread token + set creds_tag 1 + } else { + # Credentials not to be changed + set creds_tag 0 + set credentials {}; # Ignored + } + + if {[info exists serverprincipal]} { + if {$serverprincipal eq ""} { + set serverprincipaltag 0; # Default based on com_initialize_security + } else { + set serverprincipaltag 2 + } + } else { + set serverprincipaltag 1; # Unchanged server principal + set serverprincipal "" + } + + return [list $authenticationservice 0 $serverprincipaltag $serverprincipal $authenticationlevel $impersonationlevel $creds_tag $credentials $eoac] +} + +proc twapi::com_query_client_blanket {} { + lassign [CoQueryClientBlanket] authn authz server authlevel implevel client capabilities + if {$capabilities & 0x20} { + # EOAC_STATIC_CLOAKING + set cloaking static + } elseif {$capabilities & 0x40} { + set cloaking dynamic + } else { + set cloaking none + } + + # Note there is no implevel set as CoQueryClientBlanket does + # not return that information and implevel is a dummy value + return [list \ + -authenticationservice [_com_authsvc_to_name $authn] \ + -authorizationservice [dict* {0 none 1 name 2 dce} $authz] \ + -serverprincipal $server \ + -authenticationlevel [_com_authlevel_to_name $authlevel] \ + -clientprincipal $client \ + -cloaking $cloaking \ + ] +} + +# TBD - document +proc twapi::com_query_proxy_blanket {ifc} { + lassign [CoQueryProxyBlanket [lindex $args 0]] authn authz server authlevel implevel client capabilities + if {$capabilities & 0x20} { + # EOAC_STATIC_CLOAKING + set cloaking static + } elseif {$capabilities & 0x40} { + set cloaking dynamic + } else { + set cloaking none + } + + return [list \ + -authenticationservice [_com_authsvc_to_name $authn] \ + -authorizationservice [dict* {0 none 1 name 2 dce} $authz] \ + -serverprincipal $server \ + -authenticationlevel [_com_authlevel_to_name $authlevel] \ + -impersonationlevel [_com_impersonation_to_name $implevel] \ + -clientprincipal $client \ + -cloaking $cloaking \ + ] + +} + +proc twapi::com_initialize_security {args} { + # TBD - mutualauth? + # TBD - securerefs? + parseargs args { + {authenticationlevel.arg default} + {impersonationlevel.arg impersonate} + {cloaking.sym none {none 0 static 0x20 dynamic 0x40}} + secd.arg + appid.arg + authenticationservices.arg + } -maxleftover 0 -setvars + + if {[info exists secd] && [info exists appid]} { + badargs! "Only one of -secd and -appid can be specified." + } + + set impersonationlevel [_com_name_to_impersonation $impersonationlevel] + set authenticationlevel [_com_name_to_authlevel $authenticationlevel] + + set eoac $cloaking + if {[info exists appid]} { + incr eoac 8; # 8 -> EOAC_APPID + set secarg $appid + } else { + if {[info exists secd]} { + set secarg $secd + } else { + set secarg {} + } + } + + set authlist {} + if {[info exists authenticationservices]} { + foreach authsvc $authenticationservices { + lappend authlist [list [_com_name_to_authsvc [lindex $authsvc 0]] 0 [lindex $authsvc 1]] + } + } + + CoInitializeSecurity $secarg "" "" $authenticationlevel $impersonationlevel $authlist $eoac "" +} + +interp alias {} twapi::com_make_credentials {} twapi::make_logon_identity + +# TBD - document +proc twapi::com_create_instance {clsid args} { + array set opts [parseargs args { + {model.arg any} + download.bool + {disablelog.bool false} + enableaaa.bool + {nocustommarshal.bool false 0x1000} + {interface.arg IUnknown} + {authenticationservice.arg none} + {impersonationlevel.arg impersonate} + {credentials.arg {}} + {serverprincipal.arg {}} + {authenticationlevel.arg default} + {mutualauth.bool 0 0x1} + securityblanket.arg + system.arg + raw + } -maxleftover 0] + + set opts(authenticationservice) [_com_name_to_authsvc $opts(authenticationservice)] + set opts(authenticationlevel) [_com_name_to_authlevel $opts(authenticationlevel)] + set opts(impersonationlevel) [_com_name_to_impersonation $opts(impersonationlevel)] + + # CLSCTX_NO_CUSTOM_MARSHAL ? + set flags $opts(nocustommarshal) + + set model 0 + if {[info exists opts(model)]} { + foreach m $opts(model) { + switch -exact -- $m { + any {setbits model 23} + inprocserver {setbits model 1} + inprochandler {setbits model 2} + localserver {setbits model 4} + remoteserver {setbits model 16} + } + } + } + + setbits flags $model + + if {[info exists opts(download)]} { + if {$opts(download)} { + setbits flags 0x2000; # CLSCTX_ENABLE_CODE_DOWNLOAD + } else { + setbits flags 0x400; # CLSCTX_NO_CODE_DOWNLOAD + } + } + + if {$opts(disablelog)} { + setbits flags 0x4000; # CLSCTX_NO_FAILURE_LOG + } + + if {[info exists opts(enableaaa)]} { + if {$opts(enableaaa)} { + setbits flags 0x10000; # CLSCTX_ENABLE_AAA + } else { + setbits flags 0x8000; # CLSCTX_DISABLE_AAA + } + } + + if {[info exists opts(system)]} { + set coserverinfo [list 0 $opts(system) \ + [list $opts(authenticationservice) \ + 0 \ + $opts(serverprincipal) \ + $opts(authenticationlevel) \ + $opts(impersonationlevel) \ + $opts(credentials) \ + $opts(mutualauth) \ + ] \ + 0] + set activation_blanket \ + [com_security_blanket \ + -authenticationservice $opts(authenticationservice) \ + -serverprincipal $opts(serverprincipal) \ + -authenticationlevel $opts(authenticationlevel) \ + -impersonationlevel $opts(impersonationlevel) \ + -credentials $opts(credentials)] + } else { + set coserverinfo {} + } + + # If remote, set the specified security blanket on the proxy. Note + # that the blanket settings passed to CoCreateInstanceEx are used + # only for activation and do NOT get passed down to method calls + # If a remote component is activated with specific identity, we + # assume method calls require the same security settings. + + if {([info exists activation_blanket] || [llength $opts(credentials)]) && + ![info exists opts(securityblanket)]} { + if {[info exists activation_blanket]} { + set opts(securityblanket) $activation_blanket + } else { + set opts(securityblanket) [com_security_blanket -credentials $opts(credentials)] + } + } + + lassign [_resolve_iid $opts(interface)] iid iid_name + + # TBD - is all this OleRun still necessary or is there a check we can make + # before going down that path ? + # Microsoft Office (and maybe others) have some, uhhm, quirks. + # If they are loaded as inproc, all calls to retrieve an interface other + # than IUnknown fails. We have to get the IUnknown interface, + # call OleRun and then retrieve the desired interface. + # This does not happen if the localserver model was requested. + # We could check for a specific error code but no guarantee that + # the error is same in all versions so we catch and retry on all errors. + # 3rd element of each sublist is status. Non-0 -> Failure code + if {[catch {set ifcs [CoCreateInstanceEx $clsid NULL $flags $coserverinfo [list $iid]]}] || [lindex $ifcs 0 2] != 0} { + # Try through IUnknown + set ifcs [CoCreateInstanceEx $clsid NULL $flags $coserverinfo [list [_iid_iunknown]]] + + if {[lindex $ifcs 0 2] != 0} { + win32_error [lindex $ifcs 0 2] + } + set iunk [lindex $ifcs 0 1] + + # Need to set security blanket if specified before invoking any method + # else will get access denied + if {[info exists opts(securityblanket)]} { + trap { + CoSetProxyBlanket $iunk {*}$opts(securityblanket) + } onerror {} { + IUnknown_Release $iunk + rethrow + } + } + + trap { + # Wait for it to run, then get desired interface from it + twapi::OleRun $iunk + set ifc [Twapi_IUnknown_QueryInterface $iunk $iid $iid_name] + } finally { + IUnknown_Release $iunk + } + } else { + set ifc [lindex $ifcs 0 1] + } + + # All interfaces are returned typed as IUnknown by the C level + # even though they are actually the requested type. + set ifc [cast_handle $ifc $iid_name] + + if {[info exists activation_blanket]} { + # In order for servers to release objects properly, the IUnknown + # interface must have the same security settings as were used in + # the object creation + _com_set_iunknown_proxy $ifc $activation_blanket + } + + if {$opts(raw)} { + if {[info exists opts(securityblanket)]} { + trap { + CoSetProxyBlanket $ifc {*}$opts(securityblanket) + } onerror {} { + IUnknown_Release $ifc + rethrow + } + } + return $ifc + } else { + set proxy [make_interface_proxy $ifc] + if {[info exists opts(securityblanket)]} { + trap { + $proxy @SetSecurityBlanket $opts(securityblanket) + } onerror {} { + catch {$proxy Release} + rethrow + } + } + return $proxy + } +} + + +proc twapi::comobj_idispatch {ifc {addref 0} {objclsid ""} {lcid 0}} { + if {[pointer_null? $ifc]} { + return ::twapi::comobj_null + } + + if {[pointer? $ifc IDispatch]} { + if {$addref} { IUnknown_AddRef $ifc } + set proxyobj [IDispatchProxy new $ifc $objclsid] + } elseif {[pointer? $ifc IDispatchEx]} { + if {$addref} { IUnknown_AddRef $ifc } + set proxyobj [IDispatchExProxy new $ifc $objclsid] + } else { + error "'$ifc' does not reference an IDispatch interface" + } + + return [Automation new $proxyobj $lcid] +} + +# +# Create an object command for a COM object from a name +proc twapi::comobj_object {path args} { + array set opts [parseargs args { + progid.arg + {interface.arg IDispatch {IDispatch IDispatchEx}} + {lcid.int 0} + } -maxleftover 0] + + set clsid "" + if {[info exists opts(progid)]} { + # TBD - document once we have a test case for this + # Specify which app to use to open the file. + # See "Mapping Visual Basic to Automation" in SDK help + set clsid [_convert_to_clsid $opts(progid)] + set ipersistfile [com_create_instance $clsid -interface IPersistFile] + trap { + IPersistFile_Load $ipersistfile $path 0 + set idisp [Twapi_IUnknown_QueryInterface $ipersistfile [_iid_idispatch] IDispatch] + } finally { + IUnknown_Release $ipersistfile + } + } else { + # TBD - can we get the CLSID for this case + set idisp [::twapi::Twapi_CoGetObject $path {} [name_to_iid $opts(interface)] $opts(interface)] + } + + return [comobj_idispatch $idisp 0 $clsid $opts(lcid)] +} + +# +# Create a object command for a COM object IDispatch interface +# comid is either a CLSID or a PROGID +proc twapi::comobj {comid args} { + array set opts [parseargs args { + {interface.arg IDispatch {IDispatch IDispatchEx}} + active + {lcid.int 0} + } -ignoreunknown] + set clsid [_convert_to_clsid $comid] + if {$opts(active)} { + set iunk [GetActiveObject $clsid] + twapi::trap { + # TBD - do we need to deal with security blanket here? How do + # know what blanket is to be used on an already active object? + # Get the IDispatch interface + set idisp [IUnknown_QueryInterface $iunk {{00020400-0000-0000-C000-000000000046}}] + return [comobj_idispatch $idisp 0 $clsid $opts(lcid)] + } finally { + IUnknown_Release $iunk + } + } else { + set proxy [com_create_instance $clsid -interface $opts(interface) {*}$args] + $proxy @SetCLSID $clsid + return [Automation new $proxy $opts(lcid)] + } +} + +proc twapi::comobj_destroy args { + foreach arg $args { + catch {$arg -destroy} + } +} + +# Return an interface to a typelib +proc twapi::ITypeLibProxy_from_path {path args} { + array set opts [parseargs args { + {registration.arg none {none register default}} + } -maxleftover 0] + + return [make_interface_proxy [LoadTypeLibEx $path [kl_get {default 0 register 1 none 2} $opts(registration) $opts(registration)]]] +} + +# +# Return an interface to a typelib from the registry +proc twapi::ITypeLibProxy_from_guid {uuid major minor args} { + array set opts [parseargs args { + lcid.int + } -maxleftover 0 -nulldefault] + + return [make_interface_proxy [LoadRegTypeLib $uuid $major $minor $opts(lcid)]] +} + +# +# Unregister a typelib +proc twapi::unregister_typelib {uuid major minor args} { + array set opts [parseargs args { + lcid.int + } -maxleftover 0 -nulldefault] + + UnRegisterTypeLib $uuid $major $minor $opts(lcid) 1 +} + +# +# Returns the path to the typelib based on a guid +proc twapi::get_typelib_path_from_guid {guid major minor args} { + array set opts [parseargs args { + lcid.int + } -maxleftover 0 -nulldefault] + + + set path [variant_value [QueryPathOfRegTypeLib $guid $major $minor $opts(lcid)] 0 0 $opts(lcid)] + # At least some versions have a bug in that there is an extra \0 + # at the end. + if {[string equal [string index $path end] \0]} { + set path [string range $path 0 end-1] + } + return $path +} + +# +# Map interface name to IID +proc twapi::name_to_iid {iname} { + set iname [string tolower $iname] + + if {[info exists ::twapi::_name_to_iid_cache($iname)]} { + return $::twapi::_name_to_iid_cache($iname) + } + + # Look up the registry + set iids {} + foreach iid [registry keys HKEY_CLASSES_ROOT\\Interface] { + if {![catch { + set val [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""] + }]} { + if {[string equal -nocase $iname $val]} { + lappend iids $iid + } + } + } + + if {[llength $iids] == 1} { + return [set ::twapi::_name_to_iid_cache($iname) [lindex $iids 0]] + } elseif {[llength $iids]} { + error "Multiple interfaces found matching name $iname: [join $iids ,]" + } else { + return [set ::twapi::_name_to_iid_cache($iname) ""] + } +} + + +# +# Map interface IID to name +proc twapi::iid_to_name {iid} { + set iname "" + catch {set iname [registry get HKEY_CLASSES_ROOT\\Interface\\$iid ""]} + return $iname +} + +# +# Convert a variant time to a time list +proc twapi::variant_time_to_timelist {double} { + return [VariantTimeToSystemTime $double] +} + +# +# Convert a time list time to a variant time +proc twapi::timelist_to_variant_time {timelist} { + return [SystemTimeToVariantTime $timelist] +} + + +proc twapi::typelib_print {path args} { + array set opts [parseargs args { + type.arg + name.arg + output.arg + } -maxleftover 0 -nulldefault] + + + if {$opts(output) ne ""} { + if {[file exists $opts(output)]} { + error "File $opts(output) already exists." + } + set outfd [open $opts(output) a] + } else { + set outfd stdout + } + + trap { + set tl [ITypeLibProxy_from_path $path -registration none] + puts $outfd [$tl @Text -type $opts(type) -name $opts(name)] + } finally { + if {[info exists tl]} { + $tl Release + } + if {$outfd ne "stdout"} { + close $outfd + } + } + + return +} + +proc twapi::generate_code_from_typelib {path args} { + array set opts [parseargs args { + output.arg + } -ignoreunknown] + + if {[info exists opts(output)]} { + if {$opts(output) ne "stdout"} { + if {[file exists $opts(output)]} { + error "File $opts(output) already exists." + } + set outfd [open $opts(output) a] + } else { + set outfd stdout + } + } + + trap { + set tl [ITypeLibProxy_from_path $path -registration none] + set code [$tl @GenerateCode {*}$args] + if {[info exists outfd]} { + set libattr [$tl @GetLibAttr -all] + puts $outfd "# Automatically generated type library interface" + puts $outfd "# File: [file tail $path]" + puts $outfd "# Name: [$tl @GetName]" + puts $outfd "# GUID: [dict get $libattr -guid]" + puts $outfd "# Version: [dict get $libattr -majorversion].[dict get $libattr -minorversion]" + puts $outfd "# LCID: [dict get $libattr -lcid]" + + puts $outfd "\npackage require twapi_com" + puts $outfd $code + return + } else { + return $code + } + } finally { + if {[info exists tl]} { + $tl Release + } + if {[info exists outfd] && $outfd ne "stdout"} { + close $outfd + } + } +} + + + + +proc twapi::_interface_text {ti} { + # ti must be TypeInfo for an interface or module (or enum?) - TBD + set desc "" + array set attrs [$ti @GetTypeAttr -all] + set desc "Functions:\n" + for {set j 0} {$j < $attrs(-fncount)} {incr j} { + array set funcdata [$ti @GetFuncDesc $j -all] + if {$funcdata(-funckind) eq "dispatch"} { + set funckind "(dispid $funcdata(-memid))" + } else { + set funckind "(vtable $funcdata(-vtbloffset))" + } + append desc "\t$funckind [::twapi::_resolve_com_type_text $ti $funcdata(-datatype)] $funcdata(-name) $funcdata(-invkind) [::twapi::_resolve_com_params_text $ti $funcdata(-params) $funcdata(-paramnames)]\n" + } + append desc "Variables:\n" + for {set j 0} {$j < $attrs(-varcount)} {incr j} { + array set vardata [$ti @GetVarDesc $j -all] + set vardesc "($vardata(-memid)) $vardata(-varkind) [::twapi::_flatten_com_type [::twapi::_resolve_com_type_text $ti $vardata(-datatype)]] $vardata(-name)" + if {$attrs(-typekind) eq "enum" || $vardata(-varkind) eq "const"} { + append vardesc " = $vardata(-value)" + } else { + append vardesc " (offset $vardata(-value))" + } + append desc "\t$vardesc\n" + } + return $desc +} + +# +# Print methods in an interface, including inherited names +proc twapi::dispatch_print {di args} { + array set opts [parseargs args { + output.arg + } -maxleftover 0 -nulldefault] + + if {$opts(output) ne ""} { + if {[file exists $opts(output)]} { + error "File $opts(output) already exists." + } + set outfd [open $opts(output) a] + } else { + set outfd stdout + } + + trap { + set ti [$di @GetTypeInfo] + twapi::_dispatch_print_helper $ti $outfd + } finally { + if {[info exists ti]} { + $ti Release + } + if {$outfd ne "stdout"} { + close $outfd + } + } + + return +} + +proc twapi::_dispatch_print_helper {ti outfd {names_already_done ""}} { + set name [$ti @GetName] + if {$name in $names_already_done} { + # Already printed this + return $names_already_done + } + lappend names_already_done $name + + # Check for dual interfaces - we want to print both vtable and disp versions + set tilist [list $ti] + if {![catch {set ti2 [$ti @GetRefTypeInfoFromIndex $ti -1]}]} { + lappend tilist $ti2 + } + + trap { + foreach tifc $tilist { + puts $outfd $name + puts $outfd [_interface_text $tifc] + } + } finally { + if {[info exists ti2]} { + $ti2 Release + } + } + + # Now get any referenced typeinfos and print them + array set tiattrs [$ti GetTypeAttr] + for {set j 0} {$j < $tiattrs(cImplTypes)} {incr j} { + set ti2 [$ti @GetRefTypeInfoFromIndex $j] + trap { + set names_already_done [_dispatch_print_helper $ti2 $outfd $names_already_done] + } finally { + $ti2 Release + } + } + + return $names_already_done +} + + + +# +# Resolves references to parameter definition +proc twapi::_resolve_com_params_text {ti params paramnames} { + set result [list ] + foreach param $params paramname $paramnames { + set paramdesc [_flatten_com_type [_resolve_com_type_text $ti [lindex $param 0]]] + if {[llength $param] > 1 && [llength [lindex $param 1]] > 0} { + set paramdesc "\[[lindex $param 1]\] $paramdesc" + } + if {[llength $param] > 2} { + append paramdesc " [lrange $param 2 end]" + } + append paramdesc " $paramname" + lappend result $paramdesc + } + return "([join $result {, }])" +} + +# Flattens the output of _resolve_com_type_text +proc twapi::_flatten_com_type {com_type_desc} { + if {[llength $com_type_desc] < 2} { + return $com_type_desc + } + + if {[lindex $com_type_desc 0] eq "ptr"} { + return "[_flatten_com_type [lindex $com_type_desc 1]]*" + } else { + return "([lindex $com_type_desc 0] [_flatten_com_type [lindex $com_type_desc 1]])" + } +} + +# +# Resolves typedefs +proc twapi::_resolve_com_type_text {ti typedesc} { + + switch -exact -- [lindex $typedesc 0] { + 26 - + ptr { + # Recurse to resolve any inner types + set typedesc [list ptr [_resolve_com_type_text $ti [lindex $typedesc 1]]] + } + 29 - + userdefined { + set hreftype [lindex $typedesc 1] + set ti2 [$ti @GetRefTypeInfo $hreftype] + set typedesc "[$ti2 @GetName]" + $ti2 Release + } + default { + set typedesc [_vttype_to_string $typedesc] + } + } + + return $typedesc +} + + +# +# Given a COM type descriptor, resolved all user defined types (UDT) in it +# The descriptor must be in raw form as returned by the C code +proc twapi::_resolve_comtype {ti typedesc} { + + if {[lindex $typedesc 0] == 26} { + # VT_PTR - {26 INNER_TYPEDESC} + # If pointing to a UDT, convert to appropriate base type if possible + set inner [_resolve_comtype $ti [lindex $typedesc 1]] + set inner_type [lindex $inner 0] + if {$inner_type == 29} { + # When the referenced type is a UDT (29) which is actually + # a dispatch or other interface, replace the + # "pointer to UDT" with VT_DISPATCH/VT_INTERFACE + switch -exact -- [lindex $inner 1] { + dispatch {set typedesc [list 9]} + interface {set typedesc [list 13]} + coclass { + # Replace pointers to a user defined type that is + # a coclass having a default dispatch interface with + # the type for a dispatch interface + set idispatch_guid [coclass_idispatch_guid [lindex $inner 2]] + if {$idispatch_guid eq ""} { + # Coclass has no default dispatch interface + set typedesc [list 26 $inner] + } else { + # TBD - can we store idispatch_guid in param def so + # for return values we automatically convert to correct + # comobj type? + set typedesc [list 9]; # VT_DISPATCH + } + } + default { + # TBD - need to decode all the other types (record etc.) + set typedesc [list 26 $inner] + } + } + } else { + set typedesc [list 26 $inner] + } + } elseif {[lindex $typedesc 0] == 29} { + # VT_USERDEFINED - {29 HREFTYPE} + set ti2 [$ti @GetRefTypeInfo [lindex $typedesc 1]] + array set tattr [$ti2 @GetTypeAttr -guid -typekind] + switch -exact -- $tattr(-typekind) { + enum { + set typedesc [list 3]; # 3 -> i4 + } + alias { + set typedesc [_resolve_comtype $ti2 [kl_get [$ti2 GetTypeAttr] tdescAlias]] + } + default { + set typedesc [list 29 $tattr(-typekind) $tattr(-guid)] + } + } + $ti2 Release + } + + return $typedesc +} + +proc twapi::_resolve_params_for_prototype {ti paramdescs} { + set params {} + foreach paramdesc $paramdescs { + lappend params \ + [lreplace $paramdesc 0 0 [::twapi::_resolve_comtype $ti [lindex $paramdesc 0]]] + } + return $params +} + +proc twapi::_variant_values_from_safearray {sa ndims {raw false} {addref false} {lcid 0}} { + set result {} + if {[incr ndims -1] > 0} { + foreach elem $sa { + lappend result [_variant_values_from_safearray $elem $ndims $raw $addref $lcid] + } + } else { + foreach elem $sa { + lappend result [twapi::variant_value $elem $raw $addref $lcid] + } + } + return $result +} + +proc twapi::outvar {varname} { return [Twapi_InternalCast outvar $varname] } + +proc twapi::variant_value {variant raw addref {lcid 0}} { + # TBD - format appropriately depending on variant type for dates and + # currency + if {[llength $variant] == 0} { + return "" + } + set vt [lindex $variant 0] + + if {$vt & 0x2000} { + # VT_ARRAY - second element is {dimensions value} + if {[llength $variant] < 2} { + return [list ] + } + lassign [lindex $variant 1] dimensions values + set vt [expr {$vt & ~ 0x2000}] + if {$vt == 12} { + # Array of variants. Recursively convert values + return [_variant_values_from_safearray \ + $values \ + [expr {[llength $dimensions] / 2}] \ + $raw $addref $lcid] + } else { + return $values + } + } else { + if {$vt == 9} { + set idisp [lindex $variant 1]; # May be NULL! + if {$addref && ! [pointer_null? $idisp]} { + IUnknown_AddRef $idisp + } + if {$raw} { + return $idisp + } else { + # Note comobj_idispatch takes care of NULL + return [comobj_idispatch $idisp 0 "" $lcid] + } + } elseif {$vt == 13} { + set iunk [lindex $variant 1]; # May be NULL! + if {$addref && ! [pointer_null? $iunk]} { + IUnknown_AddRef $iunk + } + if {$raw} { + return $iunk + } else { + return [make_interface_proxy $iunk] + } + } + } + return [lindex $variant 1] +} + +proc twapi::variant_type {variant} { + return [lindex $variant 0] +} + +proc twapi::vt_null {} { + return [tclcast null ""] +} + +proc twapi::vt_empty {} { + return [tclcast empty ""] +} + +# +# General dispatcher for callbacks from event sinks. Invokes the actual +# registered script after mapping dispid's +proc twapi::_eventsink_callback {comobj script callee args} { + # Check if the comobj is still active + if {[llength [info commands $comobj]] == 0} { + if {$::twapi::log_config(twapi_com)} { + debuglog "COM event received for inactive object" + } + return; # Object has gone away, ignore + } + + set retcode [catch { + # We are invoked with cooked values so no need to call variant_value + uplevel #0 $script [list $callee] $args + } result] + + if {$::twapi::log_config(twapi_com) && $retcode} { + debuglog "Event sink callback error ($retcode): $result\n$::errorInfo" + } + + # $retcode is returned as HRESULT by the Invoke + return -code $retcode $result +} + +# +# Return clsid from a string. If $clsid is a valid CLSID - returns as is +# else tries to convert it from progid. An error is generated if neither +# works +proc twapi::_convert_to_clsid {comid} { + if {! [Twapi_IsValidGUID $comid]} { + return [progid_to_clsid $comid] + } + return $comid +} + +# +# Format a prototype definition for human consumption +# Proto is in the form {DISPID LCID INVOKEFLAGS RETTYPE PARAMTYPES PARAMNAMES} +proc twapi::_format_prototype {name proto} { + set dispid_lcid [lindex $proto 0]/[lindex $proto 1] + set ret_type [_vttype_to_string [lindex $proto 3]] + set invkind [_invkind_to_string [lindex $proto 2]] + # Distinguish between no parameters and parameters not known + set paramstr "" + if {[llength $proto] > 4} { + set params {} + foreach param [lindex $proto 4] paramname [lindex $proto 5] { + if {[string length $paramname]} { + set paramname " $paramname" + } + lassign $param type paramdesc + set type [_vttype_to_string $type] + set parammods [_paramflags_to_tokens [lindex $paramdesc 0]] + if {[llength [lindex $paramdesc 1]]} { + # Default specified + lappend parammods "default:[lindex [lindex $paramdesc 1] 1]" + } + lappend params "\[$parammods\] $type$paramname" + } + set paramstr " ([join $params {, }])" + } + return "$dispid_lcid $invkind $ret_type ${name}${paramstr}" +} + +# Convert parameter modifiers to string tokens. +# modifiers is list of integer flags or tokens. +proc twapi::_paramflags_to_tokens {modifiers} { + array set tokens {} + foreach mod $modifiers { + if {! [string is integer -strict $mod]} { + # mod is a token itself + set tokens($mod) "" + } else { + foreach tok [_make_symbolic_bitmask $mod { + in 1 + out 2 + lcid 4 + retval 8 + optional 16 + hasdefault 32 + hascustom 64 + }] { + set tokens($tok) "" + } + } + } + + # For cosmetic reasons, in/out should be first and remaining sorted + # Also (in,out) -> inout + if {[info exists tokens(in)]} { + if {[info exists tokens(out)]} { + set inout [list inout] + unset tokens(in) + unset tokens(out) + } else { + set inout [list in] + unset tokens(in) + } + } else { + if {[info exists tokens(out)]} { + set inout [list out] + unset tokens(out) + } + } + + if {[info exists inout]} { + return [linsert [lsort [array names tokens]] 0 $inout] + } else { + return [lsort [array names tokens]] + } +} + +# +# Map method invocation code to string +# Return code itself if no match +proc twapi::_invkind_to_string {code} { + return [kl_get { + 1 func + 2 propget + 4 propput + 8 propputref + } $code $code] +} + +# +# Map string method invocation symbol to code +# Error if no match and not an integer +proc twapi::_string_to_invkind {s} { + if {[string is integer $s]} { return $s } + return [kl_get { + func 1 + propget 2 + propput 4 + propputref 8 + } $s] +} + + +# +# Convert a VT typedef to a string +# vttype may be nested +proc twapi::_vttype_to_string {vttype} { + set vts [_vtcode_to_string [lindex $vttype 0]] + if {[llength $vttype] < 2} { + return $vts + } + + return [list $vts [_vttype_to_string [lindex $vttype 1]]] +} + +# +# Convert VT codes to strings +proc twapi::_vtcode_to_string {vt} { + return [kl_get { + 2 i2 + 3 i4 + 4 r4 + 5 r8 + 6 cy + 7 date + 8 bstr + 9 idispatch + 10 error + 11 bool + 12 variant + 13 iunknown + 14 decimal + 16 i1 + 17 ui1 + 18 ui2 + 19 ui4 + 20 i8 + 21 ui8 + 22 int + 23 uint + 24 void + 25 hresult + 26 ptr + 27 safearray + 28 carray + 29 userdefined + 30 lpstr + 31 lpwstr + 36 record + } $vt $vt] +} + +proc twapi::_string_to_base_vt {tok} { + # Only maps base VT tokens to numeric value + # TBD - record and userdefined? + return [dict get { + i2 2 + i4 3 + r4 4 + r8 5 + cy 6 + date 7 + bstr 8 + idispatch 9 + error 10 + bool 11 + iunknown 13 + decimal 14 + i1 16 + ui1 17 + ui2 18 + ui4 19 + i8 20 + ui8 21 + int 22 + uint 23 + hresult 25 + userdefined 29 + record 36 + } [string tolower $tok]] + +} + +# +# Get ADSI provider service +proc twapi::_adsi {{prov WinNT} {path {//.}}} { + return [comobj_object "${prov}:$path"] +} + +# Get cached IDispatch and IUNknown IID's +proc twapi::_iid_iunknown {} { + return $::twapi::_name_to_iid_cache(iunknown) +} +proc twapi::_iid_idispatch {} { + return $::twapi::_name_to_iid_cache(idispatch) +} + +# +# Return IID and name given a IID or name +proc twapi::_resolve_iid {name_or_iid} { + + # IID -> name mapping is more efficient so first assume it is + # an IID else we will unnecessarily trundle through the whole + # registry area looking for an IID when we already have it + # Assume it is a name + set other [iid_to_name $name_or_iid] + if {$other ne ""} { + # It was indeed the IID. Return the pair + return [list $name_or_iid $other] + } + + # Else resolve as a name + set other [name_to_iid $name_or_iid] + if {$other ne ""} { + # Yep + return [list $other $name_or_iid] + } + + win32_error 0x80004002 "Could not find IID $name_or_iid" +} + + +namespace eval twapi { + # Enable use of TclOO for new Tcl versions. To override setting + # applications should define and set before sourcing this file. + variable use_tcloo_for_com 1 + if {![info exists use_tcloo_for_com]} { + set use_tcloo_for_com [package vsatisfies [package require Tcl] 8.6b2] + } + if {$use_tcloo_for_com} { + interp alias {} ::twapi::class {} ::oo::class + proc ::oo::define::twapi_exportall {} { + uplevel 1 export [info class methods [lindex [info level -1] 1] -private] + } + proc comobj? {cobj} { + # We do not want change the internal type so + # do not check for some types that + # could not be a comobj. In particular, + # if a list type, we do not even check + # because it cannot be a comobj and even checking + # will result in nested list types being + # destroyed which affects safearray type detection + # TBD - would it be faster to keep explicit track through + # a dictionary ? + if {[twapi::tcltype $cobj] in {bstr empty null bytecode TwapiOpaque list int double bytearray dict wideInt booleanString}} { + return 0 + } + set cobj [uplevel 1 [list namespace which -command $cobj]] + if {[info object isa object $cobj] && + [info object isa typeof $cobj ::twapi::Automation]} { + return 1 + } else { + return 0 + } + } + proc comobj_instances {} { + set comobj_classes [list ::twapi::Automation] + set objs {} + while {[llength $comobj_classes]} { + set comobj_classes [lassign $comobj_classes class] + lappend objs {*}[info class instances $class] + lappend comobj_classes {*}[info class subclasses $class] + } + # Get rid of dups which may occur if subclasses use + # multiple (diamond type) inheritance + return [lsort -unique $objs] + } + } else { + package require metoo + interp alias {} ::twapi::class {} ::metoo::class + namespace eval ::metoo::define { + proc twapi_exportall {args} { + # args is dummy to match metoo's class definition signature + # Nothing to do, all methods are metoo are public + } + } + proc comobj? {cobj} { + set cobj [uplevel 1 [list namespace which -command $cobj]] + return [metoo::introspect object isa $cobj ::twapi::Automation] + } + proc comobj_instances {} { + return [metoo::introspect object list ::twapi::Automation] + } + } + + # The prototype cache is indexed a composite key consisting of + # - the GUID of the interface, + # - the name of the function + # - the LCID + # - the invocation kind (as an integer) + # Each value contains the full prototype in a form + # that can be passed to IDispatch_Invoke. This is a list with the + # elements {DISPID LCID INVOKEFLAGS RETTYPE PARAMTYPES PARAMNAMES} + # Here PARAMTYPES is a list each element of which describes a + # parameter in the following format: + # {TYPE {FLAGS DEFAULT} NAMEDARGVALUE} where DEFAULT is optional + # and NAMEDARGVALUE only appears (optionally) when the prototype is + # passed to Invoke, not in the cached prototype itself. + # PARAMNAMES is list of parameter names in order and is + # only present if PARAMTYPES is also present. + + variable _dispatch_prototype_cache + array set _dispatch_prototype_cache {} +} + + +interp alias {} twapi::_dispatch_prototype_get {} twapi::dispatch_prototype_get +proc twapi::dispatch_prototype_get {guid name lcid invkind vproto} { + variable _dispatch_prototype_cache + set invkind [::twapi::_string_to_invkind $invkind] + if {[info exists _dispatch_prototype_cache($guid,$name,$lcid,$invkind)]} { + # Note this may be null if that name does not exist in the interface + upvar 1 $vproto proto + set proto $_dispatch_prototype_cache($guid,$name,$lcid,$invkind) + return 1 + } + return 0 +} + +# Update a prototype in cache. Note lcid and invkind cannot be +# picked up from prototype since it might be empty. +interp alias {} twapi::_dispatch_prototype_set {} twapi::dispatch_prototype_set +proc twapi::dispatch_prototype_set {guid name lcid invkind proto} { + # If the prototype does not contain the 5th element (params) + # it is a constructed prototype and we do NOT cache it as the + # disp id can change. Note empty prototypes are cached so + # we don't keep looking up something that does not exist + # Bug 130 + + if {[llength $proto] == 4} { + return + } + + variable _dispatch_prototype_cache + set invkind [_string_to_invkind $invkind] + set _dispatch_prototype_cache($guid,$name,$lcid,$invkind) $proto + return +} + +# Explicitly set prototypes for a guid +# protolist is a list of alternating name and prototype pairs. +# Each prototype must contain the LCID and invkind fields +proc twapi::_dispatch_prototype_load {guid protolist} { + foreach {name proto} $protolist { + dispatch_prototype_set $guid $name [lindex $proto 1] [lindex $proto 2] $proto + } +} + +proc twapi::coclass_idispatch_guid {coclass_guid} { + variable _coclass_idispatch_guids + if {[info exists _coclass_idispatch_guids($coclass_guid)]} { + return $_coclass_idispatch_guids($coclass_guid) + } + return "" +} + +proc twapi::_parse_dispatch_paramdef {paramdef} { + set errormsg "Invalid parameter or return type declaration '$paramdef'" + + set paramregex {^(\[[^\]]*\])?\s*(\w+)\s*(\[\s*\])?\s*([*]?)\s*(\w+)?$} + if {![regexp $paramregex [string trim $paramdef] def attrs paramtype safearray ptr paramname]} { + error $errormsg + } + + if {[string length $paramname]} { + lappend paramnames $paramname + } + # attrs can be in, out, opt separated by spaces + set paramflags 0 + foreach attr [string range $attrs 1 end-1] { + switch -exact -- $attr { + in {set paramflags [expr {$paramflags | 1}]} + out {set paramflags [expr {$paramflags | 2}]} + inout {set paramflags [expr {$paramflags | 3}]} + opt - + optional {set paramflags [expr {$paramflags | 16}]} + default {error "Unknown parameter attribute $attr"} + } + } + if {($paramflags & 3) == 0} { + set paramflags [expr {$paramflags | 1}]; # in param if unspecified + } + # Resolve parameter type. It can be + # - a safearray of base types or "variant"s (not pointers) + # - a pointer to a base type + # - a pointer to a safearray + # - a base type or "variant" + switch -exact -- $paramtype { + variant { set paramtype 12 } + void { set paramtype 24 } + default { set paramtype [_string_to_base_vt $paramtype] } + } + if {[string length $safearray]} { + if {$paramtype == 24} { + # Safearray of type void is an invalid type decl + error $errormsg + } + set paramtype [list 27 $paramtype] + } + if {[string length $ptr]} { + if {$paramtype == 24} { + # Pointer to type void is an invalid type + error $errormsg + } + set paramtype [list 26 $paramtype] + } + + return [list $paramflags $paramtype $paramname] +} + +proc twapi::define_dispatch_prototypes {guid protos args} { + array set opts [parseargs args { + {lcid.int 0} + } -maxleftover 0] + + set guid [canonicalize_guid $guid] + + set defregx {^\s*(\w+)\s+(\d+)\s+(\w[^\(]*)\(([^\)]*)\)(.*)$} + set parsed_protos {} + # Loop picking out one prototype in each interation + while {[regexp $defregx $protos -> membertype memid rettype paramstring protos]} { + set params {} + set paramnames {} + foreach paramdef [split $paramstring ,] { + lassign [_parse_dispatch_paramdef $paramdef] paramflags paramtype paramname + if {[string length $paramname]} { + lappend paramnames $paramname + } + lappend params [list $paramtype [list $paramflags]] + } + if {[llength $paramnames] && + [llength $params] != [llength $paramnames]} { + error "Missing parameter name in '$paramstring'. All parameter names must be specified or none at all." + } + + lassign [_parse_dispatch_paramdef $rettype] _ rettype name + set invkind [_string_to_invkind $membertype] + set proto [list $memid $opts(lcid) $invkind $rettype $params $paramnames] + lappend parsed_protos $name $proto + } + + set protos [string trim $protos] + if {[string length $protos]} { + error "Invalid dispatch prototype: '$protos'" + } + + _dispatch_prototype_load $guid $parsed_protos +} + +# Used to track when interface proxies are renamed/deleted +proc twapi::_interface_proxy_tracer {ifc oldname newname op} { + variable _interface_proxies + if {$op eq "rename"} { + if {$oldname eq $newname} return + set _interface_proxies($ifc) $newname + } else { + unset _interface_proxies($ifc) + } +} + + +# Return a COM interface proxy object for the specified interface. +# If such an object already exists, it is returned. Otherwise a new one +# is created. $ifc must be a valid COM Interface pointer for which +# the caller is holding a reference. Caller relinquishes ownership +# of the interface and must solely invoke operations through the +# returned proxy object. When done with the object, call the Release +# method on it, NOT destroy. +# TBD - how does this interact with security blankets ? +proc twapi::make_interface_proxy {ifc} { + variable _interface_proxies + + if {[info exists _interface_proxies($ifc)]} { + set proxy $_interface_proxies($ifc) + $proxy AddRef + if {! [pointer_null? $ifc]} { + # Release the caller's ref to the interface since we are holding + # one in the proxy object + ::twapi::IUnknown_Release $ifc + } + } else { + if {[pointer_null? $ifc]} { + set proxy [INullProxy new $ifc] + } else { + set ifcname [pointer_type $ifc] + set proxy [${ifcname}Proxy new $ifc] + } + set _interface_proxies($ifc) $proxy + trace add command $proxy {rename delete} [list ::twapi::_interface_proxy_tracer $ifc] + } + return $proxy +} + +# "Null" object - clones IUnknownProxy but will raise error on method calls +# We could have inherited but IUnknownProxy assumes non-null ifc so it +# and its inherited classes do not have to check for null in every method. +twapi::class create ::twapi::INullProxy { + constructor {ifc} { + my variable _ifc + # We keep the interface pointer because it encodes type information + if {! [::twapi::pointer_null? $ifc]} { + error "Attempt to create a INullProxy with non-NULL interface" + } + + set _ifc $ifc + + my variable _nrefs; # Internal ref count (held by app) + set _nrefs 1 + } + + method @Null? {} { return 1 } + method @Type {} { + my variable _ifc + return [::twapi::pointer_type $_ifc] + } + method @Type? {type} { + my variable _ifc + return [::twapi::pointer? $_ifc $type] + } + method AddRef {} { + my variable _nrefs + # We maintain our own ref counts. _ifc is null so do not + # call the COM AddRef ! + incr _nrefs + } + + method Release {} { + my variable _nrefs + if {[incr _nrefs -1] == 0} { + my destroy + } + } + + method DebugRefCounts {} { + my variable _nrefs + + # Return out internal ref as well as the COM ones + # Note latter is always 0 since _ifc is always NULL. + return [list $_nrefs 0] + } + + method QueryInterface {name_or_iid} { + error "Attempt to call QueryInterface called on NULL pointer" + } + + method @QueryInterface {name_or_iid} { + error "Attempt to call QueryInterface called on NULL pointer" + } + + # Parameter is for compatibility with IUnknownProxy + method @Interface {{addref 1}} { + my variable _ifc + return $_ifc + } + + twapi_exportall +} + +twapi::class create ::twapi::IUnknownProxy { + # Note caller must hold ref on the ifc. This ref is passed to + # the proxy object and caller must not make use of that ref + # unless it does an AddRef on it. + constructor {ifc {objclsid ""}} { + if {[::twapi::pointer_null? $ifc]} { + error "Attempt to register a NULL interface" + } + + my variable _ifc + set _ifc $ifc + + my variable _clsid + set _clsid $objclsid + + my variable _blanket; # Security blanket + set _blanket [list ] + + # We keep an internal reference count instead of explicitly + # calling out to the object's AddRef/Release every time. + # When the internal ref count goes to 0, we will invoke the + # object's "native" Release. + # + # Note the primary purpose of maintaining our internal reference counts + # is not efficiency by shortcutting the "native" AddRefs. It is to + # prevent crashes by bad application code; we can just generate an + # error instead by having the command go away. + my variable _nrefs; # Internal ref count (held by app) + + set _nrefs 1 + } + + destructor { + my variable _ifc + ::twapi::IUnknown_Release $_ifc + } + + method AddRef {} { + my variable _nrefs + # We maintain our own ref counts. Not pass it on to the actual object + incr _nrefs + } + + method Release {} { + my variable _nrefs + if {[incr _nrefs -1] == 0} { + my destroy + } + } + + method DebugRefCounts {} { + my variable _nrefs + my variable _ifc + + # Return out internal ref as well as the COM ones + # Note latter are unstable and only to be used for + # debugging + twapi::IUnknown_AddRef $_ifc + return [list $_nrefs [twapi::IUnknown_Release $_ifc]] + } + + method QueryInterface {name_or_iid} { + my variable _ifc + lassign [::twapi::_resolve_iid $name_or_iid] iid name + return [::twapi::Twapi_IUnknown_QueryInterface $_ifc $iid $name] + } + + # Same as QueryInterface except return "" instead of exception + # if interface not found and returns proxy object instead of interface + method @QueryInterface {name_or_iid {set_blanket 0}} { + my variable _blanket + ::twapi::trap { + set proxy [::twapi::make_interface_proxy [my QueryInterface $name_or_iid]] + if {$set_blanket && [llength $_blanket]} { + $proxy @SetSecurityBlanket $_blanket + } + return $proxy + } onerror {TWAPI_WIN32 0x80004002} { + # No such interface, return "", don't generate error + return "" + } onerror {} { + if {[info exists proxy]} { + catch {$proxy Release} + } + rethrow + } + } + + method @Type {} { + my variable _ifc + return [::twapi::pointer_type $_ifc] + } + + method @Type? {type} { + my variable _ifc + return [::twapi::pointer? $_ifc $type] + } + + method @Null? {} { + my variable _ifc + return [::twapi::pointer_null? $_ifc] + } + + # Returns raw interface. Caller must call IUnknown_Release on it + # iff addref is passed as true (default) + method @Interface {{addref 1}} { + my variable _ifc + if {$addref} { + ::twapi::IUnknown_AddRef $_ifc + } + return $_ifc + } + + # Returns out class id - old deprecated - use GetCLSID + method @Clsid {} { + my variable _clsid + return $_clsid + } + + method @GetCLSID {} { + my variable _clsid + return $_clsid + } + + method @SetCLSID {clsid} { + my variable _clsid + set _clsid $clsid + return + } + + method @SetSecurityBlanket blanket { + my variable _ifc _blanket + # In-proc components will not support IClientSecurity interface + # and will raise an error. That's the for the caller to be careful + # about. + twapi::CoSetProxyBlanket $_ifc {*}$blanket + set _blanket $blanket + return + } + + method @GetSecurityBlanket {} { + my variable _blanket + return $_blanket + } + + + twapi_exportall +} + +twapi::class create ::twapi::IDispatchProxy { + superclass ::twapi::IUnknownProxy + + destructor { + my variable _typecomp + if {[info exists _typecomp] && $_typecomp ne ""} { + $_typecomp Release + } + next + } + + method GetTypeInfoCount {} { + my variable _ifc + return [::twapi::IDispatch_GetTypeInfoCount $_ifc] + } + + # names is list - method name followed by parameter names + # Returns list of name dispid pairs + method GetIDsOfNames {names {lcid 0}} { + my variable _ifc + return [::twapi::IDispatch_GetIDsOfNames $_ifc $names $lcid] + } + + # Get dispid of a method (without parameter names) + method @GetIDOfOneName {name {lcid 0}} { + return [lindex [my GetIDsOfNames [list $name] $lcid] 1] + } + + method GetTypeInfo {{infotype 0} {lcid 0}} { + my variable _ifc + if {$infotype != 0} {error "Parameter infotype must be 0"} + return [::twapi::IDispatch_GetTypeInfo $_ifc $infotype $lcid] + } + + method @GetTypeInfo {{lcid 0}} { + return [::twapi::make_interface_proxy [my GetTypeInfo 0 $lcid]] + } + + method Invoke {prototype args} { + my variable _ifc + if {[llength $prototype] == 0 && [llength $args] == 0} { + # Treat as a property get DISPID_VALUE (default value) + # {dispid=0, lcid=0 cmd=propget(2) ret type=bstr(8) {} (no params)} + set prototype {0 0 2 8 {}} + } else { + # TBD - optimize by precomputing if a prototype needs this processing + # If any arguments are comobjs, may need to replace with the + # IDispatch interface. + # Moreover, we have to manage the reference counts for both + # IUnknown and IDispatch - + # - If the parameter is an IN parameter, ref counts do not need + # to change. + # - If the parameter is an OUT parameter, we are not passing + # an interface in, so nothing to do + # - If the parameter is an INOUT, we need to AddRef it since + # the COM method will Release it when storing a replacement + # HERE WE ONLY DO THE CHECK FOR COMOBJ. The AddRef checks are + # DONE IN THE C CODE (if necessary) + + set iarg -1 + set args2 {} + foreach arg $args { + incr iarg + # TBD - optimize this loop + set argtype [lindex $prototype 4 $iarg 0] + set argflags 0 + if {[llength [lindex $prototype 4 $iarg 1]]} { + set argflags [lindex $prototype 4 $iarg 1 0] + } + if {$argflags & 1} { + # IN param + if {$argflags & 2} { + # IN/OUT + # We currently do NOT handle a In/Out - skip for now TBD + # In the future we will have to check contents of + # the passed arg as a variable in the CALLER's context + } else { + # Pure IN param. Check if it is VT_DISPATCH or + # VT_VARIANT. Else nothing + # to do + if {[lindex $argtype 0] == 26} { + # Pointer, get base type + set argtype [lindex $argtype 1] + } + if {[lindex $argtype 0] == 9 || [lindex $argtype 0] == 12} { + # If a comobj was passed, need to extract the + # dispatch pointer. + if {[twapi::comobj? $arg]} { + # Note we do not addref when getting the interface + # (last param 0) because not necessary for IN + # params, AND it is the C code's responsibility + # anyways + set arg [$arg -interface 0] + } + } + } + + } else { + # Not an IN param. Nothing to be done + } + + lappend args2 $arg + } + set args $args2 + } + + # The uplevel is so that if some parameters are output, the varnames + # are resolved in caller + uplevel 1 [list ::twapi::IDispatch_Invoke $_ifc $prototype] $args + } + + # Methods are tried in the order specified by invkinds. + method @Invoke {name invkinds lcid params {namedargs {}}} { + if {$name eq ""} { + # Default method + return [uplevel 1 [list [self] Invoke {}] $params] + } + set nparams [llength $params] + + # We will try for each invkind to match. matches can be of + # different degrees, in descending priority - + # 1. prototype has parameter info and num params match exactly + # 2. prototype has parameter info and num params is greater + # than supplied arguments (assumes others have defaults) + # 3. prototype has no parameter information + # Within these classes, the order of invkinds determines + # priority + + if {$name eq "_NewEnum"} { + # Special case property to retrieve iterator. Some objects + # call it _NewEnum, others NewEnum. The disp id must always + # be -4 so we hard code that instead + # DISPID=-4 LCID=0 INVOKE=2(propget) RETTYPE=13(IUnknown) no parameters + set class1 [list {-4 0 2 13 {} {}}] + } else { + foreach invkind $invkinds { + set proto [my @Prototype $name $invkind $lcid] + if {[llength $proto]} { + if {[llength $proto] < 5} { + # No parameter information + lappend class3 $proto + } else { + if {[llength [lindex $proto 4]] == $nparams} { + lappend class1 $proto + break; # Class 1 match, no need to try others + } elseif {[llength [lindex $proto 4]] > $nparams} { + lappend class2 $proto + } else { + # Ignore - proto has fewer than supplied params + # Could not be a match + } + } + } + } + } + # For exact match (class1), we do not need the named + # arguments as positional arguments take priority. When + # number of passed parameters is fewer than those in + # prototype, check named arguments and use those + # values. If no parameter information, we can't use named + # arguments anyways. + + if {[info exists class1]} { + set matched_proto [lindex $class1 0] + } elseif {[info exists class2]} { + set matched_proto [lindex $class2 0] + # If we are passed named arguments AND the prototype also + # has parameter name information, replace the default values + # in the parameter definitions with the named arg value if + # it exists. + if {[llength $namedargs] && + [llength [set paramnames [lindex $matched_proto 5]]]} { + foreach {paramname paramval} $namedargs { + set paramindex [lsearch -nocase $paramnames $paramname] + if {$paramindex < 0} { + twapi::win32_error 0x80020004 "No parameter with name '$paramname' found for method '$name'" + } + + # Set the default value field of the + # appropriate parameter to the named arg value + set paramtype [lindex $matched_proto 4 $paramindex 0] + + # If parameter is VT_DISPATCH or VT_VARIANT, + # convert from comobj if necessary. + if {$paramtype == 9 || $paramtype == 12} { + if {[::twapi::comobj? $paramval]} { + # Note no AddRef when getting the interface + # (last param 0) because it is the C code's + # responsibility based on in/out direction + set paramval [$paramval -interface 0] + } + } + + # Replace the default value field for that param def + lset matched_proto 4 $paramindex [linsert [lrange [lindex $matched_proto 4 $paramindex] 0 1] 2 $paramval] + } + } + } elseif {[info exists class3]} { + set matched_proto [lindex $class3 0] + } + + if {[info exists matched_proto]} { + # Need uplevel so by-ref param vars are resolved correctly + return [uplevel 1 [list [self] Invoke $matched_proto] $params] + } + + # No prototype via typecomp / typeinfo available. + # No lcid worked. + # We have to use the last resort of GetIDsOfNames + set dispid [my @GetIDOfOneName [list $name] 0] + # TBD - should we cache result ? Probably not. + if {$dispid eq ""} { + twapi::win32_error 0x80020003 "No property or method found with name '$name'." + } + + # Try all invocation types except last in turn. If error is "Member not + # found" try the next prototype. + foreach invkind [lrange $invkinds 0 end-1] { + # Note params field (last) is missing signifying we do not + # know prototypes + set matched_proto [list $dispid 0 $invkind 8] + if {![catch { + uplevel 1 [list [self] Invoke $matched_proto] $params + } result ropts]} { + return $result + } + # If member not found error, keep going. Other errors, throw + lassign [dict get $ropts -errorcode] fac winerror + if {$fac ne "TWAPI_WIN32" && $winerror != -2147352573} { + # Some other error. + return -options $ropts $result + } + } + # Try the last one and hope for the best + set matched_proto [list $dispid 0 [lindex $invkinds end] 8] + return [uplevel 1 [list [self] Invoke $matched_proto] $params] + } + + # Get prototype that match the specified name + method @Prototype {name invkind lcid} { + my variable _ifc _guid _typecomp + + # Always need the GUID so get it we have not done so already + if {![info exists _guid]} { + my @InitTypeCompAndGuid + } + # Note above call may still have failed to init _guid + + # If we have been through here before and have our guid, + # check if a prototype exists and return it. + if {[info exists _guid] && $_guid ne "" && + [::twapi::_dispatch_prototype_get $_guid $name $lcid $invkind proto]} { + return $proto + } + + # Not in cache, have to look for it + # Use the ITypeComp for this interface if we do not + # already have it. We trap any errors because we will retry with + # different LCID's below. + set proto {} + if {![info exists _typecomp]} { + my @InitTypeCompAndGuid + } + if {$_typecomp ne ""} { + ::twapi::trap { + + set invkind [::twapi::_string_to_invkind $invkind] + set lhash [::twapi::LHashValOfName $lcid $name] + + if {![catch {$_typecomp Bind $name $lhash $invkind} binddata] && + [llength $binddata]} { + lassign $binddata type data ifc + if {$type eq "funcdesc" || + ($type eq "vardesc" && [::twapi::kl_get $data varkind] == 3)} { + set params {} + set bindti [::twapi::make_interface_proxy $ifc] + ::twapi::trap { + set params [::twapi::_resolve_params_for_prototype $bindti [::twapi::kl_get $data lprgelemdescParam]] + # Param names are needed for named arguments. Index 0 is method name so skip it + if {[catch {lrange [$bindti GetNames [twapi::kl_get $data memid]] 1 end} paramnames]} { + set paramnames {} + } + } finally { + $bindti Release + } + set proto [list [::twapi::kl_get $data memid] \ + $lcid \ + $invkind \ + [::twapi::kl_get $data elemdescFunc.tdesc] \ + $params $paramnames] + } else { + ::twapi::IUnknown_Release $ifc; # Don't need ifc but must release + twapi::debuglog "IDispatchProxy::@Prototype: Unexpected Bind type: $type, data: $data" + } + } + } onerror {} { + # Ignore and retry with other LCID's below + } + } + + + # If we do not have a guid return because even if we do not + # have a proto yet, falling through to try another lcid will not + # help and in fact will cause infinite recursion. + + if {$_guid eq ""} { + return $proto + } + + # We do have a guid, store the proto in cache (even if negative) + ::twapi::dispatch_prototype_set $_guid $name $lcid $invkind $proto + + # If we have the proto return it + if {[llength $proto]} { + return $proto + } + + # Could not find a matching prototype from the typeinfo/typecomp. + # We are not done yet. We will try and fall back to other lcid's + # Note we do this AFTER setting the prototype in the cache. That + # way we prevent (infinite) mutual recursion between lcid fallbacks. + # The fallback sequence is $lcid -> 0 -> 1033 + # (1033 is US English). Note lcid could itself be 1033 + # default and land up being checked twice times but that's + # ok since that's a one-time thing, and not very expensive either + # since the second go-around will hit the cache (negative). + # Note the time this is really useful is when the cache has + # been populated explicitly from a type library since in that + # case many interfaces land up with a US ENglish lcid (MSI being + # just one example) + + if {$lcid == 0} { + # Note this call may further recurse and return either a + # proto or empty (fail) + set proto [my @Prototype $name $invkind 1033] + } else { + set proto [my @Prototype $name $invkind 0] + } + + # Store it as *original* lcid. + ::twapi::dispatch_prototype_set $_guid $name $lcid $invkind $proto + + return $proto + } + + + # Initialize _typecomp and _guid. Not in constructor because may + # not always be required. Raises error if not available + method @InitTypeCompAndGuid {} { + my variable _guid _typecomp + + if {[info exists _typecomp]} { + # Based on code below, if _typecomp exists + # _guid also exists so no need to check for that + return + } + + ::twapi::trap { + set ti [my @GetTypeInfo 0] + } onerror {} { + # We do not raise an error because + # even without the _typecomp we can try invoking + # methods via IDispatch::GetIDsOfNames + twapi::debuglog "Could not ITypeInfo: [twapi::trapresult]" + if {![info exists _guid]} { + # Do not overwrite if already set thru @SetGuid or constructor + # Set to empty otherwise so we know we tried and failed + set _guid "" + } + set _typecomp "" + return + } + + ::twapi::trap { + # In case of dual interfaces, we need the typeinfo for the + # dispatch. Again, errors handled in try handlers + set attr [$ti GetTypeAttr] + switch -exact -- [::twapi::kl_get $attr typekind] { + 4 { + # Dispatch type, fine, just what we want + } + 3 { + # Interface type, Get the dispatch interface. If that fails, + # don't raise an error for the same reason as above + # if the interface itself is marked dispatchable + if {[catch { + $ti @GetRefTypeInfo [$ti GetRefTypeOfImplType -1] + } ti2 eropts]} { + # 4096 -> TYPEFLAG_FDISPATCHABLE + if {[::twapi::kl_get $attr wTypeFlags] & 4096} { + if {![info exists _guid]} { + # Do not overwrite if already set thru @SetGuid or constructor + # Set to empty otherwise so we know we tried and failed + # TBD - should we set _guid to [kl_get $attr guid] ? + set _guid "" + } + set _typecomp "" + return; # Note the finally clause will release $ti + } else { + # TBD - should we ignore errors even if dispatchable flag is not set? + return -options $eropts $ti2 + } + } + $ti Release + set ti $ti2 + } + default { + error "Interface is not a dispatch interface" + } + } + if {![info exists _guid]} { + # _guid might have already been valid, do not overwrite + set _guid [::twapi::kl_get [$ti GetTypeAttr] guid] + } + set _typecomp [$ti @GetTypeComp]; # ITypeComp + } finally { + $ti Release + } + } + + # Some COM objects like MSI do not have TypeInfo interfaces from + # where the GUID and TypeComp can be extracted. So we allow caller + # to explicitly set the GUID so we can look up methods in the + # dispatch prototype cache if it was populated directly by the + # application. If guid is not a valid GUID, an attempt is made + # to look it up as an IID name. + method @SetGuid {guid} { + my variable _guid + if {$guid eq ""} { + if {![info exists _guid]} { + my @InitTypeCompAndGuid + } + } else { + if {![::twapi::Twapi_IsValidGUID $guid]} { + set resolved_guid [::twapi::name_to_iid $guid] + if {$resolved_guid eq ""} { + error "Could not resolve $guid to a Interface GUID." + } + set guid $resolved_guid + } + + if {[info exists _guid] && $_guid ne ""} { + if {[string compare -nocase $guid $_guid]} { + error "Attempt to set the GUID to $guid when the dispatch proxy has already been initialized to $_guid" + } + } else { + set _guid $guid + } + } + + return $_guid + } + + method @GetCoClassTypeInfo {} { + my variable _ifc + + # We can get the typeinfo for the coclass in one of two ways: + # If the object supports IProvideClassInfo, we use it. Else + # we try the following: + # - from the idispatch, we get its typeinfo + # - from the typeinfo, we get the containing typelib + # - then we search the typelib for the coclass clsid + + ::twapi::trap { + set pci_ifc [my QueryInterface IProvideClassInfo] + set ti_ifc [::twapi::IProvideClassInfo_GetClassInfo $pci_ifc] + return [::twapi::make_interface_proxy $ti_ifc] + } onerror {} { + # Ignore - try the longer route if we were given the coclass clsid + } finally { + if {[info exists pci_ifc]} { + ::twapi::IUnknown_Release $pci_ifc + } + # Note - do not do anything with ti_ifc here, EVEN on error + } + + set co_clsid [my @Clsid] + if {$co_clsid eq ""} { + # E_FAIL + twapi::win32_error 0x80004005 "Could not get ITypeInfo for coclass: object does not support IProvideClassInfo and clsid not specified." + } + + set ti [my @GetTypeInfo] + ::twapi::trap { + set tl [lindex [$ti @GetContainingTypeLib] 0] + if {0} { + $tl @Foreach -guid $co_clsid -type coclass coti { + break + } + if {[info exists coti]} { + return $coti + } + } else { + return [$tl @GetTypeInfoOfGuid $co_clsid] + } + twapi::win32_error 0x80004005 "Could not find coclass."; # E_FAIL + } finally { + if {[info exists ti]} { + $ti Release + } + if {[info exists tl]} { + $tl Release + } + } + } + + twapi_exportall +} + + +twapi::class create ::twapi::IDispatchExProxy { + superclass ::twapi::IDispatchProxy + + method DeleteMemberByDispID {dispid} { + my variable _ifc + return [::twapi::IDispatchEx_DeleteMemberByDispID $_ifc $dispid] + } + + method DeleteMemberByName {name {lcid 0}} { + my variable _ifc + return [::twapi::IDispatchEx_DeleteMemberByName $_ifc $name $lcid] + } + + method GetDispID {name flags} { + my variable _ifc + return [::twapi::IDispatchEx_GetDispID $_ifc $name $flags] + } + + method GetMemberName {dispid} { + my variable _ifc + return [::twapi::IDispatchEx_GetMemberName $_ifc $dispid] + } + + method GetMemberProperties {dispid flags} { + my variable _ifc + return [::twapi::IDispatchEx_GetMemberProperties $_ifc $dispid $flags] + } + + # For some reason, order of args is different for this call! + method GetNextDispID {flags dispid} { + my variable _ifc + return [::twapi::IDispatchEx_GetNextDispID $_ifc $flags $dispid] + } + + method GetNameSpaceParent {} { + my variable _ifc + return [::twapi::IDispatchEx_GetNameSpaceParent $_ifc] + } + + method @GetNameSpaceParent {} { + return [::twapi::make_interface_proxy [my GetNameSpaceParent]] + } + + method @Prototype {name invkind {lcid 0}} { + set invkind [::twapi::_string_to_invkind $invkind] + + # First try IDispatch + ::twapi::trap { + set proto [next $name $invkind $lcid] + if {[llength $proto]} { + return $proto + } + # Note negative results ignored, as new members may be added/deleted + # to an IDispatchEx at any time. We will try below another way. + + } onerror {} { + # Ignore the error - we will try below using another method + } + + # Not a simple dispatch interface method. Could be expando + # type which is dynamically created. NOTE: The member is NOT + # created until the GetDispID call is made. + + # 10 -> case insensitive, create if required + set dispid [my GetDispID $name 10] + + # IMPORTANT : prototype retrieval results MUST NOT be cached since + # underlying object may add/delete members at any time. + + # No type information is available for dynamic members. + # TBD - is that really true? + + # Invoke kind - 1 (method), 2 (propget), 4 (propput) + if {$invkind == 1} { + # method + set flags 0x100 + } elseif {$invkind == 2} { + # propget + set flags 0x1 + } elseif {$invkind == 4} { + # propput + set flags 0x4 + } elseif {$invkind == 8 } { + # propputref + set flags 0x10 + } else { + error "Internal error: Invalid invkind value $invkind" + } + + # Try at least getting the invocation type but even that is not + # supported by all objects in which case we assume it can be invoked. + # TBD - in that case, why even bother doing GetMemberProperties? + if {! [catch { + set flags [expr {[my GetMemberProperties 0x115] & $flags}] + }]} { + if {! $flags} { + return {}; # EMpty proto -> no valid name for this invkind + } + } + + # Valid invkind or object does not support GetMemberProperties + # Return type is 8 (BSTR) but does not really matter as + # actual type will be set based on what is returned. + return [list $dispid $lcid $invkind 8] + } + + twapi_exportall +} + + +# ITypeInfo +#----------- + +twapi::class create ::twapi::ITypeInfoProxy { + superclass ::twapi::IUnknownProxy + + method GetRefTypeOfImplType {index} { + my variable _ifc + return [::twapi::ITypeInfo_GetRefTypeOfImplType $_ifc $index] + } + + method GetDocumentation {memid} { + my variable _ifc + return [::twapi::ITypeInfo_GetDocumentation $_ifc $memid] + } + + method GetImplTypeFlags {index} { + my variable _ifc + return [::twapi::ITypeInfo_GetImplTypeFlags $_ifc $index] + } + + method GetNames {index} { + my variable _ifc + return [::twapi::ITypeInfo_GetNames $_ifc $index] + } + + method GetTypeAttr {} { + my variable _ifc + return [::twapi::ITypeInfo_GetTypeAttr $_ifc] + } + + method GetFuncDesc {index} { + my variable _ifc + return [::twapi::ITypeInfo_GetFuncDesc $_ifc $index] + } + + method GetVarDesc {index} { + my variable _ifc + return [::twapi::ITypeInfo_GetVarDesc $_ifc $index] + } + + method GetIDsOfNames {names} { + my variable _ifc + return [::twapi::ITypeInfo_GetIDsOfNames $_ifc $names] + } + + method GetRefTypeInfo {hreftype} { + my variable _ifc + return [::twapi::ITypeInfo_GetRefTypeInfo $_ifc $hreftype] + } + + method @GetRefTypeInfo {hreftype} { + return [::twapi::make_interface_proxy [my GetRefTypeInfo $hreftype]] + } + + method GetTypeComp {} { + my variable _ifc + return [::twapi::ITypeInfo_GetTypeComp $_ifc] + } + + method @GetTypeComp {} { + return [::twapi::make_interface_proxy [my GetTypeComp]] + } + + method GetContainingTypeLib {} { + my variable _ifc + return [::twapi::ITypeInfo_GetContainingTypeLib $_ifc] + } + + method @GetContainingTypeLib {} { + lassign [my GetContainingTypeLib] itypelib index + return [list [::twapi::make_interface_proxy $itypelib] $index] + } + + method @GetRefTypeInfoFromIndex {index} { + return [my @GetRefTypeInfo [my GetRefTypeOfImplType $index]] + } + + # Friendlier version of GetTypeAttr + method @GetTypeAttr {args} { + + array set opts [::twapi::parseargs args { + all + guid + lcid + constructorid + destructorid + schema + instancesize + typekind + fncount + varcount + interfacecount + vtblsize + alignment + majorversion + minorversion + aliasdesc + flags + idldesc + memidmap + } -maxleftover 0] + + array set data [my GetTypeAttr] + set result [list ] + foreach {opt key} { + guid guid + lcid lcid + constructorid memidConstructor + destructorid memidDestructor + schema lpstrSchema + instancesize cbSizeInstance + fncount cFuncs + varcount cVars + interfacecount cImplTypes + vtblsize cbSizeVft + alignment cbAlignment + majorversion wMajorVerNum + minorversion wMinorVerNum + aliasdesc tdescAlias + } { + if {$opts(all) || $opts($opt)} { + lappend result -$opt $data($key) + } + } + + if {$opts(all) || $opts(typekind)} { + set typekind $data(typekind) + if {[info exists ::twapi::_typekind_map($typekind)]} { + set typekind $::twapi::_typekind_map($typekind) + } + lappend result -typekind $typekind + } + + if {$opts(all) || $opts(flags)} { + lappend result -flags [::twapi::_make_symbolic_bitmask $data(wTypeFlags) { + appobject 1 + cancreate 2 + licensed 4 + predeclid 8 + hidden 16 + control 32 + dual 64 + nonextensible 128 + oleautomation 256 + restricted 512 + aggregatable 1024 + replaceable 2048 + dispatchable 4096 + reversebind 8192 + proxy 16384 + }] + } + + if {$opts(all) || $opts(idldesc)} { + lappend result -idldesc [::twapi::_make_symbolic_bitmask $data(idldescType) { + in 1 + out 2 + lcid 4 + retval 8 + }] + } + + if {$opts(all) || $opts(memidmap)} { + set memidmap [list ] + for {set i 0} {$i < $data(cFuncs)} {incr i} { + array set fninfo [my @GetFuncDesc $i -memid -name] + lappend memidmap $fninfo(-memid) $fninfo(-name) + } + lappend result -memidmap $memidmap + } + + return $result + } + + # + # Get a variable description associated with a type + method @GetVarDesc {index args} { + # TBD - add support for retrieving elemdescVar.paramdesc fields + + array set opts [::twapi::parseargs args { + all + name + memid + schema + datatype + value + valuetype + varkind + flags + } -maxleftover 0] + + array set data [my GetVarDesc $index] + + set result [list ] + foreach {opt key} { + memid memid + schema lpstrSchema + datatype elemdescVar.tdesc + } { + if {$opts(all) || $opts($opt)} { + lappend result -$opt $data($key) + } + } + + + if {$opts(all) || $opts(value)} { + if {[info exists data(lpvarValue)]} { + # Const value + lappend result -value [lindex $data(lpvarValue) 1] + } else { + lappend result -value $data(oInst) + } + } + + if {$opts(all) || $opts(valuetype)} { + if {[info exists data(lpvarValue)]} { + lappend result -valuetype [lindex $data(lpvarValue) 0] + } else { + lappend result -valuetype int + } + } + + if {$opts(all) || $opts(varkind)} { + lappend result -varkind [::twapi::kl_get { + 0 perinstance + 1 static + 2 const + 3 dispatch + } $data(varkind) $data(varkind)] + } + + if {$opts(all) || $opts(flags)} { + lappend result -flags [::twapi::_make_symbolic_bitmask $data(wVarFlags) { + readonly 1 + source 2 + bindable 4 + requestedit 8 + displaybind 16 + defaultbind 32 + hidden 64 + restricted 128 + defaultcollelem 256 + uidefault 512 + nonbrowsable 1024 + replaceable 2048 + immediatebind 4096 + }] + } + + if {$opts(all) || $opts(name)} { + set result [concat $result [my @GetDocumentation $data(memid) -name]] + } + + return $result + } + + method @GetFuncDesc {index args} { + array set opts [::twapi::parseargs args { + all + name + memid + funckind + invkind + callconv + params + paramnames + flags + datatype + resultcodes + vtbloffset + } -maxleftover 0] + + array set data [my GetFuncDesc $index] + set result [list ] + + if {$opts(all) || $opts(paramnames)} { + lappend result -paramnames [lrange [my GetNames $data(memid)] 1 end] + } + foreach {opt key} { + memid memid + vtbloffset oVft + datatype elemdescFunc.tdesc + resultcodes lprgscode + } { + if {$opts(all) || $opts($opt)} { + lappend result -$opt $data($key) + } + } + + if {$opts(all) || $opts(funckind)} { + lappend result -funckind [::twapi::kl_get { + 0 virtual + 1 purevirtual + 2 nonvirtual + 3 static + 4 dispatch + } $data(funckind) $data(funckind)] + } + + if {$opts(all) || $opts(invkind)} { + lappend result -invkind [::twapi::_string_to_invkind $data(invkind)] + } + + if {$opts(all) || $opts(callconv)} { + lappend result -callconv [::twapi::kl_get { + 0 fastcall + 1 cdecl + 2 pascal + 3 macpascal + 4 stdcall + 5 fpfastcall + 6 syscall + 7 mpwcdecl + 8 mpwpascal + } $data(callconv) $data(callconv)] + } + + if {$opts(all) || $opts(flags)} { + lappend result -flags [::twapi::_make_symbolic_bitmask $data(wFuncFlags) { + restricted 1 + source 2 + bindable 4 + requestedit 8 + displaybind 16 + defaultbind 32 + hidden 64 + usesgetlasterror 128 + defaultcollelem 256 + uidefault 512 + nonbrowsable 1024 + replaceable 2048 + immediatebind 4096 + }] + } + + if {$opts(all) || $opts(params)} { + set params [list ] + foreach param $data(lprgelemdescParam) { + lassign $param paramtype paramdesc + set paramflags [::twapi::_paramflags_to_tokens [lindex $paramdesc 0]] + if {[llength $paramdesc] > 1} { + # There is a default value associated with the parameter + lappend params [list $paramtype $paramflags [lindex $paramdesc 1]] + } else { + lappend params [list $paramtype $paramflags] + } + } + lappend result -params $params + } + + if {$opts(all) || $opts(name)} { + set result [concat $result [my @GetDocumentation $data(memid) -name]] + } + + return $result + } + + # + # Get documentation for a element of a type + method @GetDocumentation {memid args} { + array set opts [::twapi::parseargs args { + all + name + docstring + helpctx + helpfile + } -maxleftover 0] + + lassign [my GetDocumentation $memid] name docstring helpctx helpfile + + set result [list ] + foreach opt {name docstring helpctx helpfile} { + if {$opts(all) || $opts($opt)} { + lappend result -$opt [set $opt] + } + } + return $result + } + + method @GetName {{memid -1}} { + return [lindex [my @GetDocumentation $memid -name] 1] + } + + method @GetImplTypeFlags {index} { + return [::twapi::_make_symbolic_bitmask \ + [my GetImplTypeFlags $index] \ + { + default 1 + source 2 + restricted 4 + defaultvtable 8 + }] + } + + # + # Get the typeinfo for the default source interface of a coclass + # This object must be the typeinfo of the coclass + method @GetDefaultSourceTypeInfo {} { + set count [lindex [my @GetTypeAttr -interfacecount] 1] + for {set i 0} {$i < $count} {incr i} { + set flags [my GetImplTypeFlags $i] + # default 0x1, source 0x2 + if {($flags & 3) == 3} { + # Our source interface implementation can only handle IDispatch + # so check if the source interface is that else keep looking. + # We even ignore dual interfaces because we cannot then + # assume caller will use the dispatch version + set ti [my @GetRefTypeInfoFromIndex $i] + array set typeinfo [$ti GetTypeAttr] + # typekind == 4 -> IDispatch, + # flags - 0x1000 -> dispatchable, 0x40 -> dual + if {$typeinfo(typekind) == 4 && + ($typeinfo(wTypeFlags) & 0x1000) && + !($typeinfo(wTypeFlags) & 0x40)} { + return $ti + } + $ti destroy + } + } + return "" + } + + twapi_exportall +} + + +# ITypeLib +#---------- + +twapi::class create ::twapi::ITypeLibProxy { + superclass ::twapi::IUnknownProxy + + method GetDocumentation {index} { + my variable _ifc + return [::twapi::ITypeLib_GetDocumentation $_ifc $index] + } + method GetTypeInfoCount {} { + my variable _ifc + return [::twapi::ITypeLib_GetTypeInfoCount $_ifc] + } + method GetTypeInfoType {index} { + my variable _ifc + return [::twapi::ITypeLib_GetTypeInfoType $_ifc $index] + } + method GetLibAttr {} { + my variable _ifc + return [::twapi::ITypeLib_GetLibAttr $_ifc] + } + method GetTypeInfo {index} { + my variable _ifc + return [::twapi::ITypeLib_GetTypeInfo $_ifc $index] + } + method @GetTypeInfo {index} { + return [::twapi::make_interface_proxy [my GetTypeInfo $index]] + } + method GetTypeInfoOfGuid {guid} { + my variable _ifc + return [::twapi::ITypeLib_GetTypeInfoOfGuid $_ifc $guid] + } + method @GetTypeInfoOfGuid {guid} { + return [::twapi::make_interface_proxy [my GetTypeInfoOfGuid $guid]] + } + method @GetTypeInfoType {index} { + set typekind [my GetTypeInfoType $index] + if {[info exists ::twapi::_typekind_map($typekind)]} { + set typekind $::twapi::_typekind_map($typekind) + } + return $typekind + } + + method @GetDocumentation {id args} { + array set opts [::twapi::parseargs args { + all + name + docstring + helpctx + helpfile + } -maxleftover 0] + + lassign [my GetDocumentation $id] name docstring helpctx helpfile + set result [list ] + foreach opt {name docstring helpctx helpfile} { + if {$opts(all) || $opts($opt)} { + lappend result -$opt [set $opt] + } + } + return $result + } + + method @GetName {} { + return [lindex [my GetDocumentation -1] 0] + } + + method @GetLibAttr {args} { + array set opts [::twapi::parseargs args { + all + guid + lcid + syskind + majorversion + minorversion + flags + } -maxleftover 0] + + array set data [my GetLibAttr] + set result [list ] + foreach {opt key} { + guid guid + lcid lcid + majorversion wMajorVerNum + minorversion wMinorVerNum + } { + if {$opts(all) || $opts($opt)} { + lappend result -$opt $data($key) + } + } + + if {$opts(all) || $opts(flags)} { + lappend result -flags [::twapi::_make_symbolic_bitmask $data(wLibFlags) { + restricted 1 + control 2 + hidden 4 + hasdiskimage 8 + }] + } + + if {$opts(all) || $opts(syskind)} { + lappend result -syskind [::twapi::kl_get { + 0 win16 + 1 win32 + 2 mac + } $data(syskind) $data(syskind)] + } + + return $result + } + + # + # Iterate through a typelib. Caller is responsible for releasing + # each ITypeInfo passed to it + # + method @Foreach {args} { + + array set opts [::twapi::parseargs args { + type.arg + name.arg + guid.arg + } -maxleftover 2 -nulldefault] + + if {[llength $args] != 2} { + error "Syntax error: Should be '[self] @Foreach ?options? VARNAME SCRIPT'" + } + + lassign $args varname script + upvar $varname varti + + set count [my GetTypeInfoCount] + for {set i 0} {$i < $count} {incr i} { + if {$opts(type) ne "" && $opts(type) ne [my @GetTypeInfoType $i]} { + continue; # Type does not match + } + if {$opts(name) ne "" && + [string compare -nocase $opts(name) [lindex [my @GetDocumentation $i -name] 1]]} { + continue; # Name does not match + } + set ti [my @GetTypeInfo $i] + if {$opts(guid) ne ""} { + if {[string compare -nocase [lindex [$ti @GetTypeAttr -guid] 1] $opts(guid)]} { + $ti Release + continue + } + } + set varti $ti + set ret [catch {uplevel 1 $script} result] + switch -exact -- $ret { + 1 { + error $result $::errorInfo $::errorCode + } + 2 { + return -code return $result; # TCL_RETURN + } + 3 { + set i $count; # TCL_BREAK + } + } + } + return + } + + method @Register {path {helppath ""}} { + my variable _ifc + ::twapi::RegisterTypeLib $_ifc $path $helppath + } + + method @LoadDispatchPrototypes {} { + set data [my @Read -type dispatch] + if {![dict exists $data dispatch]} { + return + } + + dict for {guid guiddata} [dict get $data dispatch] { + foreach type {methods properties} { + if {[dict exists $guiddata -$type]} { + dict for {name namedata} [dict get $guiddata -$type] { + dict for {lcid lciddata} $namedata { + dict for {invkind proto} $lciddata { + ::twapi::dispatch_prototype_set \ + $guid $name $lcid $invkind $proto + } + } + } + } + } + } + } + + method @Text {args} { + array set opts [::twapi::parseargs args { + type.arg + name.arg + } -maxleftover 0 -nulldefault] + + set text {} + my @Foreach -type $opts(type) -name $opts(name) ti { + ::twapi::trap { + array set attrs [$ti @GetTypeAttr -all] + set docs [$ti @GetDocumentation -1 -name -docstring] + set desc "[string totitle $attrs(-typekind)] [::twapi::kl_get $docs -name] $attrs(-guid) - [::twapi::kl_get $docs -docstring]\n" + switch -exact -- $attrs(-typekind) { + record - + union - + enum { + for {set j 0} {$j < $attrs(-varcount)} {incr j} { + array set vardata [$ti @GetVarDesc $j -all] + set vardesc "$vardata(-varkind) [::twapi::_resolve_com_type_text $ti $vardata(-datatype)] $vardata(-name)" + if {$attrs(-typekind) eq "enum"} { + append vardesc " = $vardata(-value) ([::twapi::_resolve_com_type_text $ti $vardata(-valuetype)])" + } else { + append vardesc " (offset $vardata(-value))" + } + append desc "\t$vardesc\n" + } + } + alias { + append desc "\ttypedef $attrs(-aliasdesc)\n" + } + module - + dispatch - + interface { + append desc [::twapi::_interface_text $ti] + } + coclass { + for {set j 0} {$j < $attrs(-interfacecount)} {incr j} { + set ti2 [$ti @GetRefTypeInfoFromIndex $j] + set idesc [$ti2 @GetName] + set iflags [$ti @GetImplTypeFlags $j] + if {[llength $iflags]} { + append idesc " ([join $iflags ,])" + } + append desc \t$idesc + $ti2 Release + unset ti2 + } + } + default { + append desc "Unknown typekind: $attrs(-typekind)\n" + } + } + append text \n$desc + } finally { + $ti Release + if {[info exists ti2]} { + $ti2 Release + } + } + } + return $text + } + + method @GenerateCode {args} { + array set opts [twapi::parseargs args { + namespace.arg + } -ignoreunknown] + + if {![info exists opts(namespace)]} { + set opts(namespace) [string tolower [my @GetName]] + } + + set data [my @Read {*}$args] + + set code {} + + # If namespace specfied as empty string (as opposed to unspecified) + # do not output a namespace + if {$opts(namespace) ne "" && + ([dict exists $data enum] || + [dict exists $data module] || + [dict exists $data coclass]) + } { + append code "\nnamespace eval $opts(namespace) \{\n" + append code "\n # Array mapping coclass names to their guids\n" + append code " variable _coclass_guids\n" + append code "\n # Array mapping dispatch interface names to their guids\n" + append code " variable _dispatch_guids\n" + append code { + # Returns the GUID for a coclass or empty string if not found + proc coclass_guid {coclass_name} { + variable _coclass_guids + if {[info exists _coclass_guids($coclass_name)]} { + return $_coclass_guids($coclass_name) + } + return "" + } + # Returns the GUID for a dispatch name or empty string if not found + proc dispatch_guid {dispatch_name} { + variable _dispatch_guids + if {[info exists _dispatch_guids($dispatch_name)]} { + return $_dispatch_guids($dispatch_name) + } + return "" + } + # Marks the specified object to be of a specific dispatch/coclass type + proc declare {typename comobj} { + # First check if it is the name of a dispatch interface + set guid [dispatch_guid $typename] + if {$guid ne ""} { + $comobj -interfaceguid $guid + return + } + + # If not, check if it is the name of a coclass with a dispatch interface + set guid [coclass_guid $typename] + if {$guid ne ""} { + if {[info exists ::twapi::_coclass_idispatch_guids($guid)]} { + $comobj -interfaceguid $::twapi::_coclass_idispatch_guids($guid) + return + } + } + + error "Could not resolve interface for $typename." + } + } + }; # append code... + + if {[dict exists $data module]} { + dict for {guid guiddata} [dict get $data module] { + # Some modules may not have constants (-values). + # We currently only output constants from modules, not functions + if {[dict exists $guiddata -values]} { + set module_name [dict get $guiddata -name] + append code "\n # Module $module_name ($guid)\n" + append code " [list array set $module_name [dict get $guiddata -values]]" + append code \n + } + } + } + + if {[dict exists $data enum]} { + dict for {name def} [dict get $data enum] { + append code "\n # Enum $name\n" + append code " [list array set $name [dict get $def -values]]" + append code \n + } + } + + if {[dict exists $data coclass]} { + dict for {guid def} [dict get $data coclass] { + append code "\n # Coclass [dict get $def -name]" + # Look for the default interface so we can remember its GUID. + # This is necessary for the cases where the Dispatch interface + # GUID is not available via a TypeInfo interface (e.g. + # a 64-bit COM component not registered with the 32-bit + # COM registry) + if {[dict exists $def -defaultdispatch]} { + set default_dispatch_guid [dict get $def -defaultdispatch] + append code "\n set ::twapi::_coclass_idispatch_guids($guid) \"$default_dispatch_guid\"\n" + } else { + set default_dispatch_guid "" + } + + # We assume here that coclass has a default interface + # which is dispatchable. Else an error will be generated + # at runtime. + append code [format { + set _coclass_guids(%1$s) "%2$s" + twapi::class create %1$s { + superclass ::twapi::Automation + constructor {args} { + set ifc [twapi::com_create_instance "%2$s" -interface IDispatch -raw {*}$args] + next [twapi::IDispatchProxy new $ifc "%2$s"] + if {[string length "%3$s"]} { + my -interfaceguid "%3$s" + } + } + }} [dict get $def -name] $guid $default_dispatch_guid] + append code \n + } + } + + if {$opts(namespace) ne "" && + ([dict exists $data enum] || + [dict exists $data module] || + [dict exists $data coclass]) + } { + append code "\}" + append code \n + } + + if {[dict exists $data dispatch]} { + dict for {guid guiddata} [dict get $data dispatch] { + set dispatch_name [dict get $guiddata -name] + append code "\n# Dispatch Interface $dispatch_name\n" + append code "set [set opts(namespace)]::_dispatch_guids($dispatch_name) \"$guid\"\n" + foreach type {methods properties} { + if {[dict exists $guiddata -$type]} { + append code "# $dispatch_name [string totitle $type]\n" + dict for {name namedata} [dict get $guiddata -$type] { + dict for {lcid lciddata} $namedata { + dict for {invkind proto} $lciddata { + append code [list ::twapi::dispatch_prototype_set \ + $guid $name $lcid $invkind $proto] + append code \n + } + } + } + } + } + } + } + + return $code + } + + method @Read {args} { + array set opts [::twapi::parseargs args { + type.arg + name.arg + } -maxleftover 0 -nulldefault] + + # Dictionary to contain result + set data [dict create] + + # Entries for coclasses and dispatch interfaces have a mutual + # dependency. Generation of dispatch interface method + # prototypes need to (potentially) resolve coclass names + # that map to dispatch interfaces. + # Conversely, that resolution requires a list of dispatch + # interface guids so gather that first. + + # List of dispatch guids + array set dispatch_guids {} + if {$opts(type) in {{} coclass dispatch}} { + # Collect dispatch guids. Note we do not collect other + # dispatch details since prototypes will need the coclass + # information which we do not have yet + my @Foreach -type dispatch ti { + ::twapi::trap { + set dispatch_guids([dict get [$ti GetTypeAttr] guid]) "" + } finally { + $ti Release + } + } + # Now that we have dispatch guids, collect coclass information + my @Foreach -type coclass ti { + ::twapi::trap { + array set attrs [$ti @GetTypeAttr -guid -lcid -varcount -fncount -interfacecount -typekind] + set name [lindex [$ti @GetDocumentation -1 -name] 1] + dict set data "coclass" $attrs(-guid) -name $name + for {set j 0} {$j < $attrs(-interfacecount)} {incr j} { + set ti2 [$ti @GetRefTypeInfoFromIndex $j] + set iflags [$ti GetImplTypeFlags $j] + set iguid [twapi::kl_get [$ti2 GetTypeAttr] guid] + set iname [$ti2 @GetName] + $ti2 Release + unset ti2; # So finally clause does not release again on error + + dict set data "coclass" $attrs(-guid) -interfaces $iguid -name $iname + dict set data "coclass" $attrs(-guid) -interfaces $iguid -flags $iflags + + # If this is a dispatch interface and the default interface + # for the coclass, add it to coclass default dispatch database. + # This will be used to resolve dispatch prototypes + if {$iflags == 1 && [info exists dispatch_guids($iguid)]} { + # This is used by the parameter resolution code in + # _resolve_comtype while building prototypes + set ::twapi::_coclass_idispatch_guids($attrs(-guid)) $iguid + dict set data "coclass" $attrs(-guid) -defaultdispatch $iguid + } + } + } finally { + if {[info exists ti2]} { + $i2 Release + } + $ti Release + } + } + } + + # If we were only looking for coclass information, we already have it + if {$opts(type) eq "coclass"} { + return $data + } + + my @Foreach -type $opts(type) -name $opts(name) ti { + ::twapi::trap { + array set attrs [$ti @GetTypeAttr -guid -lcid -varcount -fncount -interfacecount -typekind] + set name [lindex [$ti @GetDocumentation -1 -name] 1] + # dict set data $attrs(-typekind) $name {} + switch -exact -- $attrs(-typekind) { + record - + union - + enum { + # For consistency with the coclass and dispatch dict structure + # we have a separate key for 'name' even though it is the same + # as the dict key + dict set data $attrs(-typekind) $name -name $name + for {set j 0} {$j < $attrs(-varcount)} {incr j} { + array set vardata [$ti @GetVarDesc $j -name -value] + dict set data $attrs(-typekind) $name -values $vardata(-name) $vardata(-value) + } + } + alias { + # TBD - anything worth importing ? + } + dispatch { + # Load up the functions + dict set data $attrs(-typekind) $attrs(-guid) -name $name + for {set j 0} {$j < $attrs(-fncount)} {incr j} { + array set funcdata [$ti GetFuncDesc $j] + if {$funcdata(funckind) != 4} { + # Not a dispatch function (4), ignore + # TBD - what else could it be if already filtering + # typeinfo on dispatch + # Vtable set funckind "(vtable $funcdata(-oVft))" + ::twapi::debuglog "Unexpected funckind value '$funcdata(funckind)' ignored. funcdata: [array get funcdata]" + continue; + } + + set proto [list $funcdata(memid) \ + $attrs(-lcid) \ + $funcdata(invkind) \ + $funcdata(elemdescFunc.tdesc) \ + [::twapi::_resolve_params_for_prototype $ti $funcdata(lprgelemdescParam)]] + # Param names are needed for named arguments. Index 0 is method name so skip it + if {[catch {lappend proto [lrange [$ti GetNames $funcdata(memid)] 1 end]}]} { + # Could not get param names + lappend proto {} + } + + dict set data "$attrs(-typekind)" \ + $attrs(-guid) \ + -methods \ + [$ti @GetName $funcdata(memid)] \ + $attrs(-lcid) \ + $funcdata(invkind) \ + $proto + } + # Load up the properties + for {set j 0} {$j < $attrs(-varcount)} {incr j} { + array set vardata [$ti GetVarDesc $j] + # We will add both propput and propget. + # propget: + dict set data "$attrs(-typekind)" \ + $attrs(-guid) \ + -properties \ + [$ti @GetName $vardata(memid)] \ + $attrs(-lcid) \ + 2 \ + [list $vardata(memid) $attrs(-lcid) 2 $vardata(elemdescVar.tdesc) {} {}] + + # TBD - mock up the parameters for the property set + # Single parameter corresponding to return type of + # property. Param list is of the form + # {PARAM1 PARAM2} where PARAM is {TYPE {FLAGS ?DEFAULT}} + # So param list with one param is + # {{TYPE {FLAGS ?DEFAULT?}}} + # propput: + if {! ($vardata(wVarFlags) & 1)} { + # Not read-only + dict set data "$attrs(-typekind)" \ + $attrs(-guid) \ + -properties \ + [$ti @GetName $vardata(memid)] \ + $attrs(-lcid) \ + 4 \ + [list $vardata(memid) $attrs(-lcid) 4 24 [list [list $vardata(elemdescVar.tdesc) [list 1]]] {}] + } + } + } + + module { + dict set data $attrs(-typekind) $attrs(-guid) -name $name + # TBD - Load up the functions + + # Now load up the variables + for {set j 0} {$j < $attrs(-varcount)} {incr j} { + array set vardata [$ti @GetVarDesc $j -name -value] + dict set data $attrs(-typekind) $attrs(-guid) -values $vardata(-name) $vardata(-value) + } + } + + interface { + # TBD + } + + coclass { + # We have already collected this information before this loop + continue + } + default { + # TBD + } + } + } finally { + $ti Release + if {[info exists ti2]} { + $ti2 Release + } + } + } + + # Unless we are collecting coclass info, remove any related info + # that we might have gathered for dispatch prototypes + if {$opts(type) ni {{} coclass}} { + dict unset data "coclass" + } + return $data + } + + twapi_exportall +} + +# ITypeComp +#---------- +twapi::class create ::twapi::ITypeCompProxy { + superclass ::twapi::IUnknownProxy + + method Bind {name lhash flags} { + my variable _ifc + return [::twapi::ITypeComp_Bind $_ifc $name $lhash $flags] + } + + # Returns empty list if bind not found + method @Bind {name flags {lcid 0}} { + ::twapi::trap { + set binding [my Bind $name [::twapi::LHashValOfName $lcid $name] $flags] + } onerror {TWAPI_WIN32 0x80028ca0} { + # Found but type mismatch (flags not correct) + return {} + } + + lassign $binding type data tifc + return [list $type $data [::twapi::make_interface_proxy $tifc]] + } + + twapi_exportall +} + +# IEnumVARIANT +#------------- + +twapi::class create ::twapi::IEnumVARIANTProxy { + superclass ::twapi::IUnknownProxy + + method Next {count {value_only 0}} { + my variable _ifc + return [::twapi::IEnumVARIANT_Next $_ifc $count $value_only] + } + method Clone {} { + my variable _ifc + return [::twapi::IEnumVARIANT_Clone $_ifc] + } + method @Clone {} { + return [::twapi::make_interface_proxy [my Clone]] + } + method Reset {} { + my variable _ifc + return [::twapi::IEnumVARIANT_Reset $_ifc] + } + method Skip {count} { + my variable _ifc + return [::twapi::IEnumVARIANT_Skip $_ifc $count] + } + + twapi_exportall +} + +# Automation +#----------- +twapi::class create ::twapi::Automation { + + # Caller gives up ownership of proxy in all cases, even errors. + # $proxy will eventually be Release'ed. If caller wants to keep + # a reference to it, it must do an *additional* AddRef on it to + # keep it from going away when the Automation object releases it. + constructor {proxy {lcid 0}} { + my variable _proxy _lcid _sinks _connection_pts + + set type [$proxy @Type] + if {$type ne "IDispatch" && $type ne "IDispatchEx"} { + $proxy Release; # Even on error, responsible for releasing + error "Automation objects do not support interfaces of type '$type'" + } + if {$type eq "IDispatchEx"} { + my variable _have_dispex + # If _have_dispex variable + # - does not exist, have not tried to get IDispatchEx yet + # - is 0, have tried but failed + # - is 1, already have IDispatchEx + set _have_dispex 1 + } + + set _proxy $proxy + set _lcid $lcid + array set _sinks {} + array set _connection_pts {} + } + + destructor { + my variable _proxy _sinks + + # Release sinks, connection points + foreach sinkid [array names _sinks] { + my -unbind $sinkid + } + + if {[info exists _proxy]} { + $_proxy Release + } + return + } + + # Intended to be called only from another method. Not directly. + # Does an uplevel 2 to get to application context. + # On failures, retries with IDispatchEx interface + # TBD - get rid of this uplevel business by having internal + # callers to equivalent of "uplevel 1 my _invoke ... + method _invoke {name invkinds params args} { + my variable _proxy _lcid + + if {[$_proxy @Null?]} { + error "Attempt to invoke method $name on NULL COM object" + } + + array set opts [twapi::parseargs args { + raw.bool + namedargs.arg + } -nulldefault -maxleftover 0] + + ::twapi::trap { + set vtval [uplevel 2 [list $_proxy @Invoke $name $invkinds $_lcid $params $opts(namedargs)]] + if {$opts(raw)} { + return $vtval + } else { + return [::twapi::variant_value $vtval 0 0 $_lcid] + } + } onerror {} { + # TBD - should we only drop down below to check for IDispatchEx + # for specific error codes. Right now we do it for all. + set erinfo $::errorInfo + set ercode $::errorCode + set ermsg [::twapi::trapresult] + } + + # We plan on trying to get a IDispatchEx interface in case + # the method/property is the "expando" type + my variable _have_dispex + if {[info exists _have_dispex]} { + # We have already tried for IDispatchEx, either successfully + # or not. Either way, no need to try again + error $ermsg $erinfo $ercode + } + + # Try getting a IDispatchEx interface + if {[catch {$_proxy @QueryInterface IDispatchEx 1} proxy_ex] || + $proxy_ex eq ""} { + set _have_dispex 0 + error $ermsg $erinfo $ercode + } + + set _have_dispex 1 + $_proxy Release + set _proxy $proxy_ex + + # Retry with the IDispatchEx interface + set vtval [uplevel 2 [list $_proxy @Invoke $name $invkinds $_lcid $params $opts(namedargs)]] + if {$opts(raw)} { + return $vtval + } else { + return [::twapi::variant_value $vtval 0 0 $_lcid] + } + } + + method -get {name args} { + return [my _invoke $name [list 2] $args] + } + + method -put {name args} { + return [my _invoke $name [list 4] $args] + } + forward -set my -put + + method -putref {name args} { + return [my _invoke $name [list 8] $args] + } + + method -call {name args} { + return [my _invoke $name [list 1] $args] + } + + method -callnamedargs {name args} { + return [my _invoke $name [list 1] {} -namedargs $args] + } + + # Need a wrapper around _invoke in order for latter's uplevel 2 + # to work correctly + # TBD - document, test + method -invoke {name invkinds params args} { + return [my _invoke $name $invkinds $params {*}$args] + } + + method -destroy {} { + my destroy + } + + method -isnull {} { + my variable _proxy + return [$_proxy @Null?] + } + + method -default {} { + my variable _proxy _lcid + return [::twapi::variant_value [$_proxy Invoke ""] 0 0 $_lcid] + } + + # Caller must call release on the proxy + method -proxy {} { + my variable _proxy + $_proxy AddRef + return $_proxy + } + + # Only for debugging + method -proxyrefcounts {} { + my variable _proxy + return [$_proxy DebugRefCounts] + } + + # Returns the raw interface. Caller must call IUnknownRelease on it + # iff addref is passed as true (default) + method -interface {{addref 1}} { + my variable _proxy + return [$_proxy @Interface $addref] + } + + # Validates internal structures + method -validate {} { + twapi::ValidateIUnknown [my -interface 0] + } + + # Set/return the GUID for the interface + method -interfaceguid {{guid ""}} { + my variable _proxy + return [$_proxy @SetGuid $guid] + } + + # Sets the idispatch or coclass of the object + method -instanceof {coclass} { + # The coclass may be a GUID or the Tcl name + if {[::twapi::Twapi_IsValidGUID $coclass]} { + if {[info exists ::twapi::_coclass_idispatch_guids($coclass)]} { + $comobj -interfaceguid $::twapi::_coclass_idispatch_guids($coclass) + } + error "Could not resolve interface for coclass GUID $coclass." + } + # Check for corresponding Tcl class name generated from a type + # library + set ns [namespace qualifiers $coclass] + if {$ns eq ""} { + error "Coclass name must be qualified with name of containing namespace." + } + uplevel 1 [list ${ns}::declare [namespace tail $coclass] [self]] + } + + # Return the disp id for a method/property + method -dispid {name} { + my variable _proxy + return [$_proxy @GetIDOfOneName $name] + } + + # Prints methods in an interface + method -print {} { + my variable _proxy + ::twapi::dispatch_print $_proxy + } + + method -with {subobjlist args} { + # $obj -with SUBOBJECTPATHLIST arguments + # where SUBOBJECTPATHLIST is list each element of which is + # either a property or a method of the previous element in + # the list. The element may itself be a list in which case + # the first element is the property/method and remaining + # are passed to it + # + # Note that 'arguments' may themselves be comobj subcommands! + set next [self] + set releaselist [list ] + ::twapi::trap { + while {[llength $subobjlist]} { + set nextargs [lindex $subobjlist 0] + set subobjlist [lrange $subobjlist 1 end] + set next [uplevel 1 [list $next] $nextargs] + lappend releaselist $next + } + # We use uplevel here because again we want to run in caller + # context + return [uplevel 1 [list $next] $args] + } finally { + foreach next $releaselist { + $next -destroy + } + } + } + + method -iterate {args} { + my variable _lcid + + array set opts [::twapi::parseargs args { + cleanup + }] + + if {[llength $args] < 2} { + error "Syntax: COMOBJ -iterate ?options? VARNAME SCRIPT" + } + upvar 1 [lindex $args 0] var + set script [lindex $args 1] + + # First get IEnumVariant iterator using the _NewEnum method + # TBD - As per MS OLE Automation spec, it appears _NewEnum + # MUST have dispid -4. Can we use this information when + # this object does not have an associated interface guid or + # when no prototype is available ? + set enumerator [my -get _NewEnum] + # This gives us an IUnknown. + ::twapi::trap { + # Convert the IUnknown to IEnumVARIANT + set iter [$enumerator @QueryInterface IEnumVARIANT] + if {! [$iter @Null?]} { + set more 1 + while {$more} { + # Get the next item from iterator + set next [$iter Next 1] + lassign $next more values + if {[llength $values]} { + set var [::twapi::variant_value [lindex $values 0] 0 0 $_lcid] + set ret [catch {uplevel 1 $script} msg options] + switch -exact -- $ret { + 0 - + 4 { + # Body executed successfully, or invoked continue + if {$opts(cleanup)} { + $var destroy + } + } + 3 { + if {$opts(cleanup)} { + $var destroy + } + set more 0; # TCL_BREAK + } + 1 - + 2 - + default { + if {$opts(cleanup)} { + $var destroy + } + dict incr options -level + return -options $options $msg + } + + } + } + } + } + } finally { + $enumerator Release + if {[info exists iter] && ![$iter @Null?]} { + $iter Release + } + } + return + } + + method -bind {script} { + my variable _proxy _sinks _connection_pts + + # Get the coclass typeinfo and locate the source interface + # within it and retrieve disp id mappings + ::twapi::trap { + set coti [$_proxy @GetCoClassTypeInfo] + + # $coti is the coclass information. Get dispids for the default + # source interface for events and its guid + set srcti [$coti @GetDefaultSourceTypeInfo] + array set srcinfo [$srcti @GetTypeAttr -memidmap -guid] + + # TBD - implement IConnectionPointContainerProxy + # Now we need to get the actual connection point itself + set container [$_proxy QueryInterface IConnectionPointContainer] + set connpt_ifc [::twapi::IConnectionPointContainer_FindConnectionPoint $container $srcinfo(-guid)] + + # Finally, create our sink object + # TBD - need to make sure Automation object is not deleted or + # should the callback itself check? + # TBD - what guid should we be passing? CLSID or IID ? + set sink_ifc [::twapi::Twapi_ComServer $srcinfo(-guid) $srcinfo(-memidmap) [list ::twapi::_eventsink_callback [self] $script]] + + # OK, we finally have everything we need. Tell the event source + set sinkid [::twapi::IConnectionPoint_Advise $connpt_ifc $sink_ifc] + + set _sinks($sinkid) $sink_ifc + set _connection_pts($sinkid) $connpt_ifc + return $sinkid + } onerror {} { + # These are released only on error as otherwise they have + # to be kept until unbind time + foreach ifc {connpt_ifc sink_ifc} { + if {[info exists $ifc] && [set $ifc] ne ""} { + ::twapi::IUnknown_Release [set $ifc] + } + } + twapi::rethrow + } finally { + # In all cases, release any interfaces we created + # Note connpt_ifc and sink_ifc are released at unbind time except + # on error + foreach obj {coti srcti} { + if {[info exists $obj]} { + [set $obj] Release + } + } + if {[info exists container]} { + ::twapi::IUnknown_Release $container + } + } + } + + method -unbind {sinkid} { + my variable _proxy _sinks _connection_pts + + if {[info exists _connection_pts($sinkid)]} { + ::twapi::IConnectionPoint_Unadvise $_connection_pts($sinkid) $sinkid + unset _connection_pts($sinkid) + } + + if {[info exists _sinks($sinkid)]} { + ::twapi::IUnknown_Release $_sinks($sinkid) + unset _sinks($sinkid) + } + return + } + + method -securityblanket {args} { + my variable _proxy + if {[llength $args]} { + $_proxy @SetSecurityBlanket [lindex $args 0] + return + } else { + return [$_proxy @GetSecurityBlanket] + } + } + + method -lcid {{lcid ""}} { + my variable _lcid + if {$lcid ne ""} { + if {![string is integer -strict $lcid]} { + error "Invalid LCID $lcid" + } + set _lcid $lcid + } + return $_lcid + } + + method unknown {name args} { + # We have to figure out if it is a property get, property put + # or a method. We make a guess based on number of parameters. + # We specify an order to try based on this. The invoke will try + # all invocations in that order. + set nargs [llength $args] + if {$nargs == 0} { + # No arguments, cannot be propput*. Try propget and method + set invkinds [list 2 1] + } elseif {$nargs == 1} { + # One argument, likely propput, method, propget, propputref + # propputref is last as least likely + set invkinds [list 4 1 2 8] + } else { + # Multiple arguments, likely method, propput, propget, propputref + # propputref is last as least likely + set invkinds [list 1 4 2 8] + } + + return [my _invoke $name $invkinds $args] + } + + twapi_exportall +} + +# +# Singleton NULL comobj object. We want to override default destroy methods +# to prevent object from being destroyed. This is a backward compatibility +# hack and not fool proof since the command could just be renamed away. +twapi::class create twapi::NullAutomation { + superclass twapi::Automation + constructor {} { + next [twapi::make_interface_proxy {0 IDispatch}] + } + method -destroy {} { + # Silently ignore + } + method destroy {} { + # Silently ignore + } + twapi_exportall +} + +twapi::NullAutomation create twapi::comobj_null +# twapi::Automation create twapi::comobj_null [twapi::make_interface_proxy {0 IDispatch}] + +proc twapi::_comobj_cleanup {} { + foreach obj [comobj_instances] { + $obj destroy + } +} + +# In order for servers to release objects properly, the IUnknown interface +# must have the same security settings as were used in the object creation +# call. This is a helper for that. +proc twapi::_com_set_iunknown_proxy {ifc blanket} { + set iunk [Twapi_IUnknown_QueryInterface $ifc [_iid_iunknown] IUnknown] + trap { + CoSetProxyBlanket $iunk {*}$blanket + } finally { + IUnknown_Release $iunk + } +} + + +twapi::proc* twapi::_init_authnames {} { + variable _com_authsvc_to_name + variable _com_name_to_authsvc + variable _com_impersonation_to_name + variable _com_name_to_impersonation + variable _com_authlevel_to_name + variable _com_name_to_authlevel + + set _com_authsvc_to_name {0 none 9 negotiate 10 ntlm 14 schannel 16 kerberos 0xffffffff default} + set _com_name_to_authsvc [swapl $_com_authsvc_to_name] + set _com_name_to_impersonation {default 0 anonymous 1 identify 2 impersonate 3 delegate 4} + set _com_impersonation_to_name [swapl $_com_name_to_impersonation] + set _com_name_to_authlevel {default 0 none 1 connect 2 call 3 packet 4 packetintegrity 5 privacy 6} + set _com_authlevel_to_name [swapl $_com_name_to_authlevel] +} { +} + +twapi::proc* twapi::_com_authsvc_to_name {authsvc} { + _init_authnames +} { + variable _com_authsvc_to_name + return [dict* $_com_authsvc_to_name $authsvc] +} + +twapi::proc* twapi::_com_name_to_authsvc {name} { + _init_authnames +} { + variable _com_name_to_authsvc + if {[string is integer -strict $name]} { + return $name + } + return [dict! $_com_name_to_authsvc $name] +} + +twapi::proc* twapi::_com_authlevel_to_name {authlevel} { + _init_authnames +} { + variable _com_authlevel_to_name + return [dict* $_com_authlevel_to_name $authlevel] +} + +twapi::proc* twapi::_com_name_to_authlevel {name} { + _init_authnames +} { + variable _com_name_to_authlevel + if {[string is integer -strict $name]} { + return $name + } + return [dict! $_com_name_to_authlevel $name] +} + + +twapi::proc* twapi::_com_impersonation_to_name {imp} { + _init_authnames +} { + variable _com_impersonation_to_name + return [dict* $_com_impersonation_to_name $imp] +} + +twapi::proc* twapi::_com_name_to_impersonation {name} { + _init_authnames +} { + variable _com_name_to_impersonation + if {[string is integer -strict $name]} { + return $name + } + return [dict! $_com_name_to_impersonation $name] +} + +################################################################# +# COM server implementation +# WARNING: do not use any fancy TclOO features because it has to +# run under 8.5/metoo as well +# TBD - test scripts? + +twapi::class create twapi::ComFactory { + constructor {clsid member_map create_command_prefix} { + my variable _clsid _create_command_prefix _member_map _ifc + + set _clsid $clsid + set _member_map $member_map + set _create_command_prefix $create_command_prefix + + set _ifc [twapi::Twapi_ClassFactory $_clsid [list [self] _create_instance]] + } + + destructor { + # TBD - what happens if factory is destroyed while objects still + # exist ? + # App MUST explicitly destroy objects before exiting + my variable _class_registration_id + if {[info exists _class_registration_id]} { + twapi::CoRevokeClassObject $_class_registration_id + } + } + + # Called from Twapi_ClassFactory_CreateInstance to create a new object + # Should not be called from elsewhere + method _create_instance {iid} { + my variable _create_command_prefix _member_map + # Note [list {*}$foo] != $foo - consider when foo contains a ";" + set obj_prefix [uplevel #0 [list {*}$_create_command_prefix]] + twapi::trap { + # Since we are not holding on to this interface ourselves, + # we can pass it on without AddRef'ing it + return [twapi::Twapi_ComServer $iid $_member_map $obj_prefix] + } onerror {} { + $obj_prefix destroy + twapi::rethrow + } + } + + method register {args} { + my variable _clsid _create_command_prefix _member_map _ifc _class_registration_id + twapi::parseargs args { + {model.arg any} + } -setvars -maxleftover 0 + set model_flags 0 + foreach m $model { + switch -exact -- $m { + any {twapi::setbits model_flags 20} + localserver {twapi::setbits model_flags 4} + remoteserver {twapi::setbits model_flags 16} + default {twapi::badargs! "Invalid COM class model '$m'"} + } + } + + # 0x6 -> REGCLS_MULTI_SEPARATE | REGCLS_SUSPENDED + set _class_registration_id [twapi::CoRegisterClassObject $_clsid $_ifc $model_flags 0x6] + return + } + + export _create_instance +} + +proc twapi::comserver_factory {clsid member_map command_prefix {name {}}} { + if {$name ne ""} { + uplevel 1 [list [namespace current]::ComFactory create $name $clsid $member_map $command_prefix] + } else { + uplevel 1 [list [namespace current]::ComFactory new $clsid $member_map $command_prefix] + } +} + +proc twapi::start_factories {{cmd {}}} { + # TBD - what if no class objects ? + CoResumeClassObjects + + if {[llength $cmd]} { + # TBD - normalize $cmd so to run in right namespace etc. + trace add variable [namspace current]::com_shutdown_signal write $cmd + return + } + + # This is set from the C code when we are not serving up any + # COM objects (either event callbacks or com servers) + vwait [namespace current]::com_shutdown_signal +} + +proc twapi::suspend_factories {} { + CoSuspendClassObjects +} + +proc twapi::resume_factories {} { + CoResumeClassObjects +} + +proc twapi::install_coclass_script {progid clsid version script_path args} { + # Need to extract params so we can prefix script name + set saved_args $args + array set opts [parseargs args { + params.arg + } -ignoreunknown] + + set script_path [file normalize $script_path] + + # Try to locate the wish executable to run the component + if {[info commands wm] eq ""} { + set dir [file dirname [info nameofexecutable]] + set wishes [glob -nocomplain -directory $dir wish*.exe] + if {[llength $wishes] == 0} { + error "Could not locate wish program." + } + set wish [lindex $wishes 0] + } else { + # We are running wish already + set wish [info nameofexecutable] + } + + set exe_path [file nativename [file attributes $wish -shortname]] + + set params "\"$script_path\"" + if {[info exists opts(params)]} { + append params " $params" + } + return [install_coclass $progid $clsid $version $exe_path {*}$args -outproc -params $params] +} + +proc twapi::install_coclass {progid clsid version path args} { + array set opts [twapi::parseargs args { + {scope.arg user {user system}} + appid.arg + appname.arg + inproc + outproc + service + params.arg + name.arg + } -maxleftover 0] + + switch [tcl::mathop::+ $opts(inproc) $opts(outproc) $opts(service)] { + 0 { + # Need to figure out the type + switch [file extension $path] { + .exe { set opts(outproc) 1 } + .ocx - + .dll { set opts(inproc) 1 } + default { set opts(service) 1 } + } + } + 1 {} + default { + badargs! "Only one of -inproc, -outproc or -service may be specified" + } + } + + if {(! [string is integer -strict $version]) || $version <= 0} { + twapi::badargs! "Invalid version '$version'. Must be a positive integer" + } + if {![regexp {^[[:alpha:]][[:alnum:]]*\.[[:alpha:]][[:alnum:]]*$} $progid]} { + badargs! "Invalid PROGID syntax '$progid'" + } + set clsid [canonicalize_guid $clsid] + if {![info exists opts(appid)]} { + # This is what dcomcnfg and oleview do - default to the CLSID + set opts(appid) $clsid + } else { + set opts(appid) [canonicalize_guid $opts(appid)] + } + + if {$opts(scope) eq "user"} { + if {$opts(service)} { + twapi::badargs! "Option -service cannot be specified if -scope is \"user\"" + } + set regtop HKEY_CURRENT_USER + } else { + set regtop HKEY_LOCAL_MACHINE + } + + set progid_path "$regtop\\Software\\Classes\\$progid" + set clsid_path "$regtop\\Software\\Classes\\CLSID\\$clsid" + set appid_path "$regtop\\Software\\Classes\\AppID\\$opts(appid)" + + if {$opts(service)} { + # TBD + badargs! "Option -service is not implemented" + } elseif {$opts(outproc)} { + if {[info exists opts(params)]} { + registry set "$clsid_path\\LocalServer32" "" "\"[file nativename [file normalize $path]]\" $opts(params)" + } else { + registry set "$clsid_path\\LocalServer32" "" "\"[file nativename [file normalize $path]]\"" + } + # TBD - We do not quote path for ServerExecutable, should we ? + registry set "$clsid_path\\LocalServer32" "ServerExecutable" [file nativename [file normalize $path]] + } else { + # TBD - We do not quote path here either, should we ? + registry set "$clsid_path\\InprocServer32" "" [file nativename [file normalize $path]] + } + + registry set "$clsid_path\\ProgID" "" "$progid.$version" + registry set "$clsid_path\\VersionIndependentProgID" "" $progid + + # Set the registry under the progid and progid.version + registry set "$progid_path\\CLSID" "" $clsid + registry set "$progid_path\\CurVer" "" "$progid.$version" + if {[info exists opts(name)]} { + registry set $progid_path "" $opts(name) + } + + append progid_path ".$version" + registry set "$progid_path\\CLSID" "" $clsid + if {[info exists opts(name)]} { + registry set $progid_path "" $opts(name) + } + + registry set $clsid_path "AppID" $opts(appid) + registry set $appid_path; # Always create the key even if nothing below + if {[info exists opts(appname)]} { + registry set $appid_path "" $opts(appname) + } + + if {$opts(service)} { + registry set $appid_path "LocalService" $path + if {[info exists opts(params)]} { + registry set $appid_path "ServiceParameters" $opts(params) + } + } + + return +} + +proc twapi::uninstall_coclass {progid args} { + # Note "CLSID" itself is a valid ProgID (it has a CLSID key below it) + # Also we want to protect against horrible errors that blow away + # entire branches if progid is empty, wrong value, etc. + # So only work with keys of the form X.X + if {![regexp {^[[:alpha:]][[:alnum:]]*\.[[:alpha:]][[:alnum:]]*$} $progid]} { + badargs! "Invalid PROGID syntax '$progid'" + } + + # Do NOT want to delete the CLSID key by mistake. Note below checks + # will not protect against this since they will return a valid value + # if progid is "CLSID" since that has a CLSID key below it as well. + if {[string equal -nocase $progid CLSID]} { + badargs! "Attempt to delete protected key 'CLSID'" + } + + array set opts [twapi::parseargs args { + {scope.arg user {user system}} + keepappid + } -maxleftover 0] + + switch -exact -- $opts(scope) { + user { set regtop HKEY_CURRENT_USER } + system { set regtop HKEY_LOCAL_MACHINE } + default { + badargs! "Invalid class registration scope '$opts(scope)'. Must be 'user' or 'system'" + } + } + + if {0} { + # Do NOT use this. If running under elevated, it will ignore + # HKEY_CURRENT_USER. + set clsid [progid_to_clsid $progid]; # Also protects against bogus progids + } else { + set clsid [registry get "$regtop\\Software\\Classes\\$progid\\CLSID" ""] + } + + # Should not be empty at this point but do not want to delete the + # whole Classes tree in case progid or clsid are empty strings + # because of some bug! That would be an epic disaster so try and + # protect. + if {$clsid eq ""} { + badargs! "CLSID corresponding to PROGID '$progid' is empty" + } + + # See if we need to delete the linked current version + if {! [catch { + registry get "$regtop\\Software\\Classes\\$progid\\CurVer" "" + } curver]} { + if {[string match -nocase ${progid}.* $curver]} { + registry delete "$regtop\\Software\\Classes\\$curver" + } + } + + # See if we need to delete the APPID + if {! $opts(keepappid)} { + if {! [catch { + registry get "$regtop\\Software\\Classes\\CLSID\\$clsid" "AppID" + } appid]} { + # Validate it is a real GUID + if {![catch {canonicalize_guid $appid}]} { + registry delete "$regtop\\Software\\Classes\\AppID\\$appid" + } + } + } + + # Finally delete the keys and hope we have not trashed the system + registry delete "$regtop\\Software\\Classes\\CLSID\\$clsid" + registry delete "$regtop\\Software\\Classes\\$progid" + + return +} + + diff --git a/src/vendorlib_tcl8/twapi4.7.2/console.tcl b/src/vendorlib_tcl8/twapi-5.0b1/console.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/console.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/console.tcl index 3f503040..696f61ad 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/console.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/console.tcl @@ -1,736 +1,736 @@ -# -# Copyright (c) 2004-2014, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { -} - -# Allocate a new console -proc twapi::allocate_console {} { - AllocConsole -} - -# Free a console -proc twapi::free_console {} { - FreeConsole -} - -# Get a console handle -proc twapi::get_console_handle {type} { - switch -exact -- $type { - 0 - - stdin { set fn "CONIN\$" } - 1 - - stdout - - 2 - - stderr { set fn "CONOUT\$" } - default { - error "Unknown console handle type '$type'" - } - } - - # 0xC0000000 -> GENERIC_READ | GENERIC_WRITE - # 3 -> FILE_SHARE_READ | FILE_SHARE_WRITE - # 3 -> OPEN_EXISTING - return [CreateFile $fn \ - 0xC0000000 \ - 3 \ - {{} 1} \ - 3 \ - 0 \ - NULL] -} - -proc twapi::_standard_handle_type {type} { - if {[string is integer -strict $type]} { - set type [format %d $type] ; # Convert hex etc. - } - switch -exact -- $type { - 0 - - -10 - - stdin { set type -10 } - 1 - - -11 - - stdout { set type -11 } - 2 - - -12 - - stderr { set type -12 } - default { - error "Unknown console handle type '$type'" - } - } - return $type -} - -# Get a console handle -proc twapi::get_standard_handle {type} { - return [GetStdHandle [_standard_handle_type $type]] -} - -# Set a console handle -proc twapi::set_standard_handle {type handle} { - return [SetStdHandle [_standard_handle_type $type] $handle] -} - -proc twapi::_console_output_attr_to_flags {attrs} { - set flags 0 - foreach {attr bool} $attrs { - if {$bool} { - set flags [expr {$flags | [_console_output_attr $attr]}] - } - } - return $flags -} - -proc twapi::_flags_to_console_output_attr {flags} { - # Check for multiple bit attributes first, in order - set attrs {} - foreach attr { - -fgwhite -bgwhite -fggray -bggray - -fgturquoise -bgturquoise -fgpurple -bgpurple -fgyellow -bgyellow - -fgred -bgred -fggreen -bggreen -fgblue -bgblue - -fgbright -bgbright - } { - if {($flags & [_console_output_attr $attr]) == [_console_output_attr $attr]} { - lappend attrs $attr 1 - set flags [expr {$flags & ~ [_console_output_attr $attr]}] - if {$flags == 0} { - break - } - } - } - - return $attrs -} - - -# Get the current mode settings for the console -proc twapi::_get_console_input_mode {conh} { - set mode [GetConsoleMode $conh] - return [_bitmask_to_switches $mode [_console_input_mode_syms]] -} -interp alias {} twapi::get_console_input_mode {} twapi::_do_console_proc twapi::_get_console_input_mode stdin - -# Get the current mode settings for the console -proc twapi::_get_console_output_mode {conh} { - set mode [GetConsoleMode $conh] - return [_bitmask_to_switches $mode [_console_output_mode_syms]] -} -interp alias {} twapi::get_console_output_mode {} twapi::_do_console_proc twapi::_get_console_output_mode stdout - -# Set console input mode -proc twapi::_set_console_input_mode {conh args} { - set mode [_switches_to_bitmask $args [_console_input_mode_syms]] - # If insertmode or quickedit mode are set, make sure to set extended bit - if {$mode & 0x60} { - setbits mode 0x80; # ENABLE_EXTENDED_FLAGS - } - - SetConsoleMode $conh $mode -} -interp alias {} twapi::set_console_input_mode {} twapi::_do_console_proc twapi::_set_console_input_mode stdin - -# Modify console input mode -proc twapi::_modify_console_input_mode {conh args} { - set prev [GetConsoleMode $conh] - set mode [_switches_to_bitmask $args [_console_input_mode_syms] $prev] - # If insertmode or quickedit mode are set, make sure to set extended bit - if {$mode & 0x60} { - setbits mode 0x80; # ENABLE_EXTENDED_FLAGS - } - - SetConsoleMode $conh $mode - # Returns the old modes - return [_bitmask_to_switches $prev [_console_input_mode_syms]] -} -interp alias {} twapi::modify_console_input_mode {} twapi::_do_console_proc twapi::_modify_console_input_mode stdin - -# -# Set console output mode -proc twapi::_set_console_output_mode {conh args} { - set mode [_switches_to_bitmask $args [_console_output_mode_syms]] - - SetConsoleMode $conh $mode - -} -interp alias {} twapi::set_console_output_mode {} twapi::_do_console_proc twapi::_set_console_output_mode stdout - -# Set console output mode -proc twapi::_modify_console_output_mode {conh args} { - set prev [GetConsoleMode $conh] - set mode [_switches_to_bitmask $args [_console_output_mode_syms] $prev] - - SetConsoleMode $conh $mode - # Returns the old modes - return [_bitmask_to_switches $prev [_console_output_mode_syms]] -} -interp alias {} twapi::modify_console_output_mode {} twapi::_do_console_proc twapi::_modify_console_output_mode stdout - - -# Create and return a handle to a screen buffer -proc twapi::create_console_screen_buffer {args} { - array set opts [parseargs args { - {inherit.bool 0} - {mode.arg readwrite {read write readwrite}} - {secd.arg ""} - {share.arg readwrite {none read write readwrite}} - } -maxleftover 0] - - switch -exact -- $opts(mode) { - read { set mode [_access_rights_to_mask generic_read] } - write { set mode [_access_rights_to_mask generic_write] } - readwrite { - set mode [_access_rights_to_mask {generic_read generic_write}] - } - } - switch -exact -- $opts(share) { - none { - set share 0 - } - read { - set share 1 ;# FILE_SHARE_READ - } - write { - set share 2 ;# FILE_SHARE_WRITE - } - readwrite { - set share 3 - } - } - - return [CreateConsoleScreenBuffer \ - $mode \ - $share \ - [_make_secattr $opts(secd) $opts(inherit)] \ - 1] -} - -# Retrieve information about a console screen buffer -proc twapi::_get_console_screen_buffer_info {conh args} { - array set opts [parseargs args { - all - textattr - cursorpos - maxwindowsize - size - windowlocation - windowpos - windowsize - } -maxleftover 0] - - lassign [GetConsoleScreenBufferInfo $conh] size cursorpos textattr windowlocation maxwindowsize - - set result [list ] - foreach opt {size cursorpos maxwindowsize windowlocation} { - if {$opts($opt) || $opts(all)} { - lappend result -$opt [set $opt] - } - } - - if {$opts(windowpos) || $opts(all)} { - lappend result -windowpos [lrange $windowlocation 0 1] - } - - if {$opts(windowsize) || $opts(all)} { - lassign $windowlocation left top right bot - lappend result -windowsize [list [expr {$right-$left+1}] [expr {$bot-$top+1}]] - } - - if {$opts(textattr) || $opts(all)} { - lappend result -textattr [_flags_to_console_output_attr $textattr] - } - - return $result -} -interp alias {} twapi::get_console_screen_buffer_info {} twapi::_do_console_proc twapi::_get_console_screen_buffer_info stdout - -# Set the cursor position -proc twapi::_set_console_cursor_position {conh pos} { - SetConsoleCursorPosition $conh $pos -} -interp alias {} twapi::set_console_cursor_position {} twapi::_do_console_proc twapi::_set_console_cursor_position stdout - -# Get the cursor position -proc twapi::get_console_cursor_position {conh} { - return [lindex [get_console_screen_buffer_info $conh -cursorpos] 1] -} - -# Write the specified string to the console -proc twapi::_console_write {conh s args} { - # Note writes are always in raw mode, - # TBD - support for scrolling - # TBD - support for attributes - - array set opts [parseargs args { - position.arg - {newlinemode.arg column {line column}} - {restoreposition.bool 0} - } -maxleftover 0] - - # Get screen buffer info including cursor position - array set csbi [get_console_screen_buffer_info $conh -cursorpos -size] - - # Get current console mode for later restoration - # If console is in processed mode, set it to raw mode - set oldmode [get_console_output_mode $conh] - set processed_index [lsearch -exact $oldmode "processed"] - if {$processed_index >= 0} { - # Console was in processed mode. Set it to raw mode - set newmode [lreplace $oldmode $processed_index $processed_index] - set_console_output_mode $conh $newmode - } - - trap { - # x,y are starting position to write - if {[info exists opts(position)]} { - lassign [_parse_integer_pair $opts(position)] x y - } else { - # No position specified, get current cursor position - lassign $csbi(-cursorpos) x y - } - - set startx [expr {$opts(newlinemode) == "column" ? $x : 0}] - - # Get screen buffer limits - lassign $csbi(-size) width height - - # Ensure line terminations are just \n - set s [string map [list \r\n \n] $s] - - # Write out each line at ($x,$y) - # Either \r or \n is considered a newline - foreach line [split $s \r\n] { - if {$y >= $height} break - set_console_cursor_position $conh [list $x $y] - if {$x < $width} { - # Write the characters - do not write more than buffer width - set num_chars [expr {$width-$x}] - if {[string length $line] < $num_chars} { - set num_chars [string length $line] - } - WriteConsole $conh $line $num_chars - } - - - # Calculate starting position of next line - incr y - set x $startx - } - - } finally { - # Restore cursor if requested - if {$opts(restoreposition)} { - set_console_cursor_position $conh $csbi(-cursorpos) - } - # Restore output mode if changed - if {[info exists newmode]} { - set_console_output_mode $conh $oldmode - } - } - - return -} -interp alias {} twapi::write_console {} twapi::_do_console_proc twapi::_console_write stdout -interp alias {} twapi::console_write {} twapi::_do_console_proc twapi::_console_write stdout - -# Fill an area of the console with the specified attribute -proc twapi::_fill_console {conh args} { - array set opts [parseargs args { - position.arg - numlines.int - numcols.int - {mode.arg column {line column}} - window.bool - fillchar.arg - } -ignoreunknown] - - # args will now contain attribute switches if any - set attr [_console_output_attr_to_flags $args] - - # Get screen buffer info for window and size of buffer - array set csbi [get_console_screen_buffer_info $conh -windowpos -windowsize -size] - # Height and width of the console - lassign $csbi(-size) conx cony - - # Figure out what area we want to fill - # startx,starty are starting position to write - # sizex, sizey are the number of rows/lines - if {[info exists opts(window)]} { - if {[info exists opts(numlines)] || [info exists opts(numcols)] - || [info exists opts(position)]} { - error "Option -window cannot be used togther with options -position, -numlines or -numcols" - } - lassign [_parse_integer_pair $csbi(-windowpos)] startx starty - lassign [_parse_integer_pair $csbi(-windowsize)] sizex sizey - } else { - if {[info exists opts(position)]} { - lassign [_parse_integer_pair $opts(position)] startx starty - } else { - set startx 0 - set starty 0 - } - if {[info exists opts(numlines)]} { - set sizey $opts(numlines) - } else { - set sizey $cony - } - if {[info exists opts(numcols)]} { - set sizex $opts(numcols) - } else { - set sizex [expr {$conx - $startx}] - } - } - - set firstcol [expr {$opts(mode) == "column" ? $startx : 0}] - - # Fill attribute at ($x,$y) - set x $startx - set y $starty - while {$y < $cony && $y < ($starty + $sizey)} { - if {$x < $conx} { - # Write the characters - do not write more than buffer width - set max [expr {$conx-$x}] - if {[info exists attr]} { - FillConsoleOutputAttribute $conh $attr [expr {$sizex > $max ? $max : $sizex}] [list $x $y] - } - if {[info exists opts(fillchar)]} { - FillConsoleOutputCharacter $conh $opts(fillchar) [expr {$sizex > $max ? $max : $sizex}] [list $x $y] - } - } - - # Calculate starting position of next line - incr y - set x $firstcol - } - - return -} -interp alias {} twapi::fill_console {} twapi::_do_console_proc twapi::_fill_console stdout - -# Clear the console -proc twapi::_clear_console {conh args} { - # I support we could just call fill_console but this code was already - # written and is faster - array set opts [parseargs args { - {fillchar.arg " "} - {windowonly.bool 0} - } -maxleftover 0] - - array set cinfo [get_console_screen_buffer_info $conh -size -windowpos -windowsize] - lassign $cinfo(-size) width height - if {$opts(windowonly)} { - # Only clear portion visible in the window. We have to do this - # line by line since we do not want to erase text scrolled off - # the window either in the vertical or horizontal direction - lassign $cinfo(-windowpos) x y - lassign $cinfo(-windowsize) w h - for {set i 0} {$i < $h} {incr i} { - FillConsoleOutputCharacter \ - $conh \ - $opts(fillchar) \ - $w \ - [list $x [expr {$y+$i}]] - } - } else { - FillConsoleOutputCharacter \ - $conh \ - $opts(fillchar) \ - [expr {($width*$height) }] \ - [list 0 0] - } - return -} -interp alias {} twapi::clear_console {} twapi::_do_console_proc twapi::_clear_console stdout -# -# Flush console input -proc twapi::_flush_console_input {conh} { - FlushConsoleInputBuffer $conh -} -interp alias {} twapi::flush_console_input {} twapi::_do_console_proc twapi::_flush_console_input stdin - -# Return number of pending console input events -proc twapi::_get_console_pending_input_count {conh} { - return [GetNumberOfConsoleInputEvents $conh] -} -interp alias {} twapi::get_console_pending_input_count {} twapi::_do_console_proc twapi::_get_console_pending_input_count stdin - -# Generate a console control event -proc twapi::generate_console_control_event {event {procgrp 0}} { - switch -exact -- $event { - ctrl-c {set event 0} - ctrl-break {set event 1} - default {error "Invalid event definition '$event'"} - } - GenerateConsoleCtrlEvent $event $procgrp -} - -# Get number of mouse buttons -proc twapi::num_console_mouse_buttons {} { - return [GetNumberOfConsoleMouseButtons] -} - -# Get console title text -proc twapi::get_console_title {} { - return [GetConsoleTitle] -} - -# Set console title text -proc twapi::set_console_title {title} { - return [SetConsoleTitle $title] -} - -# Get the handle to the console window -proc twapi::get_console_window {} { - return [GetConsoleWindow] -} - -# Get the largest console window size -proc twapi::_get_console_window_maxsize {conh} { - return [GetLargestConsoleWindowSize $conh] -} -interp alias {} twapi::get_console_window_maxsize {} twapi::_do_console_proc twapi::_get_console_window_maxsize stdout - -proc twapi::_set_console_active_screen_buffer {conh} { - SetConsoleActiveScreenBuffer $conh -} -interp alias {} twapi::set_console_active_screen_buffer {} twapi::_do_console_proc twapi::_set_console_active_screen_buffer stdout - -# Set the size of the console screen buffer -proc twapi::_set_console_screen_buffer_size {conh size} { - SetConsoleScreenBufferSize $conh [_parse_integer_pair $size] -} -interp alias {} twapi::set_console_screen_buffer_size {} twapi::_do_console_proc twapi::_set_console_screen_buffer_size stdout - -# Set the default text attribute -proc twapi::_set_console_default_attr {conh args} { - SetConsoleTextAttribute $conh [_console_output_attr_to_flags $args] -} -interp alias {} twapi::set_console_default_attr {} twapi::_do_console_proc twapi::_set_console_default_attr stdout - -# Set the console window position -proc twapi::_set_console_window_location {conh rect args} { - array set opts [parseargs args { - {absolute.bool true} - } -maxleftover 0] - - SetConsoleWindowInfo $conh $opts(absolute) $rect -} -interp alias {} twapi::set_console_window_location {} twapi::_do_console_proc twapi::_set_console_window_location stdout - -proc twapi::get_console_window_location {conh} { - return [lindex [get_console_screen_buffer_info $conh -windowlocation] 1] -} - -# Get the console code page -proc twapi::get_console_output_codepage {} { - return [GetConsoleOutputCP] -} - -# Set the console code page -proc twapi::set_console_output_codepage {cp} { - SetConsoleOutputCP $cp -} - -# Get the console input code page -proc twapi::get_console_input_codepage {} { - return [GetConsoleCP] -} - -# Set the console input code page -proc twapi::set_console_input_codepage {cp} { - SetConsoleCP $cp -} - -# Read a line of input -proc twapi::_console_read {conh args} { - if {[llength $args]} { - set oldmode [modify_console_input_mode $conh {*}$args] - } - trap { - return [ReadConsole $conh 1024] - } finally { - if {[info exists oldmode]} { - set_console_input_mode $conh {*}$oldmode - } - } -} -interp alias {} twapi::console_read {} twapi::_do_console_proc twapi::_console_read stdin - -proc twapi::_map_console_controlkeys {control} { - return [_make_symbolic_bitmask $control { - capslock 0x80 - enhanced 0x100 - leftalt 0x2 - leftctrl 0x8 - numlock 0x20 - rightalt 0x1 - rightctrl 4 - scrolllock 0x40 - shift 0x10 - } 0] -} - -proc twapi::_console_read_input_records {conh args} { - parseargs args { - {count.int 1} - peek - } -setvars -maxleftover 0 - set recs {} - if {$peek} { - set input [PeekConsoleInput $conh $count] - } else { - set input [ReadConsoleInput $conh $count] - } - foreach rec $input { - switch [format %d [lindex $rec 0]] { - 1 { - lassign [lindex $rec 1] keydown repeat keycode scancode char controlstate - lappend recs \ - [list key [list \ - keystate [expr {$keydown ? "down" : "up"}] \ - repeat $repeat keycode $keycode \ - scancode $scancode char $char \ - controls [_map_console_controlkeys $controlstate]]] - } - 2 { - lassign [lindex $rec 1] position buttonstate controlstate flags - set buttons {} - if {[expr {$buttonstate & 0x1}]} {lappend buttons left} - if {[expr {$buttonstate & 0x2}]} {lappend buttons right} - if {[expr {$buttonstate & 0x4}]} {lappend buttons left2} - if {[expr {$buttonstate & 0x8}]} {lappend buttons left3} - if {[expr {$buttonstate & 0x10}]} {lappend buttons left4} - if {$flags & 0x8} { - set horizontalwheel [expr {$buttonstate >> 16}] - } else { - set horizontalwheel 0 - } - if {$flags & 0x4} { - set verticalwheel [expr {$buttonstate >> 16}] - } else { - set verticalwheel 0 - } - lappend recs \ - [list mouse [list \ - position $position \ - buttons $buttons \ - controls [_map_console_controlkeys $controlstate] \ - doubleclick [expr {$flags & 0x2}] \ - horizontalwheel $horizontalwheel \ - moved [expr {$flags & 0x1}] \ - verticalwheel $verticalwheel]] - } - default { - lappend recs [list \ - [dict* {4 buffersize 8 menu 16 focus} [lindex $rec 0]] \ - [lindex $rec 1]] - } - } - } - return $recs -} -interp alias {} twapi::console_read_input_records {} twapi::_do_console_proc twapi::_console_read_input_records stdin - -# Set up a console handler -proc twapi::_console_ctrl_handler {ctrl} { - variable _console_control_script - if {[info exists _console_control_script]} { - return [uplevel #0 [linsert $_console_control_script end $ctrl]] - } - return 0; # Not handled -} -proc twapi::set_console_control_handler {script} { - variable _console_control_script - if {[string length $script]} { - if {![info exists _console_control_script]} { - Twapi_ConsoleEventNotifier 1 - } - set _console_control_script $script - } else { - if {[info exists _console_control_script]} { - Twapi_ConsoleEventNotifier 0 - unset _console_control_script - } - } -} - -# -# Utilities -# - -# Helper to call a proc after doing a stdin/stdout/stderr -> handle -# mapping. The handle is closed after calling the proc. The first -# arg in $args must be the console handle if $args is not an empty list -proc twapi::_do_console_proc {proc default args} { - if {[llength $args] == 0} { - set args [list $default] - } - set conh [lindex $args 0] - switch -exact -- [string tolower $conh] { - stdin - - stdout - - stderr { - set real_handle [get_console_handle $conh] - trap { - lset args 0 $real_handle - return [uplevel 1 [list $proc] $args] - } finally { - CloseHandle $real_handle - } - } - } - - return [uplevel 1 [list $proc] $args] -} - -proc twapi::_console_input_mode_syms {} { - return { - -processedinput 0x0001 - -lineinput 0x0002 - -echoinput 0x0004 - -windowinput 0x0008 - -mouseinput 0x0010 - -insertmode 0x0020 - -quickeditmode 0x0040 - -extendedmode 0x0080 - -autoposition 0x0100 - } -} - -proc twapi::_console_output_mode_syms {} { - return { -processedoutput 1 -wrapoutput 2 } -} - -twapi::proc* twapi::_console_output_attr {sym} { - variable _console_output_attr_syms - array set _console_output_attr_syms { - -fgblue 1 - -fggreen 2 - -fgturquoise 3 - -fgred 4 - -fgpurple 5 - -fgyellow 6 - -fggray 7 - -fgbright 8 - -fgwhite 15 - -bgblue 16 - -bggreen 32 - -bgturquoise 48 - -bgred 64 - -bgpurple 80 - -bgyellow 96 - -bggray 112 - -bgbright 128 - -bgwhite 240 - } -} { - variable _console_output_attr_syms - if {[info exists _console_output_attr_syms($sym)]} { - return $_console_output_attr_syms($sym) - } - - badargs! "Invalid console output attribute '$sym'" 3 -} - +# +# Copyright (c) 2004-2014, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { +} + +# Allocate a new console +proc twapi::allocate_console {} { + AllocConsole +} + +# Free a console +proc twapi::free_console {} { + FreeConsole +} + +# Get a console handle +proc twapi::get_console_handle {type} { + switch -exact -- $type { + 0 - + stdin { set fn "CONIN\$" } + 1 - + stdout - + 2 - + stderr { set fn "CONOUT\$" } + default { + error "Unknown console handle type '$type'" + } + } + + # 0xC0000000 -> GENERIC_READ | GENERIC_WRITE + # 3 -> FILE_SHARE_READ | FILE_SHARE_WRITE + # 3 -> OPEN_EXISTING + return [CreateFile $fn \ + 0xC0000000 \ + 3 \ + {{} 1} \ + 3 \ + 0 \ + NULL] +} + +proc twapi::_standard_handle_type {type} { + if {[string is integer -strict $type]} { + set type [format %d $type] ; # Convert hex etc. + } + switch -exact -- $type { + 0 - + -10 - + stdin { set type -10 } + 1 - + -11 - + stdout { set type -11 } + 2 - + -12 - + stderr { set type -12 } + default { + error "Unknown console handle type '$type'" + } + } + return $type +} + +# Get a console handle +proc twapi::get_standard_handle {type} { + return [GetStdHandle [_standard_handle_type $type]] +} + +# Set a console handle +proc twapi::set_standard_handle {type handle} { + return [SetStdHandle [_standard_handle_type $type] $handle] +} + +proc twapi::_console_output_attr_to_flags {attrs} { + set flags 0 + foreach {attr bool} $attrs { + if {$bool} { + set flags [expr {$flags | [_console_output_attr $attr]}] + } + } + return $flags +} + +proc twapi::_flags_to_console_output_attr {flags} { + # Check for multiple bit attributes first, in order + set attrs {} + foreach attr { + -fgwhite -bgwhite -fggray -bggray + -fgturquoise -bgturquoise -fgpurple -bgpurple -fgyellow -bgyellow + -fgred -bgred -fggreen -bggreen -fgblue -bgblue + -fgbright -bgbright + } { + if {($flags & [_console_output_attr $attr]) == [_console_output_attr $attr]} { + lappend attrs $attr 1 + set flags [expr {$flags & ~ [_console_output_attr $attr]}] + if {$flags == 0} { + break + } + } + } + + return $attrs +} + + +# Get the current mode settings for the console +proc twapi::_get_console_input_mode {conh} { + set mode [GetConsoleMode $conh] + return [_bitmask_to_switches $mode [_console_input_mode_syms]] +} +interp alias {} twapi::get_console_input_mode {} twapi::_do_console_proc twapi::_get_console_input_mode stdin + +# Get the current mode settings for the console +proc twapi::_get_console_output_mode {conh} { + set mode [GetConsoleMode $conh] + return [_bitmask_to_switches $mode [_console_output_mode_syms]] +} +interp alias {} twapi::get_console_output_mode {} twapi::_do_console_proc twapi::_get_console_output_mode stdout + +# Set console input mode +proc twapi::_set_console_input_mode {conh args} { + set mode [_switches_to_bitmask $args [_console_input_mode_syms]] + # If insertmode or quickedit mode are set, make sure to set extended bit + if {$mode & 0x60} { + setbits mode 0x80; # ENABLE_EXTENDED_FLAGS + } + + SetConsoleMode $conh $mode +} +interp alias {} twapi::set_console_input_mode {} twapi::_do_console_proc twapi::_set_console_input_mode stdin + +# Modify console input mode +proc twapi::_modify_console_input_mode {conh args} { + set prev [GetConsoleMode $conh] + set mode [_switches_to_bitmask $args [_console_input_mode_syms] $prev] + # If insertmode or quickedit mode are set, make sure to set extended bit + if {$mode & 0x60} { + setbits mode 0x80; # ENABLE_EXTENDED_FLAGS + } + + SetConsoleMode $conh $mode + # Returns the old modes + return [_bitmask_to_switches $prev [_console_input_mode_syms]] +} +interp alias {} twapi::modify_console_input_mode {} twapi::_do_console_proc twapi::_modify_console_input_mode stdin + +# +# Set console output mode +proc twapi::_set_console_output_mode {conh args} { + set mode [_switches_to_bitmask $args [_console_output_mode_syms]] + + SetConsoleMode $conh $mode + +} +interp alias {} twapi::set_console_output_mode {} twapi::_do_console_proc twapi::_set_console_output_mode stdout + +# Set console output mode +proc twapi::_modify_console_output_mode {conh args} { + set prev [GetConsoleMode $conh] + set mode [_switches_to_bitmask $args [_console_output_mode_syms] $prev] + + SetConsoleMode $conh $mode + # Returns the old modes + return [_bitmask_to_switches $prev [_console_output_mode_syms]] +} +interp alias {} twapi::modify_console_output_mode {} twapi::_do_console_proc twapi::_modify_console_output_mode stdout + + +# Create and return a handle to a screen buffer +proc twapi::create_console_screen_buffer {args} { + array set opts [parseargs args { + {inherit.bool 0} + {mode.arg readwrite {read write readwrite}} + {secd.arg ""} + {share.arg readwrite {none read write readwrite}} + } -maxleftover 0] + + switch -exact -- $opts(mode) { + read { set mode [_access_rights_to_mask generic_read] } + write { set mode [_access_rights_to_mask generic_write] } + readwrite { + set mode [_access_rights_to_mask {generic_read generic_write}] + } + } + switch -exact -- $opts(share) { + none { + set share 0 + } + read { + set share 1 ;# FILE_SHARE_READ + } + write { + set share 2 ;# FILE_SHARE_WRITE + } + readwrite { + set share 3 + } + } + + return [CreateConsoleScreenBuffer \ + $mode \ + $share \ + [_make_secattr $opts(secd) $opts(inherit)] \ + 1] +} + +# Retrieve information about a console screen buffer +proc twapi::_get_console_screen_buffer_info {conh args} { + array set opts [parseargs args { + all + textattr + cursorpos + maxwindowsize + size + windowlocation + windowpos + windowsize + } -maxleftover 0] + + lassign [GetConsoleScreenBufferInfo $conh] size cursorpos textattr windowlocation maxwindowsize + + set result [list ] + foreach opt {size cursorpos maxwindowsize windowlocation} { + if {$opts($opt) || $opts(all)} { + lappend result -$opt [set $opt] + } + } + + if {$opts(windowpos) || $opts(all)} { + lappend result -windowpos [lrange $windowlocation 0 1] + } + + if {$opts(windowsize) || $opts(all)} { + lassign $windowlocation left top right bot + lappend result -windowsize [list [expr {$right-$left+1}] [expr {$bot-$top+1}]] + } + + if {$opts(textattr) || $opts(all)} { + lappend result -textattr [_flags_to_console_output_attr $textattr] + } + + return $result +} +interp alias {} twapi::get_console_screen_buffer_info {} twapi::_do_console_proc twapi::_get_console_screen_buffer_info stdout + +# Set the cursor position +proc twapi::_set_console_cursor_position {conh pos} { + SetConsoleCursorPosition $conh $pos +} +interp alias {} twapi::set_console_cursor_position {} twapi::_do_console_proc twapi::_set_console_cursor_position stdout + +# Get the cursor position +proc twapi::get_console_cursor_position {conh} { + return [lindex [get_console_screen_buffer_info $conh -cursorpos] 1] +} + +# Write the specified string to the console +proc twapi::_console_write {conh s args} { + # Note writes are always in raw mode, + # TBD - support for scrolling + # TBD - support for attributes + + array set opts [parseargs args { + position.arg + {newlinemode.arg column {line column}} + {restoreposition.bool 0} + } -maxleftover 0] + + # Get screen buffer info including cursor position + array set csbi [get_console_screen_buffer_info $conh -cursorpos -size] + + # Get current console mode for later restoration + # If console is in processed mode, set it to raw mode + set oldmode [get_console_output_mode $conh] + set processed_index [lsearch -exact $oldmode "processed"] + if {$processed_index >= 0} { + # Console was in processed mode. Set it to raw mode + set newmode [lreplace $oldmode $processed_index $processed_index] + set_console_output_mode $conh $newmode + } + + trap { + # x,y are starting position to write + if {[info exists opts(position)]} { + lassign [_parse_integer_pair $opts(position)] x y + } else { + # No position specified, get current cursor position + lassign $csbi(-cursorpos) x y + } + + set startx [expr {$opts(newlinemode) == "column" ? $x : 0}] + + # Get screen buffer limits + lassign $csbi(-size) width height + + # Ensure line terminations are just \n + set s [string map [list \r\n \n] $s] + + # Write out each line at ($x,$y) + # Either \r or \n is considered a newline + foreach line [split $s \r\n] { + if {$y >= $height} break + set_console_cursor_position $conh [list $x $y] + if {$x < $width} { + # Write the characters - do not write more than buffer width + set num_chars [expr {$width-$x}] + if {[string length $line] < $num_chars} { + set num_chars [string length $line] + } + WriteConsole $conh $line $num_chars + } + + + # Calculate starting position of next line + incr y + set x $startx + } + + } finally { + # Restore cursor if requested + if {$opts(restoreposition)} { + set_console_cursor_position $conh $csbi(-cursorpos) + } + # Restore output mode if changed + if {[info exists newmode]} { + set_console_output_mode $conh $oldmode + } + } + + return +} +interp alias {} twapi::write_console {} twapi::_do_console_proc twapi::_console_write stdout +interp alias {} twapi::console_write {} twapi::_do_console_proc twapi::_console_write stdout + +# Fill an area of the console with the specified attribute +proc twapi::_fill_console {conh args} { + array set opts [parseargs args { + position.arg + numlines.int + numcols.int + {mode.arg column {line column}} + window.bool + fillchar.arg + } -ignoreunknown] + + # args will now contain attribute switches if any + set attr [_console_output_attr_to_flags $args] + + # Get screen buffer info for window and size of buffer + array set csbi [get_console_screen_buffer_info $conh -windowpos -windowsize -size] + # Height and width of the console + lassign $csbi(-size) conx cony + + # Figure out what area we want to fill + # startx,starty are starting position to write + # sizex, sizey are the number of rows/lines + if {[info exists opts(window)]} { + if {[info exists opts(numlines)] || [info exists opts(numcols)] + || [info exists opts(position)]} { + error "Option -window cannot be used togther with options -position, -numlines or -numcols" + } + lassign [_parse_integer_pair $csbi(-windowpos)] startx starty + lassign [_parse_integer_pair $csbi(-windowsize)] sizex sizey + } else { + if {[info exists opts(position)]} { + lassign [_parse_integer_pair $opts(position)] startx starty + } else { + set startx 0 + set starty 0 + } + if {[info exists opts(numlines)]} { + set sizey $opts(numlines) + } else { + set sizey $cony + } + if {[info exists opts(numcols)]} { + set sizex $opts(numcols) + } else { + set sizex [expr {$conx - $startx}] + } + } + + set firstcol [expr {$opts(mode) == "column" ? $startx : 0}] + + # Fill attribute at ($x,$y) + set x $startx + set y $starty + while {$y < $cony && $y < ($starty + $sizey)} { + if {$x < $conx} { + # Write the characters - do not write more than buffer width + set max [expr {$conx-$x}] + if {[info exists attr]} { + FillConsoleOutputAttribute $conh $attr [expr {$sizex > $max ? $max : $sizex}] [list $x $y] + } + if {[info exists opts(fillchar)]} { + FillConsoleOutputCharacter $conh $opts(fillchar) [expr {$sizex > $max ? $max : $sizex}] [list $x $y] + } + } + + # Calculate starting position of next line + incr y + set x $firstcol + } + + return +} +interp alias {} twapi::fill_console {} twapi::_do_console_proc twapi::_fill_console stdout + +# Clear the console +proc twapi::_clear_console {conh args} { + # I support we could just call fill_console but this code was already + # written and is faster + array set opts [parseargs args { + {fillchar.arg " "} + {windowonly.bool 0} + } -maxleftover 0] + + array set cinfo [get_console_screen_buffer_info $conh -size -windowpos -windowsize] + lassign $cinfo(-size) width height + if {$opts(windowonly)} { + # Only clear portion visible in the window. We have to do this + # line by line since we do not want to erase text scrolled off + # the window either in the vertical or horizontal direction + lassign $cinfo(-windowpos) x y + lassign $cinfo(-windowsize) w h + for {set i 0} {$i < $h} {incr i} { + FillConsoleOutputCharacter \ + $conh \ + $opts(fillchar) \ + $w \ + [list $x [expr {$y+$i}]] + } + } else { + FillConsoleOutputCharacter \ + $conh \ + $opts(fillchar) \ + [expr {($width*$height) }] \ + [list 0 0] + } + return +} +interp alias {} twapi::clear_console {} twapi::_do_console_proc twapi::_clear_console stdout +# +# Flush console input +proc twapi::_flush_console_input {conh} { + FlushConsoleInputBuffer $conh +} +interp alias {} twapi::flush_console_input {} twapi::_do_console_proc twapi::_flush_console_input stdin + +# Return number of pending console input events +proc twapi::_get_console_pending_input_count {conh} { + return [GetNumberOfConsoleInputEvents $conh] +} +interp alias {} twapi::get_console_pending_input_count {} twapi::_do_console_proc twapi::_get_console_pending_input_count stdin + +# Generate a console control event +proc twapi::generate_console_control_event {event {procgrp 0}} { + switch -exact -- $event { + ctrl-c {set event 0} + ctrl-break {set event 1} + default {error "Invalid event definition '$event'"} + } + GenerateConsoleCtrlEvent $event $procgrp +} + +# Get number of mouse buttons +proc twapi::num_console_mouse_buttons {} { + return [GetNumberOfConsoleMouseButtons] +} + +# Get console title text +proc twapi::get_console_title {} { + return [GetConsoleTitle] +} + +# Set console title text +proc twapi::set_console_title {title} { + return [SetConsoleTitle $title] +} + +# Get the handle to the console window +proc twapi::get_console_window {} { + return [GetConsoleWindow] +} + +# Get the largest console window size +proc twapi::_get_console_window_maxsize {conh} { + return [GetLargestConsoleWindowSize $conh] +} +interp alias {} twapi::get_console_window_maxsize {} twapi::_do_console_proc twapi::_get_console_window_maxsize stdout + +proc twapi::_set_console_active_screen_buffer {conh} { + SetConsoleActiveScreenBuffer $conh +} +interp alias {} twapi::set_console_active_screen_buffer {} twapi::_do_console_proc twapi::_set_console_active_screen_buffer stdout + +# Set the size of the console screen buffer +proc twapi::_set_console_screen_buffer_size {conh size} { + SetConsoleScreenBufferSize $conh [_parse_integer_pair $size] +} +interp alias {} twapi::set_console_screen_buffer_size {} twapi::_do_console_proc twapi::_set_console_screen_buffer_size stdout + +# Set the default text attribute +proc twapi::_set_console_default_attr {conh args} { + SetConsoleTextAttribute $conh [_console_output_attr_to_flags $args] +} +interp alias {} twapi::set_console_default_attr {} twapi::_do_console_proc twapi::_set_console_default_attr stdout + +# Set the console window position +proc twapi::_set_console_window_location {conh rect args} { + array set opts [parseargs args { + {absolute.bool true} + } -maxleftover 0] + + SetConsoleWindowInfo $conh $opts(absolute) $rect +} +interp alias {} twapi::set_console_window_location {} twapi::_do_console_proc twapi::_set_console_window_location stdout + +proc twapi::get_console_window_location {conh} { + return [lindex [get_console_screen_buffer_info $conh -windowlocation] 1] +} + +# Get the console code page +proc twapi::get_console_output_codepage {} { + return [GetConsoleOutputCP] +} + +# Set the console code page +proc twapi::set_console_output_codepage {cp} { + SetConsoleOutputCP $cp +} + +# Get the console input code page +proc twapi::get_console_input_codepage {} { + return [GetConsoleCP] +} + +# Set the console input code page +proc twapi::set_console_input_codepage {cp} { + SetConsoleCP $cp +} + +# Read a line of input +proc twapi::_console_read {conh args} { + if {[llength $args]} { + set oldmode [modify_console_input_mode $conh {*}$args] + } + trap { + return [ReadConsole $conh 1024] + } finally { + if {[info exists oldmode]} { + set_console_input_mode $conh {*}$oldmode + } + } +} +interp alias {} twapi::console_read {} twapi::_do_console_proc twapi::_console_read stdin + +proc twapi::_map_console_controlkeys {control} { + return [_make_symbolic_bitmask $control { + capslock 0x80 + enhanced 0x100 + leftalt 0x2 + leftctrl 0x8 + numlock 0x20 + rightalt 0x1 + rightctrl 4 + scrolllock 0x40 + shift 0x10 + } 0] +} + +proc twapi::_console_read_input_records {conh args} { + parseargs args { + {count.int 1} + peek + } -setvars -maxleftover 0 + set recs {} + if {$peek} { + set input [PeekConsoleInput $conh $count] + } else { + set input [ReadConsoleInput $conh $count] + } + foreach rec $input { + switch [format %d [lindex $rec 0]] { + 1 { + lassign [lindex $rec 1] keydown repeat keycode scancode char controlstate + lappend recs \ + [list key [list \ + keystate [expr {$keydown ? "down" : "up"}] \ + repeat $repeat keycode $keycode \ + scancode $scancode char $char \ + controls [_map_console_controlkeys $controlstate]]] + } + 2 { + lassign [lindex $rec 1] position buttonstate controlstate flags + set buttons {} + if {[expr {$buttonstate & 0x1}]} {lappend buttons left} + if {[expr {$buttonstate & 0x2}]} {lappend buttons right} + if {[expr {$buttonstate & 0x4}]} {lappend buttons left2} + if {[expr {$buttonstate & 0x8}]} {lappend buttons left3} + if {[expr {$buttonstate & 0x10}]} {lappend buttons left4} + if {$flags & 0x8} { + set horizontalwheel [expr {$buttonstate >> 16}] + } else { + set horizontalwheel 0 + } + if {$flags & 0x4} { + set verticalwheel [expr {$buttonstate >> 16}] + } else { + set verticalwheel 0 + } + lappend recs \ + [list mouse [list \ + position $position \ + buttons $buttons \ + controls [_map_console_controlkeys $controlstate] \ + doubleclick [expr {$flags & 0x2}] \ + horizontalwheel $horizontalwheel \ + moved [expr {$flags & 0x1}] \ + verticalwheel $verticalwheel]] + } + default { + lappend recs [list \ + [dict* {4 buffersize 8 menu 16 focus} [lindex $rec 0]] \ + [lindex $rec 1]] + } + } + } + return $recs +} +interp alias {} twapi::console_read_input_records {} twapi::_do_console_proc twapi::_console_read_input_records stdin + +# Set up a console handler +proc twapi::_console_ctrl_handler {ctrl} { + variable _console_control_script + if {[info exists _console_control_script]} { + return [uplevel #0 [linsert $_console_control_script end $ctrl]] + } + return 0; # Not handled +} +proc twapi::set_console_control_handler {script} { + variable _console_control_script + if {[string length $script]} { + if {![info exists _console_control_script]} { + Twapi_ConsoleEventNotifier 1 + } + set _console_control_script $script + } else { + if {[info exists _console_control_script]} { + Twapi_ConsoleEventNotifier 0 + unset _console_control_script + } + } +} + +# +# Utilities +# + +# Helper to call a proc after doing a stdin/stdout/stderr -> handle +# mapping. The handle is closed after calling the proc. The first +# arg in $args must be the console handle if $args is not an empty list +proc twapi::_do_console_proc {proc default args} { + if {[llength $args] == 0} { + set args [list $default] + } + set conh [lindex $args 0] + switch -exact -- [string tolower $conh] { + stdin - + stdout - + stderr { + set real_handle [get_console_handle $conh] + trap { + lset args 0 $real_handle + return [uplevel 1 [list $proc] $args] + } finally { + CloseHandle $real_handle + } + } + } + + return [uplevel 1 [list $proc] $args] +} + +proc twapi::_console_input_mode_syms {} { + return { + -processedinput 0x0001 + -lineinput 0x0002 + -echoinput 0x0004 + -windowinput 0x0008 + -mouseinput 0x0010 + -insertmode 0x0020 + -quickeditmode 0x0040 + -extendedmode 0x0080 + -autoposition 0x0100 + } +} + +proc twapi::_console_output_mode_syms {} { + return { -processedoutput 1 -wrapoutput 2 } +} + +twapi::proc* twapi::_console_output_attr {sym} { + variable _console_output_attr_syms + array set _console_output_attr_syms { + -fgblue 1 + -fggreen 2 + -fgturquoise 3 + -fgred 4 + -fgpurple 5 + -fgyellow 6 + -fggray 7 + -fgbright 8 + -fgwhite 15 + -bgblue 16 + -bggreen 32 + -bgturquoise 48 + -bgred 64 + -bgpurple 80 + -bgyellow 96 + -bggray 112 + -bgbright 128 + -bgwhite 240 + } +} { + variable _console_output_attr_syms + if {[info exists _console_output_attr_syms($sym)]} { + return $_console_output_attr_syms($sym) + } + + badargs! "Invalid console output attribute '$sym'" 3 +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/crypto.tcl b/src/vendorlib_tcl8/twapi-5.0b1/crypto.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/crypto.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/crypto.tcl index b7cc9c32..33e6afad 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/crypto.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/crypto.tcl @@ -1,3457 +1,3456 @@ -# -# Copyright (c) 2007-2021, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - variable wintrust_guids - # Array key names match those in softpub.h in SDK - array set wintrust_guids { - action_generic_verify_v2 00AAC56B-CD44-11d0-8CC2-00C04FC295EE - action_trust_provider_test 573E31F8-DDBA-11d0-8CCB-00C04FC295EE - action_generic_cert_verify 189A3842-3041-11d1-85E1-00C04FC295EE - action_generic_chain_verify fc451c16-ac75-11d1-b4b8-00c04fb66ea0 - httpsprov_action 573E31F8-AABA-11d0-8CCB-00C04FC295EE - driver_action_verify F750E6C3-38EE-11d1-85E5-00C04FC295EE - } - - # Dictionaries used by capi_encrypt|decrypt_bytes to store partial blocks of data - # First level key is Crypto key handle - # Second level keys are Blocklen (block size in bytes) and Data (data bytes left over) - variable _capi_encrypt_partials - variable _capi_decrypt_partials - set _capi_encrypt_partials {} - set _capi_decrypt_partials {} -} - -### Hash functions - -proc twapi::capi_hash_create {hcrypt algid {hkey NULL}} { - return [CryptCreateHash $hcrypt [capi_algid $algid] $hkey] -} - -proc twapi::capi_hash_string {hhash s {enc utf-8}} { - return [capi_hash_bytes $hhash [encoding convertto $enc $s] 0] -} - -proc twapi::capi_hash_value {hhash} { - return [CryptGetHashParam $hhash 2]; # HP_HASHVAL -} - -proc twapi::capi_hash_sign {hhash keyspec args} { - # -pad not documented because new Windows version do not support X.931 - # and there are some openssl incompatibilities I cannot figure out - parseargs args { - {nohashoid.bool 0 1} - {pad.arg pkcs1 {pkcs1 x931}} - } -maxleftover 0 -setvars - set flags [expr {[dict get {pkcs1 0 x931 4} $pad] | $nohashoid}] - return [CryptSignHash $hhash [_crypt_keyspec $keyspec] "" $flags] -} - -proc twapi::capi_hash_verify {hhash sig hkey args} { - # -pad not documented because new Windows version do not support X.931 - # and there are some openssl incompatibilities I cannot figure out - parseargs args { - {nohashoid.bool 0 1} - {pad.arg pkcs1 {pkcs1 x931}} - } -maxleftover 0 -setvars - set flags [expr {[dict get {pkcs1 0 x931 4} $pad] | $nohashoid}] - return [CryptVerifySignature $hhash $sig $hkey "" $flags] -} - -proc twapi::_do_hash {csptype alg s {enc ""}} { - if {$enc ne ""} { - set s [encoding convertto $enc $s] - } - set hcrypt [crypt_acquire -csptype $csptype] - trap { - set hhash [capi_hash_create $hcrypt $alg] - capi_hash_bytes $hhash $s - return [capi_hash_value $hhash] - } finally { - if {[info exists hhash]} { - capi_hash_free $hhash - } - crypt_free $hcrypt - } -} - -interp alias {} twapi::md5 {} twapi::_do_hash prov_rsa_full md5 -interp alias {} twapi::sha1 {} twapi::_do_hash prov_rsa_full sha1 -interp alias {} twapi::sha256 {} twapi::_do_hash prov_rsa_aes sha_256 -interp alias {} twapi::sha384 {} twapi::_do_hash prov_rsa_aes sha_384 -interp alias {} twapi::sha512 {} twapi::_do_hash prov_rsa_aes sha_512 - -proc twapi::hmac {data key {prf sha1} {charset {}}} { - if {$charset ne ""} { - set data [encoding convertto $charset $data] - } - - # Choose prov_rsa_aes because older CSP's do not support sha256 - set hcrypt [crypt_acquire -csptype prov_rsa_aes] - try { - # The algorithm specified for importing the key actually is not - # executed at all. It's only used for importing the key. - # However it has to be something that will accept any key size. - # On Windows 8 at least, RC4 seems to require at least 5 byte keys. - # RC2 on the other hand, if the -ipsechmac flag is specifie - # will accept any number. TBD - the pbkdf2 source code implies - # on Win8.1 single byte keys will not be accepted by rc2 and - # keys need to be padded with 0's. Need to check that. - set hkey [crypt_import_key $hcrypt [capi_keyblob_concealed rc2 $key] -ipsechmac 1] - set hhash [capi_hash_create $hcrypt hmac $hkey] - # 5 -> HP_HMAC_INFO - CryptSetHashParam $hhash 5 [list [capi_algid $prf] "" ""] - capi_hash_bytes $hhash $data - return [capi_hash_value $hhash] - } finally { - if {[info exists hhash]} { - capi_hash_free $hhash - } - if {[info exists hkey]} { - capi_key_free $hkey - } - crypt_free $hcrypt - } -} - - -### Data protection - -proc twapi::protect_data {data args} { - - # Not used because doesn't seem to have any effect - # {promptonunprotect.bool 0 0x1} - parseargs args { - {description.arg ""} - {localmachine.bool 0 0x4} - {noui.bool 0 0x1} - {audit.bool 0 0x10} - {hwnd.arg NULL} - prompt.arg - } -setvars -maxleftover 0 - - if {[info exists prompt]} { - # 2 -> PROMPTONPROTECT - set prompt [list 2 $hwnd $prompt] - } else { - set prompt {} - } - - return [CryptProtectData $data $description "" "" $prompt [expr {$localmachine | $noui | $audit}]] -} - -proc twapi::unprotect_data {data args} { - # Do not seem to have any effect - # {promptonunprotect.bool 0 0x1} - # {promptonprotect.bool 0 0x2} - parseargs args { - {withdescription.bool 0} - {noui.bool 0 0x1} - {hwnd.arg NULL} - prompt.arg - } -setvars -maxleftover 0 - - if {[info exists prompt]} { - # 2 -> PROMPTONPROTECT - set prompt [list 2 $hwnd $prompt] - } else { - set prompt {} - } - - set data [CryptUnprotectData $data "" "" $prompt $noui] - if {$withdescription} { - return $data - } else { - return [lindex $data 0] - } -} - - - -################################################################ -# Certificate Stores - -# Close a certificate store -proc twapi::cert_store_release {hstore} { - CertCloseStore $hstore 0 - return -} - -proc twapi::cert_temporary_store {args} { - # TBD - add support for PKCS12_NO_PERSIST_KEY post-XP. If not - # specified and on XP document a means of getting rid of the key - # containers. See https://msdn.microsoft.com/en-us/library/ms867088.aspx#pk_topic6 - # Also CryptAcquireCertificatePrivateKey and GetCryptProvFromCert - # might be useful in this regard - parseargs args { - {encoding.arg {} {der pem {}}} - serialized.arg - pkcs7.arg - {password.arg ""} - pfx.arg - pkcs12.arg - {exportableprivatekeys.bool 0 1} - {userprotected.bool 0 2} - keysettype.arg - } -setvars -maxleftover 0 - - set nformats 0 - foreach format {serialized pkcs7 pfx pkcs12} { - if {[info exists $format]} { - set data [set $format] - incr nformats - } - } - if {$nformats > 1} { - badargs! "At most one of -pfx, -pkcs12, -pkcs7 or -serialized may be specified." - } - if {$nformats == 0} { - # 2 -> CERT_STORE_PROV_MEMORY - return [CertOpenStore 2 0 NULL 0 ""] - } - - # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING - - if {[info exists serialized]} { - # 6 -> CERT_STORE_PROV_SERIALIZED - return [CertOpenStore 6 0x10001 NULL 0 $data] - } - - if {[info exists pkcs7]} { - # 5 -> CERT_STORE_PROV_PKCS7 - return [CertOpenStore 5 0x10001 NULL 0 [_pem_decode $data $encoding]] - } - - # PFX/PKCS12 - if {[string length $password] == 0} { - set password [conceal ""] - } - set flags 0 - if {[info exists keysettype]} { - set flags [dict! {user 0x1000 machine 0x20} $keysettype] - } - - set flags [tcl::mathop::| $flags $exportableprivatekeys $userprotected] - return [PFXImportCertStore $data $password $flags] -} - -proc twapi::cert_file_store_open {path args} { - set flags [_parse_store_open_opts $args] - - if {! ($flags & 0x00008000)} { - # If not readonly, set commitenable - set flags [expr {$flags | 0x00010000}] - } - - # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING - # 8 -> CERT_STORE_PROV_FILENAME_W - return [CertOpenStore 8 0x10001 NULL $flags [file nativename [file normalize $path]]] -} - -proc twapi::cert_serialized_store_open {data args} { - set flags [_parse_store_open_opts $args] - - # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING - # 6 -> CERT_STORE_PROV_SERIALIZED - return [CertOpenStore 6 0x10001 NULL $flags $data] -} - -proc twapi::cert_physical_store_open {name location args} { - variable _system_stores - - set flags [_parse_store_open_opts $args] - incr flags [_system_store_id $location] - # 14 -> CERT_STORE_PROV_PHYSICAL_W - return [CertOpenStore 14 0 NULL $flags $name] -} - -proc twapi::cert_physical_store_delete {name location} { - set flags 0x10; # CERT_STORE_DELETE_FLAG - incr flags [_system_store_id $location] - - # 14 -> CERT_STORE_PROV_PHYSICAL_W - return [CertOpenStore 14 0 NULL $flags $name] -} - -# TBD - document and figure out what format to return data in -proc twapi::cert_physical_stores {system_store_name location} { - return [CertEnumPhysicalStore $system_store_name [_system_store_id $location]] -} - -proc twapi::cert_system_store_open {name args} { - variable _system_stores - - if {[llength $args] == 0} { - return [CertOpenSystemStore $name] - } - - set flags [_parse_store_open_opts [lassign $args location]] - incr flags [_system_store_id $location] - return [CertOpenStore 10 0 NULL $flags $name] -} - -proc twapi::cert_system_store_delete {name location} { - set flags 0x10; # CERT_STORE_DELETE_FLAG - incr flags [_system_store_id $location] - return [CertOpenStore 10 0 NULL $flags $name] -} - -proc twapi::cert_system_store_locations {} { - set l {} - foreach e [CertEnumSystemStoreLocation 0] { - lappend l [lindex $e 0] - } - return $l -} - -proc twapi::cert_system_stores {location} { - set l {} - foreach e [CertEnumSystemStore [_system_store_id $location] ""] { - lappend l [lindex $e 0] - } - return $l -} - -proc twapi::cert_store_iterate {hstore varname script {type any} {term {}}} { - upvar 1 $varname cert - set cert NULL - while {1} { - set cert [cert_store_find_certificate $hstore $type $term $cert] - if {$cert eq ""} break - switch [catch {uplevel 1 $script} result options] { - 0 - - 4 { - # Normal execution or continue. Keep $cert to get next cert - # from store - } - 3 { - # break - get out of loop so free the last cert - cert_release $cert - set cert "" - return - } - 1 - - default { - cert_release $cert - set cert "" - return -options $options $result - } - } - } - return -} - -proc twapi::cert_store_find_certificate {hstore {type any} {term {}} {hcert NULL}} { - - # TBD subject_cert 11<<16 - # TBD key_spec 9<<16 - - set term_types { - any 0 - existing 13<<16 - key_identifier 15<<16 - md5_hash 4<<16 - subject_public_key_md5_hash 18<<16 - sha1_hash 1<<16 - signature_hash 14<<16 - issuer_name (2<<16)|4 - subject_name (2<<16)|7 - issuer_substring (8<<16)|4 - subject_substring (8<<16)|7 - property 5<<16 - public_key 6<<16 - } - - if {$type eq "property"} { - set term [_cert_prop_id $term] - } - set type [expr [dict! $term_types $type 1]] - - # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING - return [CertFindCertificateInStore $hstore 0x10001 0 $type $term $hcert] -} - -proc twapi::cert_store_enum_contents {hstore {hcert NULL}} { - return [CertEnumCertificatesInStore $hstore $hcert] -} - -proc twapi::cert_store_add_certificate {hstore hcert args} { - array set opts [_cert_add_parseargs args] - return [CertAddCertificateContextToStore $hstore $hcert $opts(disposition)] -} - -proc twapi::cert_store_add_encoded_certificate {hstore enccert args} { - parseargs args { - {encoding.arg {} {der pem {}}} - } -ignoreunknown -setvars - array set opts [_cert_add_parseargs args] - return [CertAddEncodedCertificateToStore $hstore 0x10001 [_pem_decode $enccert $encoding] $opts(disposition)] -} - -proc twapi::cert_store_export_pem {hstore} { - set pem {} - cert_store_iterate $hstore c {append pem [cert_export $c]\n} - return $pem -} - -proc twapi::cert_store_export_pfx {hstore password args} { - parseargs args { - {exportprivatekeys.bool 0 0x4} - {failonmissingkey.bool 0 0x1} - {failonunexportablekey.bool 0 0x2} - } -maxleftover 0 -setvars - - if {[string length $password] == 0} { - set password [conceal ""] - } - - # NOTE: the -fail* flags only take effect iff the certificate in the store - # claims to have a private key but does not actually have one. It will - # not fail if the cert does not actually claim to have a private key - - set flags [tcl::mathop::| $exportprivatekeys $failonunexportablekey $failonmissingkey] - - return [PFXExportCertStoreEx $hstore $password {} $flags] -} -interp alias {} twapi::cert_store_export_pkcs12 {} twapi::cert_store_export_pfx - -proc twapi::cert_store_commit {hstore args} { - array set opts [parseargs args { - {force.bool 0} - } -maxleftover 0] - - return [Twapi_CertStoreCommit $hstore $opts(force)] -} - -proc twapi::cert_store_serialize {hstore} { - return [Twapi_CertStoreSerialize $hstore 1] -} - -proc twapi::cert_store_export_pkcs7 {hstore args} { - parseargs args { - {encoding.arg pem {der pem}} - } -setvars -maxleftover 0 - - return [_as_pem_or_der [Twapi_CertStoreSerialize $hstore 2] "PKCS7" $encoding] -} - -################################################################ -# Certificates - -interp alias {} twapi::cert_subject_name {} twapi::_cert_get_name subject -interp alias {} twapi::cert_issuer_name {} twapi::_cert_get_name issuer -proc twapi::_cert_get_name {field hcert args} { - - switch $field { - subject { set field 0 } - issuer { set field 1 } - default { badargs! "Invalid name type '$field': must be \"subject\" or \"issuer\"." - } - } - array set opts [parseargs args { - {name.arg oid_common_name} - {separator.arg comma {comma semicolon newline}} - {reverse.bool 0 0x02000000} - {noquote.bool 0 0x10000000} - {noplus.bool 0 0x20000000} - {format.arg x500 {x500 oid simple}} - } -maxleftover 0] - - set arg "" - switch $opts(name) { - email { set what 1 } - simpledisplay { set what 4 } - friendlydisplay {set what 5 } - dns { set what 6 } - url { set what 7 } - upn { set what 8 } - rdn { - set what 2 - switch $opts(format) { - simple {set arg 1} - oid {set arg 2} - x500 - - default {set arg 3} - } - set arg [expr {$arg | $opts(reverse) | $opts(noquote) | $opts(noplus)}] - switch $opts(separator) { - semicolon { set arg [expr {$arg | 0x40000000}] } - newline { set arg [expr {$arg | 0x08000000}] } - } - } - default { - set what 3; # Assume OID - set arg [oid $opts(name)] - } - } - - return [CertGetNameString $hcert $what $field $arg] - -} - -proc twapi::cert_blob_to_name {blob args} { - array set opts [parseargs args { - {format.arg x500 {x500 oid simple}} - {separator.arg comma {comma semi newline}} - {reverse.bool 0 0x02000000} - {noquote.bool 0 0x10000000} - {noplus.bool 0 0x20000000} - } -maxleftover 0] - - switch $opts(format) { - x500 {set arg 3} - simple {set arg 1} - oid {set arg 2} - } - - set arg [expr {$arg | $opts(reverse) | $opts(noquote) | $opts(noplus)}] - switch $opts(separator) { - semi { set arg [expr {$arg | 0x40000000}] } - newline { set arg [expr {$arg | 0x08000000}] } - } - - return [CertNameToStr $blob $arg] -} - -proc twapi::cert_name_to_blob {name args} { - array set opts [parseargs args { - {format.arg x500 {x500 oid simple}} - {separator.arg any {any comma semicolon newline}} - {reverse.bool 0 0x02000000} - {noquote.bool 0 0x10000000} - {noplus.bool 0 0x20000000} - } -maxleftover 0] - - switch $opts(format) { - x500 {set arg 3} - simple {set arg 1} - oid {set arg 2} - } - - set arg [expr {$arg | $opts(reverse) | $opts(noquote) | $opts(noplus)}] - switch $opts(separator) { - comma { set arg [expr {$arg | 0x04000000}] } - semicolon { set arg [expr {$arg | 0x40000000}] } - newline { set arg [expr {$arg | 0x08000000}] } - } - - return [CertStrToName $name $arg] -} - -proc twapi::cert_enum_properties {hcert args} { - parseargs args { - names - } -setvars -maxleftover 0 - - set id 0 - set ids {} - while {[set id [CertEnumCertificateContextProperties $hcert $id]]} { - if {$names} { - lappend ids [_cert_prop_name $id] - } else { - lappend ids $id - } - } - return $ids -} - -proc twapi::cert_property {hcert prop} { - # TBD - need to cook some properties - enhkey_usage - - if {[string is integer -strict $prop]} { - return [CertGetCertificateContextProperty $hcert $prop] - } else { - return [CertGetCertificateContextProperty $hcert [_cert_prop_id $prop] 1] - } -} - -proc twapi::cert_property_set {hcert prop propval} { - switch $prop { - pvk_file - - friendly_name - - description { - set val [encoding convertto unicode "${propval}\0"] - } - enhkey_usage { - set val [::twapi::CryptEncodeObjectEx 2.5.29.37 [_get_enhkey_usage_oids $propval]] - } - default { - badargs! "Invalid or unsupported property name \"$prop\". Must be one of [join $unicode_props {, }]." - } - } - - CertSetCertificateContextProperty $hcert [_cert_prop_id $prop] 0 $val -} - -proc twapi::cert_property_delete {hcert prop} { - CertSetCertificateContextProperty $hcert [_cert_prop_id $prop] 0 -} - -# TBD - Also add cert_set_key_prov_from_crypt_context -proc twapi::cert_set_key_prov {hcert keycontainer keyspec args} { - parseargs args { - csp.arg - {csptype.arg prov_rsa_full} - {keysettype.arg user {user machine}} - {silent.bool 0 0x40} - } -maxleftover 0 -nulldefault -setvars - - set flags $silent - if {$keysettype eq "machine"} { - incr flags 0x20; # CRYPT_KEYSET_MACHINE - } - - # 2 -> CERT_KEY_PROV_INFO_PROP_ID - # TBD - the provider param is hardcoded as {}. Should that be an option ? - CertSetCertificateContextProperty $hcert 2 0 \ - [list $keycontainer $csp [_csp_type_name_to_id $csptype] $flags {} [_crypt_keyspec $keyspec]] - return -} - -proc twapi::cert_export {hcert args} { - parseargs args { - {encoding.arg pem {der pem}} - } -maxleftover 0 -setvars - - return [_as_pem_or_der [lindex [Twapi_CertGetEncoded $hcert] 1] CERTIFICATE $encoding] -} - -proc twapi::cert_import {enccert args} { - parseargs args { - {encoding.arg {} {der pem {}}} - } -maxleftover 0 -setvars - return [CertCreateCertificateContext 0x10001 [_pem_decode $enccert $encoding]] -} - -proc twapi::cert_enhkey_usage {hcert {loc both}} { - return [_cert_decode_enhkey [CertGetEnhancedKeyUsage $hcert [dict! {property 4 extension 2 both 0} $loc 1]]] -} - -proc twapi::cert_key_usage {hcert} { - # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING - return [_cert_decode_keyusage [Twapi_CertGetIntendedKeyUsage 0x10001 $hcert]] -} - -proc twapi::cert_thumbprint {hcert} { - binary scan [cert_property $hcert sha1_hash] H* hash - return $hash -} - -proc twapi::cert_info {hcert} { - # TBD - add option to cook extensions using _cert_decode_extension - # instead of returning the raw form - set info [twine { - -version -serialnumber -signaturealgorithm -issuer - -start -end -subject -publickey -issuerid -subjectid -extensions} \ - [Twapi_CertGetInfo $hcert]] - dict set info -start \ - [clock format \ - [large_system_time_to_secs_since_1970 [dict get $info -start]] \ - -timezone :UTC \ - -format "%Y-%m-%d %H:%M:%S"] - dict set info -end \ - [clock format \ - [large_system_time_to_secs_since_1970 [dict get $info -end]] \ - -timezone :UTC \ - -format "%Y-%m-%d %H:%M:%S"] - - return $info -} - -proc twapi::cert_extension {hcert oid} { - set ext [CertFindExtension $hcert [oid $oid]] - if {[llength $ext] == 0} { - return $ext - } - lassign $ext oid critical val - return [list $critical [_cert_decode_extension $oid $val]] -} - -proc twapi::cert_create_self_signed {subject keycontainer keyspec args} { - set args [_cert_create_parse_options $args opts] - - array set opts [parseargs args { - {keysettype.arg user {machine user}} - {silent.bool 0 0x40} - {csp.arg {}} - {csptype.arg {prov_rsa_full}} - {signaturealgorithm.arg {}} - } -maxleftover 0 -ignoreunknown] - - set name_blob [cert_name_to_blob $subject] - - set kiflags $opts(silent) - if {$opts(keysettype) eq "machine"} { - incr kiflags 0x20; # CRYPT_MACHINE_KEYSET - } - set keyinfo [list \ - $keycontainer \ - $opts(csp) \ - [_csp_type_name_to_id $opts(csptype)] \ - $kiflags \ - {} \ - [_crypt_keyspec $keyspec]] - - set flags 0; # Always 0 for now - return [CertCreateSelfSignCertificate NULL $name_blob $flags $keyinfo \ - [_make_algorithm_identifier $opts(signaturealgorithm)] \ - $opts(start) $opts(end) $opts(extensions)] -} - -proc twapi::cert_create_self_signed_from_crypt_context {subject hprov args} { - set args [_cert_create_parse_options $args opts] - - array set opts [parseargs args { - {signaturealgorithm.arg {}} - } -maxleftover 0] - - set name_blob [cert_name_to_blob $subject] - - set flags 0; # Always 0 for now - return [CertCreateSelfSignCertificate $hprov $name_blob $flags {} \ - [_make_algorithm_identifier $opts(signaturealgorithm)] \ - $opts(start) $opts(end) $opts(extensions)] -} - -proc twapi::cert_create {subject pubkey cissuer args} { - set args [_cert_create_parse_options $args opts] - - parseargs args { - {encoding.arg pem {der pem}} - } -maxleftover 0 -setvars - - # TBD - check that issuer is a CA - but then what about self-signed? - - set issuer_info [cert_info $cissuer] - set issuer_blob [cert_name_to_blob [dict get $issuer_info -subject] -format x500] - set sigalgo [dict get $issuer_info -signaturealgorithm] - - # If issuer cert has altnames, use they as issuer altnames for new cert - set issuer_altnames [lindex [cert_extension $cissuer 2.5.29.17] 1] - if {[llength $issuer_altnames]} { - lappend opts(extensions) [_make_altnames_ext $issuer_altnames 0 1] - } - - # The subject key id in issuer's cert will become the - # authority key id in the new cert - # TBD - if fail, get the CERT_KEY_IDENTIFIER_PROP_ID - # 2.5.29.14 -> oid_subject_key_identifier - set issuer_subject_key_id [cert_extension $cissuer 2.5.29.14] - if {[string length [lindex $issuer_subject_key_id 1]] } { - # 2.5.29.35 -> oid_authority_key_identifier - lappend opts(extensions) [list 2.5.29.35 0 [list [lindex $issuer_subject_key_id 1] {} {}]] - } - - # Generate a subject key identifier for this cert based on a hash - # of the public key - set subject_key_id [Twapi_HashPublicKeyInfo $pubkey] - lappend opts(extensions) [list 2.5.29.14 0 $subject_key_id] - - set start [timelist_to_large_system_time $opts(start)] - set end [timelist_to_large_system_time $opts(end)] - - # 2 -> CERT_V3 - # issuer_id and subject_id for the certificate are left empty - # as recommended by gutman's X.509 paper - set cert_info [list 2 $opts(serialnumber) $sigalgo $issuer_blob \ - $start $end \ - [cert_name_to_blob $subject] \ - $pubkey {} {} \ - $opts(extensions)] - - # We need to get the crypt provider for the issuer cert since - # that is what will sign the new cert - lassign [cert_property $cissuer key_prov_info] issuer_container issuer_provname issuer_provtype issuer_flags dontcare issuer_keyspec - set hissuerprov [crypt_acquire $issuer_container -csp $issuer_provname -csptype $issuer_provtype -keysettype [expr {$issuer_flags & 0x20 ? "machine" : "user"}]] - trap { - # 0x10001 -> X509_ASN_ENCODING, 2 -> X509_CERT_TO_BE_SIGNED - return [_as_pem_or_der [CryptSignAndEncodeCertificate $hissuerprov \ - $issuer_keyspec \ - 0x10001 2 $cert_info $sigalgo] \ - CERTIFICATE $encoding] - } finally { - # TBD - test to make sure ok to close this if caller had - # it open - crypt_free $hissuerprov - } -} - -# TBD - test -proc twapi::cert_chain_build {hcert args} { - # -timestamp not documented because not clear exactly how it behaves - # -disablepass1*, -returnlower* not documented because not clear how - # useful. - # TBD - what about CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT - parseargs args { - {cacheendcert.bool 0 0x1} - {disableauthrootautoupdate.bool 0 0x100} - {disablepass1qualityfiltering.bool 0 0x40} - {engine.arg user {user machine}} - {hstore.arg NULL} - {returnlowerqualitycontexts.bool 0 0x80} - {revocationcheck.arg all {none all leaf excluderoot}} - {revocationcheckcacheonly.bool 0 0x80000000} - {timestamp.arg ""} - {urlretrievalcacheonly.bool 0 0x4} - usageall.arg - usageany.arg - } -setvars -maxleftover 0 - - set flags [dict! {none 0 all 0x20000000 leaf 0x10000000 excluderoot 0x40000000} $revocationcheck] - set flags [tcl::mathop::| $flags $cacheendcert $revocationcheckcacheonly $urlretrievalcacheonly $disablepass1qualityfiltering $returnlowerqualitycontexts $disableauthrootautoupdate] - - set usage_op 1; # USAGE_MATCH_TYPE_OR - if {[info exists usageall]} { - if {[info exists usageany]} { - error "Only one of -usageall and -usageany may be specified" - } - set usage_op 0; # USAGE_MATCH_TYPE_AND - set usage [_get_enhkey_usage_oids $usageall] - } elseif {[info exists usageany]} { - set usage [_get_enhkey_usage_oids $usageany] - } else { - set usage {} - } - - return [CertGetCertificateChain \ - [dict* {user NULL machine {1 HCERTCHAINENGINE}} $engine] \ - $hcert $timestamp $hstore \ - [list [list $usage_op $usage]] $flags] -} - -proc twapi::cert_ancestors {hcert args} { - # Note - does not care if certs are valid or not - set certs {} - set hchain [cert_chain_build $hcert {*}$args] - trap { - set simple_chain [twapi::Twapi_CertChainSimpleChain $hchain 0] - } finally { - cert_chain_release $hchain - } - foreach elem [dict get $simple_chain chain] { - lappend certs [dict get $elem hcert] - } - return $certs -} - -proc twapi::cert_chain_simple_chain {hchain index} { - set simple_chain [twapi::Twapi_CertChainSimpleChain $hchain $index] - set errors [_map_trust_error [dict get $simple_chain trust_errors]] - dict set simple_chain trust_errors $errors - if {[llength $errors]} { - dict set simple_chain status fail - } else { - dict set simple_chain status ok - } - dict set simple_chain trust_info [_map_trust_info [dict get $simple_chain trust_info]] - set chain_elements {} - foreach elem [dict get $simple_chain chain] { - set errors [_map_trust_error [dict get $elem trust_errors]] - dict set elem trust_errors $errors - if {[llength $errors]} { - dict set elem status fail - } else { - dict set elem status ok - } - dict set elem trust_info [_map_trust_info [dict get $elem trust_info]] - if {[dict exists $elem revocation]} { - set revocation [dict get $elem revocation] - if {$revocation == 0} { - dict unset elem revocation - } else { - dict set elem revocation [_map_cert_verify_error $revocation] - } - } - if {[dict exists $elem application_usage]} { - dict set elem application_usage [_cert_decode_enhkey [dict get $elem application_usage]] - } - lappend chain_elements $elem - } - dict set simple_chain chain $chain_elements - return $simple_chain -} - -# TBD - test -proc twapi::cert_chain_trust_info {hchain} { - return [_map_trust_info [Twapi_CertChainInfo $hchain]] -} - -proc twapi::_map_trust_info {info} { - return [_make_symbolic_bitmask $info { - hasexactmatchissuer 0x00000001 - haskeymatchissuer 0x00000002 - hasnamematchissuer 0x00000004 - isselfsigned 0x00000008 - haspreferredissuer 0x00000100 - hasissuancechainpolicy 0x00000200 - hasvalidnameconstraints 0x00000400 - ispeertrusted 0x00000800 - hascrlvalidityextended 0x00001000 - isfromexclusivetruststore 0x00002000 - iscomplexchain 0x00010000 - }] -} - -# TBD - test -proc twapi::cert_chain_trust_errors {hchain} { - return [_map_trust_error [Twapi_CertChainError $hchain]] -} - -proc twapi::_map_trust_error {errbits} { - return [_make_symbolic_bitmask $errbits { - time 1 - revoked 4 - signature 8 - wrongusage 0x10 - untrustedroot 0x20 - revocationunknown 0x40 - trustcycle 0x80 - extension 0x100 - policy 0x200 - basiconstraints 0x400 - nameconstraints 0x800 - unsupportednameconstraint 0x1000 - undefinednameconstraint 0x2000 - unpermittednameconstraint 0x4000 - excludednameconstraint 0x8000 - revocationoffline 0x01000000 - noissuancechainpolicy 0x02000000 - distrust 0x04000000 - criticalextension 0x08000000 - weaksignature 0x00100000 - partialchain 0x00010000 - ctltime 0x00020000 - ctlsignature 0x00040000 - ctlusage 0x00080000 - }] -} - -proc twapi::cert_verify {hcert policy args} { - # TBD - should we explicitly look for nulls in the subject name? - # The Chrome source at - # https://src.chromium.org/svn/branches/455/src/net/base/x509_certificate_win.cc - # does this though it also uses the same calls as below. See - # CertSubjectCommonNameHasNull in that code. - set policy_id [dict! { - authenticode 2 authenticodets 3 base 1 basicconstraints 5 - extendedvalidation 8 microsoftroot 7 ntauth 6 - ssl 4 tls 4 - } $policy] - - # Construct policy specific options - set optdefs { - {ignoreerrors.arg {}} - policyparams.arg - {trustedroots.arg} - } - switch -exact -- $policy_id { - 4 { - # SSL/TLS - lappend optdefs server.arg - } - 5 { - # basicconstraints - lappend optdefs isa.arg - } - 6 { - # ntauth also accepts -isa as it includes basic constraints checks - lappend optdefs isa.arg - } - 7 { - # microsoftroot - lappend optdefs enabletestroot.bool - } - } - - array set opts [parseargs args $optdefs -ignoreunknown -setvars] - - if {![dict exists $args -usageall] && ![dict exists $args -usageany]} { - switch -exact -- $policy { - authenticodets - - authenticode { - dict lappend args -usageany code_signing - } - ssl - - tls { - if {[info exists server]} { - dict lappend args -usageany server_auth - } else { - dict lappend args -usageany client_auth - } - } - } - } - - set verify_flags 0 - if {[info exists isa]} { - switch -exact -- $isa { - ca { set verify_flags [expr {$verify_flags | 0x80000000}] } - endentity { set verify_flags [expr {$verify_flags | 0x40000000}] } - default { - error "Invalid value \"$isa\" specified for option -isa." - } - } - } - if {[info exists enabletestroot]} { - set verify_flags [expr {$verify_flags | 0x00010000}] - } - - if {$policy eq "basicconstraints"} { - # TBD - peertrust 0x1000, see below - set ignore_options {} - } else { - # Any other policy - # TBD - the meaning of these is not clear. Are they ignore - # error flags or options? - # peertrust 0x1000 - # trusttestroot 0x4000 - # allowtestroot 0x8000 - set ignore_options { - time 0x07 - basicconstraints 0x08 - unknownca 0x10 - usage 0x20 - name 0x40 - policy 0x80 - revocation 0xf00 - criticalextensions 0x2000 - } - } - - foreach ignore $ignoreerrors { - if {![dict exists $ignore_options $ignore]} { - error "Value $ignore for option -ignoreerrors cannot be used with policy $policy." - } - set verify_flags [expr {$verify_flags | [dict get $ignore_options $ignore]}] - } - - if {![info exists policyparams]} { - switch -exact -- $policy_id { - 4 { - # ssl/tls - if {[info exists server]} { - set policyparams [cert_policy_params_tls -ignoreerrors $ignoreerrors -server $server] - } else { - set policyparams [cert_policy_params_tls -ignoreerrors $ignoreerrors] - } - } - default { - set policyparams {} - } - } - } - - if {[info exists ignoreerrors] && "revocation" in $ignoreerrors} { - lappend args -revocationcheck none - } - set chainh [cert_chain_build $hcert {*}$args] - - trap { - # Actually verification is a bit tricky because the caller might - # have asked for certain errors to be ignored. - # Note that CertVerifyChainPolicy below does NOT check for revocation - # of certificates in the certificate chain as per Microsoft docs. - # We therefore check for revocation errors here and abort if present. - set chain_errors [cert_chain_trust_errors $chainh] - if {[llength $chain_errors]} { - if {"revoked" in $chain_errors} { - return revoked - } - if {"revocationoffline" in $chain_errors} { - return revocationoffline - } - if {"revocationunknown" in $chain_errors} { - return revocationunknown - } - - if {0} { - # For other kind of errors, caller might have indicated - # some types are to be ignored. In that case we will proceed - # to use CertVerifyTrustPolicy since that will allow - # control of which errors are to be ignored. As a - # special case, if caller has specified additional trusted - # roots, we will proceed to call CertVerifyTrustPolicy - # even when caller is not ignoring errors but only if - # there are no errors indicated. - if {[llength $chain_errors] > 1 || - [lindex $chain_errors 0] ne "untrustedroot" || - ![info exists trustedroots]} { - return $chain_errors - } - } - } - - set status [Twapi_CertVerifyChainPolicy $policy_id $chainh [list $verify_flags $policyparams]] - - # If caller had provided additional trusted roots that are not - # in the Windows trusted store, and the error is that the root is - # untrusted, see if the root cert is one of the passed trusted ones - # We will only deal when there is a single possible chain else - # the recheck becomes very complicated as we are not sure if - # the recheck will employ the same chain or not. - if {$status == 0x800B0109 && - [info exists trustedroots] && [llength $trustedroots] && - [cert_chain_simple_chain_count $chainh] == 1} { - set simple_chain [cert_chain_simple_chain $chainh 0] - # Double check no errors listed for this chain - set trust_errors [dict get $simple_chain trust_errors] - if {[llength $trust_errors] == 1 && - [lindex $trust_errors 0] eq "untrustedroot"} { - set certs_in_chain [dict get $simple_chain chain] - set root_cert [dict get [lindex $certs_in_chain end] hcert] - set thumbprint [cert_thumbprint $root_cert] - # Match against each trusted root - set trusted 0 - foreach trusted_cert $trustedroots { - if {$thumbprint eq [cert_thumbprint $trusted_cert]} { - set trusted 1 - break - } - } - if {$trusted} { - # Yes, the root is trusted. It is not enough to - # say validation is ok because even if root - # is trusted, other errors might show up - # once untrusted roots are ignored. So we have - # to call the verification again. - # 0x10 -> CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG - set verify_flags [expr {$verify_flags | 0x10}] - if {0} { - TBD - need to redo the policy params? - # 0x100 -> SECURITY_FLAG_IGNORE_UNKNOWN_CA - set checks [expr {$checks | 0x100}] - } - # Retry the call ignoring root errors - set status [Twapi_CertVerifyChainPolicy $policy_id $chainh [list $verify_flags $policyparams]] - } - } - } - - return [_map_cert_verify_error $status] - } finally { - if {[info exists simple_chain]} { - foreach cert [dict get $simple_chain chain] { - cert_release [dict get $cert hcert] - } - } - cert_chain_release $chainh - } - - return $status -} - -proc twapi::_map_cert_verify_error {err} { - if {![string is integer -strict $err]} { - return $err - } - return [dict* { - 0x00000000 ok - 0x80096004 signature - 0x80092010 revoked - 0x800b0109 untrustedroot - 0x800b010d untrustedtestroot - 0x800b010a partialchain - 0x800b0110 wrongusage - 0x800b0101 time - 0x800b0114 name - 0x800b0113 policy - 0x80096019 basicconstraints - 0x800b0105 criticalextension - 0x800b0102 validityperiodnesting - 0x80092011 norevocationdll - 0x80092012 norevocationcheck - 0x80092013 revocationoffline - 0x800b010f cnmatch - 0x800b0106 purpose - 0x800b010e revocationunknown - 0x800b0103 carole - } [format 0x%8.8x $err]] -} - -# TBD - document -proc twapi::cert_policy_params_tls {args} { - - parseargs args { - ignoreerrors.arg - server.arg - } -maxleftover 0 -setvars -ignoreunknown - - if {[info exists server]} { - set role 2; # AUTHTYPE_SERVER - } else { - set role 1; # AUTHTYPE_CLIENT - set server "" - } - - set ignore_options { - time 0x2000 - unknownca 0x100 - usage 0x200 - name 0x1000 - revocation 0x80 - } - set checks 0 - foreach ignore $ignoreerrors { - # Note we use dict*, not dict! so we can skip any ignore tokens - # that we don't know - set checks [expr {$checks | [dict* $ignore_options $ignore 0]}] - } - return [list $role $checks $server] -} - -proc twapi::cert_tls_verify {hcert args} { - return [cert_verify $hcert tls {*}$args] -} - -# TBD - provide a -peersubject option -proc twapi::cert_fetch {addr {port 443}} { - set so [tls_socket $addr $port] - trap { - set sspi_ctx [chan configure $so -context] - return [sspi_remote_cert $sspi_ctx] - } finally { - close $so - } -} - -proc twapi::cert_locate_private_key {hcert args} { - parseargs args { - {keysettype.arg any {any user machine}} - {silent 0 0x40} - } -maxleftover 0 -setvars - - return [CryptFindCertificateKeyProvInfo $hcert \ - [expr {$silent | [dict get {any 0 user 1 machine 2} $keysettype]}]] -} - -proc twapi::cert_request_parse {req args} { - parseargs args { - {encoding.arg {} {der pem {}}} - } -setvars -maxleftover 0 - - # 3 -> CRYPT_STRING_BASE64REQUESTHEADER - # 4 -> X509_CERT_REQUEST_TO_BE_SIGNED - lassign [::twapi::CryptDecodeObjectEx 4 [_pem_decode $req $encoding 3]] ver subject pubkey attrs - lappend reqdict version $ver pubkey $pubkey attributes $attrs - lappend reqdict subject [cert_blob_to_name $subject] - foreach attr $attrs { - lassign $attr oid values - if {$oid eq "1.2.840.113549.1.9.14"} { - # ...1.9.14 -> oid_rsa_certextensions - set extensions {} - foreach ext [lindex $values 0] { - lassign $ext oid critical value - set value [_cert_decode_extension $oid $value] - lappend extensions $oid [list $value $critical] - # Also add "option keyed" values - switch -exact -- $oid { - 2.5.29.15 { - lappend extensions -keyusage [list $value $critical] - } - 2.5.29.17 { - lappend extensions -altnames [list $value $critical] - } - 2.5.29.19 { - lappend extensions -basicconstraints [list $value $critical] - } - 2.5.29.37 { - lappend extensions -enhkeyusage [list $value $critical] - } - } - } - lappend reqdict extensions $extensions - } - } - - return $reqdict -} - - -proc twapi::cert_request_create {subject hprov keyspec args} { - set args [_cert_create_parse_options $args opts] - # TBD - barf if any elements other than extensions is set - # TBD - document signaturealgorithmid - parseargs args { - {signaturealgorithmid.arg oid_rsa_sha1rsa} - {encoding.arg pem {der pem}} - } -setvars -maxleftover 0 - - set sigoid [oid $signaturealgorithmid] - if {$sigoid ni [list [oid oid_rsa_sha1rsa] [oid oid_rsa_md5rsa] [oid oid_x957_sha1dsa]]} { - badargs! "Invalid signature algorithm '$sigalg'" - } - set keyspec [twapi::_crypt_keyspec $keyspec] - # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING - # Pass oid_rsa_rsa as that seems to be what OPENSSL understands in - # a CSR - set pubkeyinfo [crypt_public_key $hprov $keyspec oid_rsa_rsa] - set attrs [list 0 [cert_name_to_blob $subject] $pubkeyinfo] - if {[llength $opts(extensions)]} { - lappend attrs [list [list [oid oid_rsa_certextensions] [list $opts(extensions)]]] - } else { - lappend attrs {} - } - return [_as_pem_or_der [CryptSignAndEncodeCertificate $hprov $keyspec 0x10001 4 $attrs $sigoid] "NEW CERTIFICATE REQUEST" $encoding] -} - - -################################################################ -# Cryptographic context commands - -proc twapi::crypt_acquire {args} { - # Backward compatibility - keycontainer can be specified as first arg - if {[llength $args] & 1} { - set args [lassign $args keycontainer] - } else { - set keycontainer "" - } - - parseargs args { - {csp.arg {}} - {csptype.arg prov_rsa_full} - keycontainer.arg - {keysettype.arg user {user machine}} - {create.bool 0 0x8} - {silent.bool 0 0x40} - verifycontext.bool - } -maxleftover 0 -setvars - - # The defaults for verifycontext are a little confusing. For a named - # key container, at least the MS CSP's require -verifycontext to be 0. - # For the frequent case where private keys are not required, MS recommends - # using the null key container with -verifycontext 1. So accordingly, - # if the keycontainer is empty (or unspecified), then it - # defaults to 1, else defaults to 0. - if {![info exists verifycontext]} { - if {$keycontainer eq ""} { - set verifycontext 1 - } else { - set verifycontext 0 - } - } - - if {$verifycontext} { - set verifycontext 0xf0000000 - } - - set flags [expr {$silent | $verifycontext}] - if {$keysettype eq "machine"} { - incr flags 0x20; # CRYPT_KEYSET_MACHINE - } - - trap { - return [CryptAcquireContext $keycontainer $csp [_csp_type_name_to_id $csptype] $flags] - } onerror {TWAPI_WIN32 0x80090016} { - # NTE_BAD_KEYSET - does not exist. Try to create it. - if {$create} { - set flags [expr {$flags | $create}] - return [CryptAcquireContext $keycontainer $csp [_csp_type_name_to_id $csptype] $flags] - } else { - rethrow - } - } -} - -proc twapi::crypt_free {hcrypt} { - twapi::CryptReleaseContext $hcrypt -} - -proc twapi::crypt_key_container_delete {keycontainer args} { - parseargs args { - csp.arg - {csptype.arg prov_rsa_full} - {keysettype.arg user {machine user}} - force - } -maxleftover 0 -nulldefault -setvars - - if {$keycontainer eq "" && ! $force} { - error "Default container cannot be deleted unless the -force option is specified" - } - - set flags 0x10; # CRYPT_DELETEKEYSET - if {$keysettype eq "machine"} { - incr flags 0x20; # CRYPT_MACHINE_KEYSET - } - - return [CryptAcquireContext $keycontainer $csp [_csp_type_name_to_id $csptype] $flags] -} - -proc twapi::crypt_generate_key {hprov algid args} { - - array set opts [parseargs args { - {archivable.bool 0 0x4000} - {salt.bool 0 4} - {exportable.bool 0 1} - {pregen.bool 0x40} - {userprotected.bool 0 2} - {nosalt40.bool 0 0x10} - {size.int 0} - } -maxleftover 0] - - set algid [capi_algid $algid] - - if {$opts(size) < 0 || $opts(size) > 65535} { - badargs! "Bad key size value '$size': must be positive integer less than 65536" - } - - return [CryptGenKey $hprov $algid [expr {($opts(size) << 16) | $opts(archivable) | $opts(salt) | $opts(exportable) | $opts(pregen) | $opts(userprotected) | $opts(nosalt40)}]] -} - -proc twapi::crypt_keypair {hprov keyspec} { - return [CryptGetUserKey $hprov [dict! {keyexchange 1 signature 2} $keyspec]] -} - -proc twapi::crypt_public_key_import {hprov key args} { - parseargs args { - {algid.arg 0} - {encoding.arg {} {native pem der {}}} - } -setvars - - if {$encoding eq "native"} { - set pub $key - } elseif {$encoding eq "der"} { - set pub [CryptDecodeObjectEx 8 $key] - } elseif {$encoding eq "pem" || - ($encoding eq "" && [string match -nocase "-----BEGIN*" $key])} { - set pub [CryptDecodeObjectEx 8 [CryptStringToBinary $key 0]] - } else { - # encoding is unspecified and is either der or native - if {[catch {set pub [CryptDecodeObjectEx 8 $key]}]} { - # Not DER, assume native - set pub $key - } - } - - return [CryptImportPublicKeyInfoEx $hprov 0x10001 $pub [capi_algid $algid]] -} - -proc twapi::crypt_public_key_export {hprov keyspec args} { - parseargs args { - algoid.arg - {encoding.arg pem {pem der native}} - } -setvars -nulldefault - - if {$algoid ne ""} { - set algoid [oid $algoid] - } - set pubkey [CryptExportPublicKeyInfoEx $hprov \ - [_crypt_keyspec $keyspec] \ - 0x10001 \ - $algoid \ - 0] - if {$encoding eq "native"} { - return $pubkey - } - # Generate SubjectPublicKeyInfo - set der [CryptEncodeObjectEx 8 $pubkey] - if {$encoding eq "der"} { - return $der - } - # 0x80000001 -> No CR (only LF) and headers - return "-----BEGIN PUBLIC KEY-----\n[CryptBinaryToString $der 0x80000001]-----END PUBLIC KEY-----\n" -} - -# For back compat - undocumented -proc twapi::crypt_public_key {hcrypt algid oid} { - return [crypt_public_key_export $hcrypt $algid -encoding native -algoid $oid] -} - -proc twapi::crypt_get_security_descriptor {hprov} { - return [CryptGetProvParam $hprov 8 7] -} - -proc twapi::crypt_set_security_descriptor {hprov secd} { - CryptSetProvParam $hprov 8 $secd -} - -proc twapi::crypt_key_container_name {hprov} { - return [CryptGetProvParam $hprov 6 0] -} - -proc twapi::crypt_key_container_unique_name {hprov} { - return [CryptGetProvParam $hprov 36 0] -} - -proc twapi::crypt_csp {hprov} { - return [CryptGetProvParam $hprov 4 0] -} - -proc twapi::csps {} { - set i 0 - set result {} - while {[llength [set csp [::twapi::CryptEnumProviders $i]]]} { - lappend result [lreplace $csp 0 0 [_csp_type_id_to_name [lindex $csp 0]]] - incr i - } - return $result -} -interp alias {} twapi::crypt_csps {} twapi::csps - -proc twapi::crypt_csp_type {hprov} { - return [_csp_type_id_to_name [CryptGetProvParam $hprov 16 0]] -} - -proc twapi::csp_types {} { - set i 0 - set result {} - while {[llength [set csptype [::twapi::CryptEnumProviderTypes $i]]]} { - lappend result [lreplace $csptype 0 0 [_csp_type_id_to_name [lindex $csptype 0]]] - incr i - } - return $result -} -interp alias {} twapi::crypt_csptypes {} twapi::csp_types - -proc twapi::crypt_key_container_names {hcrypt} { - return [CryptGetProvParam $hcrypt 2 0] -} - -proc twapi::crypt_session_key_size {hcrypt} { - return [CryptGetProvParam $hcrypt 20 0] -} - -proc twapi::crypt_keyx_keysize_increment {hcrypt} { - return [CryptGetProvParam $hcrypt 35 0] -} - -proc twapi::crypt_sig_keysize_increment {hcrypt} { - return [CryptGetProvParam $hcrypt 34 0] -} - -# TBD - Doc and test -proc twapi::crypt_admin_pin {hcrypt} { - return [CryptGetProvParam $hcrypt 31 0] -} - -# TBD - Doc and test -proc twapi::crypt_keyx_pin {hcrypt} { - return [CryptGetProvParam $hcrypt 32 0] -} - -# TBD - Doc and test -proc twapi::crypt_sig_pin {hcrypt} { - return [CryptGetProvParam $hcrypt 33 0] -} - -proc twapi::crypt_csp_version {hcrypt} { - set ver [CryptGetProvParam $hcrypt 5 0] - return [format %d.%d [expr {($ver & 0xff00)>>8}] [expr {$ver & 0xff}]] -} - -proc twapi::crypt_keyset_type {hcrypt} { - return [expr {[CryptGetProvParam $hcrypt 27 0] & 0x20 ? "machine" : "user"}] -} - -proc twapi::crypt_key_specifiers {hcrypt} { - set keyspec [CryptGetProvParam $hcrypt 39 0] - set keyspecs {} - if {$keyspec & 1} { - lappend keyspecs keyexchange - } - if {$keyspec & 2} { - lappend keyspecs signature - } - return $keyspecs -} - -proc twapi::crypt_symmetric_key_size {hcrypt} { - return [CryptGetProvParam $hcrypt 19 0] -} - -proc twapi::capi_key_export {hkey blob_type args} { - parseargs args { - {wrapper.arg NULL} - {v3.bool 0 0x80} - {oeap.bool 0 0x40} - {destroy.bool 0 0x04} - } -setvars -maxleftover 0 - - return [CryptExportKey $hkey $wrapper [_capi_keyblob_type_id $blob_type] [expr {$v3|$oeap}]] -} -interp alias {} twapi::crypt_export_key {} twapi::capi_key_export - - -proc twapi::crypt_import_key {hcrypt keyblob args} { - parseargs args { - {wrapper.arg NULL} - {exportable.bool 1 0x01} - {oaep.bool 0 0x40} - {userprotected.bool 0 0x02} - {ipsechmac.bool 0 0x100} - } -setvars -maxleftover 0 - return [CryptImportKey $hcrypt $keyblob $wrapper \ - [expr {$exportable|$oaep|$userprotected|$ipsechmac}]] -} -interp alias {} twapi::capi_key_import {} twapi::crypt_import_key - -proc twapi::crypt_derive_key {hcrypt algid passphrase args} { - parseargs args { - {size.int 0} - {exportable.bool 1 0x01} - {prf.arg sha1} - {method.arg pbkdf2} - {iterations.int 100000} - {salt.arg ""} - } -maxleftover 0 -setvars - - if {$method eq "pbkdf2"} { - set algnum [capi_algid $algid] - if {$size == 0} { - # Need to figure out the default key size for the algorithm - # The loop below does not work for des/3des/3des_112 because - # it will get the actual key size whereas CryptImportKey - # wants key size with pad/parity bits. So hardcode these - if {$algnum == 0x6601} { - set size 64; # - } elseif {$algnum == 0x6603} { - set size 192; # 3des - } elseif {$algnum == 0x6609} { - set size 128; # 3des_112 - } else { - foreach alg [crypt_algorithms $hcrypt] { - if {[dict get $alg algid] == $algnum} { - set size [dict get $alg defkeylen] - break - } - } - } - if {$size == 0} { - error "Could not figure out default key size for algorithm $algid. Please use the -size option." - } - } - set pbkdf2 [PBKDF2 $passphrase $size [capi_algid $prf] $salt $iterations] - set keyblob [list 0 2 0 $algnum $pbkdf2] - return [crypt_import_key $hcrypt $keyblob -exportable $exportable] - } else { - if {$size < 0 || $size > 65535} { - # Key size of 0 is default. Else it must be within 1-65535 - badargs! "Option -size value \"$size\" is not between 0 and 65535." - } - set hhash [capi_hash_create $hcrypt [capi_algid $method]] - twapi::trap { - capi_hash_password $hhash $passphrase - return [CryptDeriveKey $hcrypt [capi_algid $algid] $hhash \ - [expr {($size << 16) | $exportable}]] - } finally { - capi_hash_free $hhash - } - } -} - -proc twapi::pbkdf2 {pass nbits alg_id salt niters} { - return [PBKDF2 $pass $nbits [capi_algid $alg_id] $salt $niters] -} - - -proc twapi::capi_encrypt_bytes {bytes hkey args} { - variable _capi_encrypt_partials - parseargs args { - {hhash.arg NULL} - {final.bool 1} - {pad.arg oaep {oaep pkcs1}} - } -setvars -maxleftover 0 - - if {[dict exists $_capi_encrypt_partials $hkey Data]} { - append plaintext \ - [dict get $_capi_encrypt_partials $hkey Data] \ - $bytes - } else { - set plaintext $bytes - } - - if {$final} { - dict unset _capi_encrypt_partials $hkey - return [CryptEncrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40} $pad] $plaintext] - } - - # If not the final segment, we have to split it up into the block size multiple. - if {[dict exists $_capi_encrypt_partials $hkey Blocklen]} { - set blocklen [dict get $_capi_encrypt_partials $hkey Blocklen] - } else { - set blocklen [expr {[capi_key_blocklen $hkey] / 8}]; # Bits -> bytes - } - - # len is largest multiple of block size less than data length - set len [expr {([string length $plaintext] / $blocklen) * $blocklen}] - set enc [CryptEncrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40} $pad] [string range $plaintext 0 $len-1]] - # Note following will not happen if CryptEncrypt throws an error. As desired - set remain [string range $plaintext $len end] - if {[string length $remain]} { - # Remember additional data - dict set _capi_encrypt_partials $hkey Data $remain - dict set _capi_encrypt_partials $hkey Blocklen $blocklen - } else { - dict unset _capi_encrypt_partials $hkey - } - - return $enc -} - -proc twapi::capi_encrypt_string {s hkey args} { - # Explicitly parse args, not just pass on because this command - # does not support -final for symmetry with capi_decrypt_string - parseargs args { - {hhash.arg NULL} - {pad.arg oaep {oaep pkcs1}} - } -setvars -maxleftover 0 - return [capi_encrypt_bytes [encoding convertto utf-8 $s] $hkey -hhash $hhash -pad $pad] -} - -proc twapi::capi_decrypt_bytes {bytes hkey args} { - variable _capi_decrypt_partials - parseargs args { - {pad.arg oaep {oaep pkcs1 nopadcheck}} - {final.bool 1} - {hhash.arg NULL} - } -setvars -maxleftover 0 - - if {[dict exists $_capi_decrypt_partials $hkey Data]} { - append enc \ - [dict get $_capi_decrypt_partials $hkey Data] \ - $bytes - } else { - set enc $bytes - } - - if {$final} { - dict unset _capi_decrypt_partials $hkey - return [CryptDecrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40 nopadcheck 0x20} $pad] $enc] - } - - # If not the final segment, we have to split it up into the block size multiple. - if {[dict exists $_capi_decrypt_partials $hkey Blocklen]} { - set blocklen [dict get $_capi_decrypt_partials $hkey Blocklen] - } else { - set blocklen [expr {[capi_key_blocklen $hkey] / 8}]; # Bits -> bytes - } - - # len is largest multiple of block size less than data length - set len [expr {([string length $enc] / $blocklen) * $blocklen}] - set plaintext [CryptDecrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40} $pad] [string range $enc 0 $len-1]] - # Note following will not happen if CryptDecrypt throws an error. As desired - set remain [string range $enc $len end] - if {[string length $remain]} { - # Remember additional data - dict set _capi_decrypt_partials $hkey Data $remain - dict set _capi_decrypt_partials $hkey Blocklen $blocklen - } else { - dict unset _capi_decrypt_partials $hkey - } - - return $plaintext -} - -proc twapi::capi_decrypt_string {s hkey args} { - # Explicitly parse args, not just pass on because this command - # does not support -final for symmetry with capi_decrypt_string - parseargs args { - {hhash.arg NULL} - {pad.arg oaep {oaep pkcs1}} - } -setvars -maxleftover 0 - return [encoding convertfrom utf-8 [capi_decrypt_bytes $s $hkey -hhash $hhash -pad $pad]] -} - -# Returns the most capable CSP -proc twapi::_crypt_acquire_default {} { - if {[catch {crypt_acquire -csptype prov_rsa_aes} hcrypt] && - [catch {crypt_acquire -csptype prov_rsa_full -csp {Microsoft Enhanced Cryptographic Provider v1.0}} hcrypt]} { - set hcrypt [crypt_acquire] - } - set cspname [crypt_csp $hcrypt] - set csptype [crypt_csp_type $hcrypt] - # Redefine ourselves for next call - proc [namespace current]::_crypt_acquire_default {} "crypt_acquire -csp {$cspname} -csptype $csptype" - return $hcrypt -} - -proc twapi::_block_cipher {algo direction bytes keybytes args} { - - # Note: padding mode is not documented since MS providers only support - # one mode anyway - parseargs args { - mode.arg - iv.arg - padding.arg - } -setvars -maxleftover 0 - - set hcrypt [_crypt_acquire_default] - try { - set hkey [crypt_import_key $hcrypt [capi_keyblob_concealed $algo $keybytes]] - if {[info exists mode]} { - capi_key_mode $hkey $mode - } - if {[info exists iv]} { - capi_key_iv $hkey $iv - } - if {$direction eq "encrypt"} { - if {[info exists padding]} { - capi_key_padding $hkey $padding - } - set ciphertext [capi_encrypt_bytes $bytes $hkey] - } else { - set ciphertext [capi_decrypt_bytes $bytes $hkey] - } - } finally { - if {[info exists hkey]} { - capi_key_free $hkey - } - crypt_free $hcrypt - } - return $ciphertext -} - -# apply to avoid global variable pollution -apply {{} { - foreach {algo blocklen} {des 8 3des 8 aes_128 16 aes_192 16 aes_256 16} { - namespace eval twapi::$algo {} - interp alias {} twapi::${algo}::encrypt {} twapi::_block_cipher $algo encrypt - interp alias {} twapi::${algo}::decrypt {} twapi::_block_cipher $algo decrypt - interp alias {} twapi::${algo}::iv {} twapi::random_bytes $blocklen - namespace eval twapi::$algo { - namespace export encrypt decrypt iv - namespace ensemble create - } - } -}} - -### -# PKCS7 commands - -proc twapi::pkcs7_encrypt {bytes recipients encalg args} { - parseargs args { - {encoding.arg pem {pem der}} - {innertype.arg 0} - } -setvars -maxleftover 0 - - # TBD - add support for the following - set flags 0 - set encauxinfo {} - - set params [list \ - 0x10001 \ - NULL \ - [_make_algorithm_identifier $encalg] \ - $encauxinfo \ - $flags \ - $innertype] - return [_as_pem_or_der [CryptEncryptMessage $params $recipients $bytes] PKCS7 $encoding] -} - -proc twapi::pkcs7_decrypt {bytes stores args} { - parseargs args { - {encoding.arg {} {der pem {}}} - {silent.bool 0 0x40} - {certvar.arg ""} - } -maxleftover 0 -setvars - - set params [list \ - 0x10001 \ - $stores \ - $silent] - if {$certvar ne ""} { - upvar 1 $certvar hcert - set certvar hcert - } - - return [CryptDecryptMessage $params [_pem_decode $bytes $encoding] $certvar] -} - -proc twapi::pkcs7_sign {bytes hcert hashalg args} { - # TBD - document crls? - parseargs args { - {detached.bool 0} - {encoding.arg pem {pem der}} - {includecerts.arg all {none leaf all}} - {silent.bool 0 0x40} - {usesignerkeyid.bool 0 0x4} - {crls.arg {}} - {innercontenttype.arg 0} - } -setvars -maxleftover 0 - - set flags [expr {$usesignerkeyid | $silent}] - - switch -exact -- $includecerts { - leaf { set certs [list [cert_duplicate $hcert]] } - none { set certs {} } - all { set certs [cert_ancestors $hcert] } - } - # TBD - add support for the following - set hashaux {} - set authattrs {} - set unauthattrs {} - set encalg "" - set hashencaux "" - # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING - set params [list \ - 0x10001 \ - $hcert \ - [_make_algorithm_identifier $hashalg] \ - $hashaux \ - $certs \ - $crls \ - $authattrs \ - $unauthattrs \ - $flags \ - $innercontenttype \ - $encalg \ - $hashencaux] - trap { - return [_as_pem_or_der [CryptSignMessage $params $detached [list $bytes]] PKCS7 $encoding] - } finally { - foreach c $certs { - cert_release $c - } - } -} - -proc twapi::pkcs7_verify {bytes args} { - parseargs args { - {encoding.arg {} {der pem {}}} - {contentvar.arg ""} - {certvar.arg ""} - } -maxleftover 0 -setvars -ignoreunknown - - if {$contentvar ne ""} { - upvar 1 $contentvar content - set contentvar content - } - set status [CryptVerifyMessageSignature [list 0x10001 NULL] 0 [_pem_decode $bytes $encoding] $contentvar hcert] - if {$status == 0} { - trap { - set status [cert_verify $hcert base {*}$args] - if {$status eq "ok"} { - if {$certvar ne ""} { - upvar 1 $certvar cert - set cert $hcert - unset hcert; # So we do not release it below - } - if {$contentvar ne ""} { - upvar 1 $contentvar con - set con content - } - } - } finally { - if {[info exists hcert]} { - cert_release $hcert - } - } - } else { - # Note these codes are different from those in _map_cert_verify_error - if {$status == 0x80090006} { - set status "signature" - } elseif {$status == 0x80090008} { - set status "invalidalgorithm" - } - } - - return $status -} - - -# For backwards compat - deprecated -interp alias {} twapi::crypt_key_free {} twapi::capi_key_free - -proc twapi::crypt_algorithms {hcrypt} { - set algs {} - foreach alg [CryptGetProvParam $hcrypt 22 0] { - lassign $alg algid defaultlen minlen maxlen protos name description - set protos [_make_symbolic_bitmask $protos { - ipsec 0x10 pct1 0x01 signing 0x20 ssl2 0x02 ssl3 0x04 tls1 0x08 - }] - lappend algs [list algid $algid defkeylen $defaultlen minkeylen $minlen maxkeylen $maxlen protocols $protos name $name description $description] - } - return $algs -} - -proc twapi::crypt_implementation_type {hcrypt} { - return [dict* {1 hardware 2 software 3 mixed 4 unknown 8 removable} [CryptGetProvParam $hcrypt 3 0]] -} - -proc twapi::capi_algid {s} { - if {[string is integer -strict $s]} { - return [expr {$s}]; # Return in decimal form - } - set algid [dict* { - 3des 0x00006603 - 3des_112 0x00006609 - aes 0x00006611 - aes_128 0x0000660e - aes_192 0x0000660f - aes_256 0x00006610 - agreedkey_any 0x0000aa03 - keyexchange 1 - signature 2 - cylink_mek 0x0000660c - des 0x00006601 - desx 0x00006604 - dh_ephem 0x0000aa02 - dh_sf 0x0000aa01 - dss_sign 0x00002200 - ecdh 0x0000aa05 - ecdsa 0x00002203 - ecmqv 0x0000a001 - hash_replace_owf 0x0000800b - hughes_md5 0x0000a003 - hmac 0x00008009 - kea_keyx 0x0000aa04 - mac 0x00008005 - md2 0x00008001 - md4 0x00008002 - md5 0x00008003 - no_sign 0x00002000 - pct1_master 0x00004c04 - rc2 0x00006602 - rc4 0x00006801 - rc5 0x0000660d - rsa_keyx 0x0000a400 - rsa_sign 0x00002400 - schannel_enc_key 0x00004c07 - schannel_mac_key 0x00004c03 - schannel_master_hash 0x00004c02 - sha 0x00008004 - sha1 0x00008004 - sha_256 0x0000800c - sha_384 0x0000800d - sha_512 0x0000800e - ssl2_master 0x00004c05 - ssl3_master 0x00004c01 - ssl3_shamd5 0x00008008 - tls1_master 0x00004c06 - tls1prf 0x0000800a - } $s ""] - - if {$algid eq ""} { - set oid [oid $s] - set algid [CertOIDToAlgId $oid] - if {$algid == 0} { - error "Could not map \"$s\" to algorithm id" - } - } - # Return the decimal form - return [expr {$algid}] -} - -# TBD - document -proc twapi::crypt_find_oid_info {key args} { - array set opts [parseargs args { - {restrict.arg any {sign encrypt any}} - keylen.int - {searchds.bool 0} - {oidgroup.arg 0} - } -maxleftover 0] - - # We will try key to be an OID, Alg Id, sign id or a simple - # name in turn - if {[catch { - set key [oid $key] - set keytype 1; # OID - }]} { - if {[catch { - set key [capi_algid $key] - set keytype 3; # Alg Id - }]} { - if {[catch { - # Sign - list of two alg id's - if {[llength $key] == 2} { - set key [list [capi_algid [lindex $key 0]] [capi_algid [lindex $key 1]]] - set keytype 4 - } else { - set keytype 2 ;# Name - } - }]} { - set keytype 2 ;# Name - } - } - } - - set oidgroup [oidgroup $opts(oidgroup)] - if {$opts(restrict) ne "any"} { - if {$oidgroup != 0 && $oidgroup != 3} { - error "The -restrict option can only be used with the oidgroup_pubkey_alg OID group" - } - if {$opts(restrict) eq "sign"} { - set keytype [expr {$keytype | 0x80000000}] - } else { - set keytype [expr {$keytype | 0x40000000}] - } - } - - if {[info exists opts(keylen)]} { - set oidgroup [expr {$oidgroup | ($opts(keylen) << 16)}] - } - - # Because search of active dir can be slow, turn it off unless - # caller explicitly requests it - if {! $opts(searchds)} { - set oidgroup [expr {$oidgroup | 0x80000000}] - } - - return [CryptFindOIDInfo $keytype $key $oidgroup] -} - -# TBD - document -proc twapi::crypt_enumerate_oid_info {{oidgroup 0}} { - # TBD - parse extra based on OID group - set ret {} - foreach info [CryptEnumOIDInfo [oidgroup $oidgroup]] { - lappend ret [twine {oid name oidgroup value extra} $info] - } - return $ret -} - -# TBD - test -proc twapi::_capi_parse {type arg args} { - parseargs args { - {contenttype.arg any} - {formattype.arg any} - {typesonly.bool 0} - } -setvars -maxleftover 0 - - # First try the formats not supported by CryptQueryObject - if {$contenttype in {any rsapublickey subjectpublickeyinfo}} { - if {$formattype eq "binary"} { - set encoding der - } elseif {$formattype eq "base64"} { - set encoding pem - } else { - set encoding "" - } - if {$type == 1} { - # arg is a file - set fd [open $arg] - trap { - fconfigure $fd -translation binary - set content [_pem_decode [read $fd] $encoding] - set is_pem [_is_pem $content] - } finally { - close $fd - } - } - if {$contenttype in {any subjectpublickeyinfo}} { - trap { - set data [CryptDecodeObjectEx 8 $content] - dict set ret contenttype subjectpublickeyinfo - dict set ret formattype [lindex {binary base64} $is_pem] - if {! $typesonly} { - dict set ret subjectpublickeyinfo $data - } - return $ret - } onerror {} { - if {$contenttype eq "subjectpublickeyinfo"} { - rethrow - } - # Go on to try other types - } - } - if {$contenttype in {any rsapublickey}} { - trap { - set data [CryptDecodeObjectEx 19 $content] - dict set ret contenttype rsapublickey - dict set ret formattype [lindex {binary base64} $is_pem] - if {! $typesonly} { - dict set ret rsapublickey $data - } - return $ret - } onerror {} { - if {$contenttype eq "rsapublickey"} { - rethrow - } - # Go on to try other types - } - } - } - - # No joy. Go on to try CryptQueryObject - - # Note - CERT_QUERY_CONTENT_FLAG_PFX_AND_LOAD not supported - # on XP/2k3 hence not included in expected_content_type - set contenttype [dict! { - cert 2 - ctl 4 - crl 8 - serializedstore 16 - serializedcert 32 - serializedctl 64 - serializedcrl 128 - pkcs7signed 256 - pkcs7unsigned 512 - pkcs7signedembed 1024 - pkcs10 2048 - pfx 4096 - certpair 8192 - any 0x3FFE - } $contenttype] - - set formattype [dict! { - binary 2 - base64 4 - asn1hex 8 - any 14 - } $formattype] - - set ret [CryptQueryObject $type $arg \ - $contenttype $formattype 0 $typesonly] - # We don't mention PKCS7_ASN v/s X509_ASN anywhere and use encoding - # to refer to PEM/DER so leave it off for now - dict unset ret encoding - dict set ret formattype [dict* { - 1 binary - 2 base64 - 3 asn1hex - } [dict get $ret formattype]] - dict set ret contenttype [dict* { - 1 cert - 2 ctl - 3 crl - 4 serializedstore - 5 serializedcert - 6 serializedctl - 7 serializedcrl - 8 pkcs7signed - 9 pkcs7unsigned - 10 pkcs7signedembed - 11 pkcs10 - 12 pfx - 13 certpair - } [dict get $ret contenttype]] - - return $ret -} -interp alias {} twapi::capi_parse_file {} twapi::_capi_parse 1 -interp alias {} twapi::capi_parse {} twapi::_capi_parse 2 - -### -# ASN.1 procs - -# TBD - document -proc twapi::asn1_decode_string {bin} { - # 24 -> X509_UNICODE_ANY_STRING - return [lindex [twapi::CryptDecodeObjectEx 24 $bin] 1] -} - -# TBD - document -proc twapi::asn1_encode_string {s {encformat utf8}} { - # 24 -> X509_UNICODE_ANY_STRING - return [twapi::CryptEncodeObjectEx 24 [list [dict! { - numeric 3 printable 4 teletex 5 t61 5 videotex 6 ia5 7 graphic 8 - visible 9 iso646 9 general 10 universal 11 int4 11 - bmp 12 unicode 12 utf8 13 - } $encformat] $s]] -} - -### -# Key procs - -proc twapi::_capi_key_param {param_id hkey args} { - if {[llength $args] == 0} { - return [CryptGetKeyParam $hkey $param_id] - } - if {[llength $args] == 1} { - return [CryptSetKeyParam $hkey $param_id [lindex $args 0]] - } - badargs! "Invalid syntax. Should be [lindex [info level -1] 0] HKEY ?VALUE?" 3 -} - -proc twapi::capi_key_iv {args} {return [_capi_key_param 1 {*}$args]} -proc twapi::capi_key_mode_bits {args} {return [_capi_key_param 5 {*}$args]} -proc twapi::capi_key_dss_p {args} {return [_capi_key_param 11 {*}$args]} -proc twapi::capi_key_dss_q {args} {return [_capi_key_param 13 {*}$args]} -proc twapi::capi_key_dss_g {args} {return [_capi_key_param 12 {*}$args]} -proc twapi::capi_key_effective_keylen {args} {return [_capi_key_param 19 {*}$args]} - -proc twapi::capi_key_blocklen {hkey} {return [CryptGetKeyParam $hkey 8]} -proc twapi::capi_key_certificate {hkey} {return [CryptGetKeyParam $hkey 26]} -proc twapi::capi_key_keylen {hkey} {return [CryptGetKeyParam $hkey 9]} - -proc twapi::capi_key_algid {hkey args} { - if {[llength $args] == 0} { - return [CryptGetKeyParam $hkey 7] - } - set args [lassign $args algid] - set algid [capi_algid $algid] - array set opts [parseargs args { - {archivable.bool 0 0x4000} - {salt.bool 0 4} - {exportable.bool 0 1} - {pregen.bool 0x40} - {userprotected.bool 0 2} - {nosalt40.bool 0 0x10} - {size.int 0} - } -maxleftover 0] - if {$opts(size) < 0 || $opts(size) > 65535} { - badargs! "Bad key size value '$size': must be positive integer less than 65536" - } - set flags [expr {($opts(size) << 16) | $opts(archivable) | $opts(salt) | $opts(exportable) | $opts(pregen) | $opts(userprotected) | $opts(nosalt40)}] - return [CryptSetKeyParam $hkey 7 $algid $flags] -} - -proc twapi::capi_key_mode {hkey args} { - if {[llength $args] == 0} { - return [dict* {1 cbc 2 ecb 3 ofb 4 cfb 5 cts} [CryptGetKeyParam $hkey 4]] - } - if {[llength $args] == 1} { - set val [dict* {cbc 1 ecb 2 ofb 3 cfb 4 cts 5} [lindex $args 0]] - return [CryptSetKeyParam $hkey 4 $val] - } - badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" -} - -proc twapi::capi_key_padding {hkey args} { - if {[llength $args] == 0} { - return [dict* {1 pkcs5 2 random 3 zeroes} [CryptGetKeyParam $hkey 3]] - } - if {[llength $args] == 1} { - set val [dict* {pkcs5 1 random 2 zeroes 3} [lindex $args 0]] - return [CryptSetKeyParam $hkey 3 $val] - } - badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" -} - -proc twapi::capi_key_permissions {hkey args} { - set bitmasks { - encrypt 0x01 decrypt 0x02 export 0x04 read 0x08 write 0x10 - mac 0x20 export_key 0x40 import_key 0x80 archive 0x100 - } - if {[llength $args] == 0} { - return [_make_symbolic_bitmask [CryptGetKeyParam $hkey 6] $bitmasks] - } - if {[llength $args] == 1} { - set val [_parse_symbolic_bitmask [lindex $args 0] $bitmasks] - return [CryptSetKeyParam $hkey 6 $val] - } - badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" -} - -proc twapi::capi_key_salt {hkey args} { - if {[llength $args] == 0} { - # 2 -> KP_SALT - return [CryptGetKeyParam $hkey 2] - } - if {[llength $args] == 1} { - # 10 -> KP_SALT_EX - return [CryptSetKeyParam $hkey 10 [lindex $args 0]] - } - badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" -} - -proc twapi::capi_keyblob_create {ver algid blob_type key} { - # 0 -> reserved field - return [list [_capi_keyblob_type_id $blob_type] $ver 0 [capi_algid $algid] $key] -} - -proc twapi::capi_keyblob_concealed {algid concealed_key} { - # 2 -> bVersion - # 0 -> concealed plaintextkeyblob - # Note: for our own home grown concealed type there is no - # BLOBHEADER - return [capi_keyblob_create 2 $algid concealed $concealed_key] -} - -proc twapi::capi_keyblob_plaintext {algid binkey} { - # typedef struct _PUBLICKEYSTRUC { - # BYTE bType; - # BYTE bVersion; - # WORD reserved; - # ALG_ID aiKeyAlg; - # } BLOBHEADER; - # 2 -> bVersion - set algnum [capi_algid $algid] - set blob_type [_capi_keyblob_type_id plaintext] - set len [string length $binkey] - set blob "[binary format ccsii $blob_type 2 0 $algnum $len]$binkey" - return [capi_keyblob_create 2 $algid plaintext $blob] -} - -proc twapi::capi_keyblob_version {kblob} { - return [lindex $kblob 1] -} - -proc twapi::capi_keyblob_algid {kblob} { - return [lindex $kblob 3] -} - -proc twapi::capi_keyblob_type {kblob} { - return [_capi_keyblob_type_name [lindex $kblob 0]] -} - -proc twapi::capi_keyblob_blob {kblob} { - return [lindex $kblob 4] -} - -proc twapi::_capi_keyblob_type_id {name} { - set blob_type [dict* { - concealed 0 - keystate 12 - opaque 9 - plaintext 8 - privatekey 7 - publickey 6 - publickeyex 10 - rfc3217 11 - simple 1 - } $name] -} - -proc twapi::_capi_keyblob_type_name {id} { - set blob_type [dict* { - 0 concealed - 1 simple - 6 publickey - 7 privatekey - 8 plaintext - 9 opaque - 10 publickeyex - 11 rfc3217 - 12 keystate - } [incr id 0]]; # incr to convert hex etc. to decimal - -} - -### -# Utility procs - -proc twapi::_make_algorithm_identifier {oid {param {}}} { - if {[string length $oid] == 0} { - return "" - } - if {0} { - # TBD - what modes to default to ? - switch -exact -- $oid { -#define szOID_NIST_AES128_CBC "2.16.840.1.101.3.4.1.2" -#define szOID_NIST_AES192_CBC "2.16.840.1.101.3.4.1.22" -#define szOID_NIST_AES256_CBC "2.16.840.1.101.3.4.1.42" - -#// For the above Algorithms, the AlgorithmIdentifier parameters must be -#// present and the parameters field MUST contain an AES-IV: -#// -#// AES-IV ::= OCTET STRING (SIZE(16)) - -#// NIST AES WRAP Algorithms -#define szOID_NIST_AES128_WRAP "2.16.840.1.101.3.4.1.5" -#define szOID_NIST_AES192_WRAP "2.16.840.1.101.3.4.1.25" -#define szOID_NIST_AES256_WRAP "2.16.840.1.101.3.4.1.45" - des { set oid "oid_rsa_des_ede3_cbc" } - des { set oid "oid_oiwsec_descbc" } - aes128 { TBD } - aes192 { TBD } - aes256 { TBD } - rc2 { set oid "oid_rsa_rc2cbc" } - rc4 { set oid "oid_rsa_rc4" } - } - } - set oid [oid $oid] - if {[string length $param]} { - return [list $oid $param] - } else { - return [list $oid] - } -} - -twapi::proc* twapi::_cert_prop_id {prop} { - # Certificate property menomics - variable _cert_prop_name_id_map - array set _cert_prop_name_id_map { - key_prov_handle 1 - key_prov_info 2 - sha1_hash 3 - hash 3 - md5_hash 4 - key_context 5 - key_spec 6 - ie30_reserved 7 - pubkey_hash_reserved 8 - enhkey_usage 9 - ctl_usage 9 - next_update_location 10 - friendly_name 11 - pvk_file 12 - description 13 - access_state 14 - signature_hash 15 - smart_card_data 16 - efs 17 - fortezza_data 18 - archived 19 - key_identifier 20 - auto_enroll 21 - pubkey_alg_para 22 - cross_cert_dist_points 23 - issuer_public_key_md5_hash 24 - subject_public_key_md5_hash 25 - id 26 - date_stamp 27 - issuer_serial_number_md5_hash 28 - subject_name_md5_hash 29 - extended_error_info 30 - - renewal 64 - archived_key_hash 65 - auto_enroll_retry 66 - aia_url_retrieved 67 - authority_info_access 68 - backed_up 69 - ocsp_response 70 - request_originator 71 - source_location 72 - source_url 73 - new_key 74 - ocsp_cache_prefix 75 - smart_card_root_info 76 - no_auto_expire_check 77 - ncrypt_key_handle 78 - hcryptprov_or_ncrypt_key_handle 79 - - subject_info_access 80 - ca_ocsp_authority_info_access 81 - ca_disable_crl 82 - root_program_cert_policies 83 - root_program_name_constraints 84 - subject_ocsp_authority_info_access 85 - subject_disable_crl 86 - cep 87 - - sign_hash_cng_alg 89 - - scard_pin_id 90 - scard_pin_info 91 - } -} { - variable _cert_prop_name_id_map - - if {[string is integer -strict $prop]} { - return $prop - } - if {![info exists _cert_prop_name_id_map($prop)]} { - badargs! "Unknown certificate property id '$prop'" 3 - } - - return $_cert_prop_name_id_map($prop) -} - -twapi::proc* twapi::_cert_prop_name {id} { - variable _cert_prop_name_id_map - variable _cert_prop_id_name_map - - _cert_prop_id key_prov_handle; # Just to init _cert_prop_name_id_map - array set _cert_prop_id_name_map [swapl [array get _cert_prop_name_id_map]] -} { - variable _cert_prop_id_name_map - if {[info exists _cert_prop_id_name_map($id)]} { - return $_cert_prop_id_name_map($id) - } - if {[string is integer -strict $id]} { - return $id - } - badargs! "Unknown certificate property id '$id'" 3 -} - -twapi::proc* twapi::_system_store_id {name} { - variable _system_store_locations - - set _system_store_locations { - service 0x40000 - "" 0x10000 - user 0x10000 - usergrouppolicy 0x70000 - localmachine 0x20000 - localmachineenterprise 0x90000 - localmachinegrouppolicy 0x80000 - services 0x50000 - users 0x60000 - } - - foreach loc [CertEnumSystemStoreLocation 0] { - dict set _system_store_locations {*}$loc - } -} { - variable _system_store_locations - - if {[string is integer -strict $name]} { - if {$name < 65536} { - badargs! "Invalid system store name $name" 3 - } - return $name - } - - return [dict! $_system_store_locations $name 2] -} - -twapi::proc* twapi::_csp_type_name_to_id prov { - variable _csp_type_name_id_map - - array set _csp_type_name_id_map { - prov_rsa_full 1 - prov_rsa_sig 2 - prov_dss 3 - prov_fortezza 4 - prov_ms_exchange 5 - prov_ssl 6 - prov_rsa_schannel 12 - prov_dss_dh 13 - prov_ec_ecdsa_sig 14 - prov_ec_ecnra_sig 15 - prov_ec_ecdsa_full 16 - prov_ec_ecnra_full 17 - prov_dh_schannel 18 - prov_spyrus_lynks 20 - prov_rng 21 - prov_intel_sec 22 - prov_replace_owf 23 - prov_rsa_aes 24 - } -} { - variable _csp_type_name_id_map - - set key [string tolower $prov] - - if {[info exists _csp_type_name_id_map($key)]} { - return $_csp_type_name_id_map($key) - } - - if {[string is integer -strict $prov]} { - return $prov - } - - badargs! "Invalid or unknown provider type '$prov'" 3 -} - -twapi::proc* twapi::_csp_type_id_to_name prov { - variable _csp_type_name_id_map - variable _csp_id_type_name_map - - _csp_type_name_to_id prov_rsa_full; # Just to ensure _csp_type_name_id_map exists - array set _csp_id_type_name_map [swapl [array get _csp_type_name_id_map]] -} { - variable _csp_id_type_name_map - if {[info exists _csp_id_type_name_map($prov)]} { - return $_csp_id_type_name_map($prov) - } - - if {[string is integer -strict $prov]} { - return $prov - } - - badargs! "Invalid or unknown CSP type id '$prov'" 3 -} - -twapi::proc* twapi::oid {name} { - variable _name_oid_map - if {![info exists _name_oid_map]} { - oids; # To init the map - } -} { - variable _name_oid_map - - if {[regexp {^\d+\.\d+(\.\d+)*$} $name]} { - return $name; # OID literal n.n... - } - if {[info exists _name_oid_map($name)]} { - return $_name_oid_map($name) - } - # Try by adding oid_ - if {[info exists _name_oid_map(oid_$name)]} { - return $_name_oid_map(oid_$name) - } - - badargs! "Invalid OID '$name'" - -} - -twapi::proc* twapi::oidname {oid} { - variable _oid_name_map - if {![info exists _oid_name_map]} { - oids; # To init the map - } -} { - variable _oid_name_map - - if {[info exists _oid_name_map($oid)]} { - return $_oid_name_map($oid) - } - if {[regexp {^\d([\d\.]*\d)?$} $oid]} { - return $oid - } else { - badargs! "Invalid OID '$oid'" - } -} - -# TBD - change OID mnemonics to those in RFC (see pki.tcl in tcllib) -twapi::proc* twapi::oids {{pattern *}} { - variable _oid_name_map - variable _name_oid_map - - # TBD - clean up table for rarely used OIDs - array set _name_oid_map { - oid_common_name "2.5.4.3" - oid_sur_name "2.5.4.4" - oid_device_serial_number "2.5.4.5" - oid_country_name "2.5.4.6" - oid_locality_name "2.5.4.7" - oid_state_or_province_name "2.5.4.8" - oid_street_address "2.5.4.9" - oid_organization_name "2.5.4.10" - oid_organizational_unit_name "2.5.4.11" - oid_title "2.5.4.12" - oid_description "2.5.4.13" - oid_search_guide "2.5.4.14" - oid_business_category "2.5.4.15" - oid_postal_address "2.5.4.16" - oid_postal_code "2.5.4.17" - oid_post_office_box "2.5.4.18" - oid_physical_delivery_office_name "2.5.4.19" - oid_telephone_number "2.5.4.20" - oid_telex_number "2.5.4.21" - oid_teletext_terminal_identifier "2.5.4.22" - oid_facsimile_telephone_number "2.5.4.23" - oid_x21_address "2.5.4.24" - oid_international_isdn_number "2.5.4.25" - oid_registered_address "2.5.4.26" - oid_destination_indicator "2.5.4.27" - oid_user_password "2.5.4.35" - oid_user_certificate "2.5.4.36" - oid_ca_certificate "2.5.4.37" - oid_authority_revocation_list "2.5.4.38" - oid_certificate_revocation_list "2.5.4.39" - oid_cross_certificate_pair "2.5.4.40" - - oid_rsa "1.2.840.113549" - oid_pkcs "1.2.840.113549.1" - oid_rsa_hash "1.2.840.113549.2" - oid_rsa_encrypt "1.2.840.113549.3" - - oid_pkcs_1 "1.2.840.113549.1.1" - oid_pkcs_2 "1.2.840.113549.1.2" - oid_pkcs_3 "1.2.840.113549.1.3" - oid_pkcs_4 "1.2.840.113549.1.4" - oid_pkcs_5 "1.2.840.113549.1.5" - oid_pkcs_6 "1.2.840.113549.1.6" - oid_pkcs_7 "1.2.840.113549.1.7" - oid_pkcs_8 "1.2.840.113549.1.8" - oid_pkcs_9 "1.2.840.113549.1.9" - oid_pkcs_10 "1.2.840.113549.1.10" - oid_pkcs_12 "1.2.840.113549.1.12" - - oid_rsa_rsa "1.2.840.113549.1.1.1" - oid_rsa_md2rsa "1.2.840.113549.1.1.2" - oid_rsa_md4rsa "1.2.840.113549.1.1.3" - oid_rsa_md5rsa "1.2.840.113549.1.1.4" - oid_rsa_sha1rsa "1.2.840.113549.1.1.5" - oid_rsa_setoaep_rsa "1.2.840.113549.1.1.6" - - oid_rsa_dh "1.2.840.113549.1.3.1" - - oid_rsa_data "1.2.840.113549.1.7.1" - oid_rsa_signeddata "1.2.840.113549.1.7.2" - oid_rsa_envelopeddata "1.2.840.113549.1.7.3" - oid_rsa_signenvdata "1.2.840.113549.1.7.4" - oid_rsa_digesteddata "1.2.840.113549.1.7.5" - oid_rsa_hasheddata "1.2.840.113549.1.7.5" - oid_rsa_encrypteddata "1.2.840.113549.1.7.6" - - oid_rsa_emailaddr "1.2.840.113549.1.9.1" - oid_rsa_unstructname "1.2.840.113549.1.9.2" - oid_rsa_contenttype "1.2.840.113549.1.9.3" - oid_rsa_messagedigest "1.2.840.113549.1.9.4" - oid_rsa_signingtime "1.2.840.113549.1.9.5" - oid_rsa_countersign "1.2.840.113549.1.9.6" - oid_rsa_challengepwd "1.2.840.113549.1.9.7" - oid_rsa_unstructaddr "1.2.840.113549.1.9.8" - oid_rsa_extcertattrs "1.2.840.113549.1.9.9" - oid_rsa_certextensions "1.2.840.113549.1.9.14" - oid_rsa_smimecapabilities "1.2.840.113549.1.9.15" - oid_rsa_prefersigneddata "1.2.840.113549.1.9.15.1" - - oid_rsa_smimealg "1.2.840.113549.1.9.16.3" - oid_rsa_smimealgesdh "1.2.840.113549.1.9.16.3.5" - oid_rsa_smimealgcms3deswrap "1.2.840.113549.1.9.16.3.6" - oid_rsa_smimealgcmsrc2wrap "1.2.840.113549.1.9.16.3.7" - - oid_rsa_md2 "1.2.840.113549.2.2" - oid_rsa_md4 "1.2.840.113549.2.4" - oid_rsa_md5 "1.2.840.113549.2.5" - - oid_rsa_rc2cbc "1.2.840.113549.3.2" - oid_rsa_rc4 "1.2.840.113549.3.4" - oid_rsa_des_ede3_cbc "1.2.840.113549.3.7" - oid_rsa_rc5_cbcpad "1.2.840.113549.3.9" - - - oid_ansi_x942 "1.2.840.10046" - oid_ansi_x942_dh "1.2.840.10046.2.1" - - oid_x957 "1.2.840.10040" - oid_x957_dsa "1.2.840.10040.4.1" - oid_x957_sha1dsa "1.2.840.10040.4.3" - - oid_ds "2.5" - oid_dsalg "2.5.8" - oid_dsalg_crpt "2.5.8.1" - oid_dsalg_hash "2.5.8.2" - oid_dsalg_sign "2.5.8.3" - oid_dsalg_rsa "2.5.8.1.1" - - oid_pkix_kp_server_auth "1.3.6.1.5.5.7.3.1" - oid_pkix_kp_client_auth "1.3.6.1.5.5.7.3.2" - oid_pkix_kp_code_signing "1.3.6.1.5.5.7.3.3" - oid_pkix_kp_email_protection "1.3.6.1.5.5.7.3.4" - oid_pkix_kp_ipsec_end_system "1.3.6.1.5.5.7.3.5" - oid_pkix_kp_ipsec_tunnel "1.3.6.1.5.5.7.3.6" - oid_pkix_kp_ipsec_user "1.3.6.1.5.5.7.3.7" - oid_pkix_kp_timestamp_signing "1.3.6.1.5.5.7.3.8" - oid_pkix_kp_ocsp_signing "1.3.6.1.5.5.7.3.9" - - oid_oiw "1.3.14" - - oid_oiwsec "1.3.14.3.2" - oid_oiwsec_md4rsa "1.3.14.3.2.2" - oid_oiwsec_md5rsa "1.3.14.3.2.3" - oid_oiwsec_md4rsa2 "1.3.14.3.2.4" - oid_oiwsec_desecb "1.3.14.3.2.6" - oid_oiwsec_descbc "1.3.14.3.2.7" - oid_oiwsec_desofb "1.3.14.3.2.8" - oid_oiwsec_descfb "1.3.14.3.2.9" - oid_oiwsec_desmac "1.3.14.3.2.10" - oid_oiwsec_rsasign "1.3.14.3.2.11" - oid_oiwsec_dsa "1.3.14.3.2.12" - oid_oiwsec_shadsa "1.3.14.3.2.13" - oid_oiwsec_mdc2rsa "1.3.14.3.2.14" - oid_oiwsec_sharsa "1.3.14.3.2.15" - oid_oiwsec_dhcommmod "1.3.14.3.2.16" - oid_oiwsec_desede "1.3.14.3.2.17" - oid_oiwsec_sha "1.3.14.3.2.18" - oid_oiwsec_mdc2 "1.3.14.3.2.19" - oid_oiwsec_dsacomm "1.3.14.3.2.20" - oid_oiwsec_dsacommsha "1.3.14.3.2.21" - oid_oiwsec_rsaxchg "1.3.14.3.2.22" - oid_oiwsec_keyhashseal "1.3.14.3.2.23" - oid_oiwsec_md2rsasign "1.3.14.3.2.24" - oid_oiwsec_md5rsasign "1.3.14.3.2.25" - oid_oiwsec_sha1 "1.3.14.3.2.26" - oid_oiwsec_dsasha1 "1.3.14.3.2.27" - oid_oiwsec_dsacommsha1 "1.3.14.3.2.28" - oid_oiwsec_sha1rsasign "1.3.14.3.2.29" - - oid_oiwdir "1.3.14.7.2" - oid_oiwdir_crpt "1.3.14.7.2.1" - oid_oiwdir_hash "1.3.14.7.2.2" - oid_oiwdir_sign "1.3.14.7.2.3" - oid_oiwdir_md2 "1.3.14.7.2.2.1" - oid_oiwdir_md2rsa "1.3.14.7.2.3.1" - - oid_infosec "2.16.840.1.101.2.1" - oid_infosec_sdnssignature "2.16.840.1.101.2.1.1.1" - oid_infosec_mosaicsignature "2.16.840.1.101.2.1.1.2" - oid_infosec_sdnsconfidentiality "2.16.840.1.101.2.1.1.3" - oid_infosec_mosaicconfidentiality "2.16.840.1.101.2.1.1.4" - oid_infosec_sdnsintegrity "2.16.840.1.101.2.1.1.5" - oid_infosec_mosaicintegrity "2.16.840.1.101.2.1.1.6" - oid_infosec_sdnstokenprotection "2.16.840.1.101.2.1.1.7" - oid_infosec_mosaictokenprotection "2.16.840.1.101.2.1.1.8" - oid_infosec_sdnskeymanagement "2.16.840.1.101.2.1.1.9" - oid_infosec_mosaickeymanagement "2.16.840.1.101.2.1.1.10" - oid_infosec_sdnskmandsig "2.16.840.1.101.2.1.1.11" - oid_infosec_mosaickmandsig "2.16.840.1.101.2.1.1.12" - oid_infosec_suiteasignature "2.16.840.1.101.2.1.1.13" - oid_infosec_suiteaconfidentiality "2.16.840.1.101.2.1.1.14" - oid_infosec_suiteaintegrity "2.16.840.1.101.2.1.1.15" - oid_infosec_suiteatokenprotection "2.16.840.1.101.2.1.1.16" - oid_infosec_suiteakeymanagement "2.16.840.1.101.2.1.1.17" - oid_infosec_suiteakmandsig "2.16.840.1.101.2.1.1.18" - oid_infosec_mosaicupdatedsig "2.16.840.1.101.2.1.1.19" - oid_infosec_mosaickmandupdsig "2.16.840.1.101.2.1.1.20" - oid_infosec_mosaicupdatedinteg "2.16.840.1.101.2.1.1.21" - } - - # OIDs for certificate extensions - array set _name_oid_map { - oid_authority_key_identifier_old "2.5.29.1" - oid_key_attributes "2.5.29.2" - oid_cert_policies_95 "2.5.29.3" - oid_key_usage_restriction "2.5.29.4" - oid_subject_alt_name_old "2.5.29.7" - oid_issuer_alt_name_old "2.5.29.8" - oid_basic_constraints_old "2.5.29.10" - oid_key_usage "2.5.29.15" - oid_privatekey_usage_period "2.5.29.16" - oid_basic_constraints "2.5.29.19" - - oid_cert_policies "2.5.29.32" - oid_any_cert_policy "2.5.29.32.0" - oid_inhibit_any_policy "2.5.29.54" - - oid_authority_key_identifier "2.5.29.35" - oid_subject_key_identifier "2.5.29.14" - oid_subject_alt_name2 "2.5.29.17" - oid_issuer_alt_name "2.5.29.18" - oid_crl_reason_code "2.5.29.21" - oid_reason_code_hold "2.5.29.23" - oid_crl_dist_points "2.5.29.31" - oid_enhanced_key_usage "2.5.29.37" - - oid_any_enhanced_key_usage "2.5.29.37.0" - - oid_crl_number "2.5.29.20" - oid_delta_crl_indicator "2.5.29.27" - oid_issuing_dist_point "2.5.29.28" - oid_freshest_crl "2.5.29.46" - oid_name_constraints "2.5.29.30" - - oid_policy_mappings "2.5.29.33" - oid_legacy_policy_mappings "2.5.29.5" - oid_policy_constraints "2.5.29.36" - } - - array set _oid_name_map [swapl [array get _name_oid_map]] -} { - variable _name_oid_map - return [array get _name_oid_map $pattern] -} - -# TBD - document -proc twapi::oidgroup {oidgroup} { - if {[string is integer -strict $oidgroup]} { - return $oidgroup - } - return [dict! { - oidgroup_hash_alg 1 - oidgroup_encrypt_alg 2 - oidgroup_pubkey_alg 3 - oidgroup_sign_alg 4 - oidgroup_rdn_attr 5 - oidgroup_ext_or_attr 6 - oidgroup_enhkey_usage 7 - oidgroup_policy 8 - oidgroup_template 9 - } $oidgroup] -} - -# TBD - document -proc twapi::oidgroup_token {oidgroup} { - return [lindex { - {} - oidgroup_hash_alg - oidgroup_encrypt_alg - oidgroup_pubkey_alg - oidgroup_sign_alg - oidgroup_rdn_attr - oidgroup_ext_or_attr - oidgroup_enhkey_usage - oidgroup_policy - oidgroup_template - } $oidgroup] -} - -proc twapi::_make_altnames_ext {altnames {critical 0} {issuer 0}} { - set names {} - foreach pair $altnames { - lassign $pair alttype altname - lappend names [list \ - [dict get { - other 1 - email 2 - dns 3 - directory 5 - url 7 - ip 8 - registered 9 - } $alttype] $altname] - } - - return [list [expr {$issuer ? "2.5.29.18" : "2.5.29.17"}] $critical $names] -} - -proc twapi::_get_enhkey_usage_oids {names} { - array set map [oids oid_pkix_kp_*] - - # We use an array to remove duplicates - array set oids {} - foreach name $names { - if {[info exists map($name)]} { - set oids($map($name)) 1 - } elseif {[info exists map(oid_pkix_kp_$name)]} { - set oids($map(oid_pkix_kp_$name)) 1 - } elseif {[regexp {^\d([\d\.]*\d)?$} $name]} { - # Any OID will do - set oids($name) 1 - } else { - error "Invalid Enhanced Key Usage OID \"$name\"" - } - } - return [array names oids] -} - -proc twapi::_make_enhkeyusage_ext {enhkeyusage {critical 0}} { - return [list "2.5.29.37" $critical [_get_enhkey_usage_oids $enhkeyusage]] -} - -twapi::proc* twapi::_init_keyusage_names {} { - variable _keyusage_byte1 - variable _keyusage_byte2 - set _keyusage_byte1 { - digital_signature 0x80 - non_repudiation 0x40 - key_encipherment 0x20 - data_encipherment 0x10 - key_agreement 0x08 - key_cert_sign 0x04 - crl_sign 0x02 - encipher_only 0x01 - } - set _keyusage_byte2 { - decipher_only 0x80 - } -} {} - -proc twapi::_make_basic_constraints_ext {basicconstraints {critical 1}} { - lassign $basicconstraints isca capathlenvalid capathlen - if {[string is boolean $isca] && [string is boolean $capathlenvalid] && - [string is integer -strict $capathlen] && $capathlen >= 0} { - return [list "2.5.29.19" $critical [list $isca $capathlenvalid $capathlen]] - } - error "Invalid basicconstraints value" -} - -proc twapi::_make_keyusage_ext {keyusage {critical 0}} { - variable _keyusage_byte1 - variable _keyusage_byte2 - - _init_keyusage_names - set byte1 0 - set byte2 0 - foreach usage $keyusage { - if {[dict exists $_keyusage_byte1 $usage]} { - set byte1 [expr {$byte1 | [dict get $_keyusage_byte1 $usage]}] - } elseif {[dict exists $_keyusage_byte2 $usage]} { - set byte2 [expr {$byte2 | [dict get $_keyusage_byte2 $usage]}] - } else { - error "Invalid key usage value \"$keyusage\"" - } - } - - set bin [binary format cc $byte1 $byte2] - # 7 -> # unused bits in last byte - return [list "2.5.29.15" $critical [list $bin 7]] -} - -# Given a byte array, decode to key usage flags -proc twapi::_cert_decode_keyusage {bin} { - variable _keyusage_byte1 - variable _keyusage_byte2 - - _init_keyusage_names - - binary scan $bin c* bytes - - if {[llength $bytes] == 0} { - return *; # Field not present, TBD - } - - set usages {} - set byte [lindex $bytes 0] - dict for {key val} $_keyusage_byte1 { - if {$byte & $val} { - lappend usages $key - } - } - - set byte [lindex $bytes 1] - dict for {key val} $_keyusage_byte2 { - if {$byte & $val} { - lappend usages $key - set byte [expr {$byte & ~$val}] - } - } - - if {0} { - # Commented out because some certificates seem to contain - # bits not defined by RF5280. Do not barf on these - - # For the second byte, not all bits are defined. Error if any - # that we do not understand - if {$byte} { - error "Key usage sequence $bytes includes unsupported bits" - } - - # If there are more bytes, they should all be 0 as well - foreach byte [lrange $bytes 2 end] { - if {$byte} { - error "Key usage sequence $bytes includes unsupported bits" - } - } - } - - return $usages -} - -proc twapi::_cert_decode_enhkey {vals} { - set result {} - set symmap [swapl [oids oid_pkix_kp_*]] - foreach val $vals { - if {[dict exists $symmap $val]} { - lappend result [string range [dict get $symmap $val] 12 end] - } else { - lappend result $val - } - } - return $result -} - -proc twapi::_cert_decode_extension {oid val} { - # TBD - see what other types need to be decoded - # 2.5.29.19 - basic constraints - # - switch $oid { - 2.5.29.15 { return [_cert_decode_keyusage $val] } - 2.5.29.37 { return [_cert_decode_enhkey $val] } - 2.5.29.17 - - 2.5.29.18 { - # TBD - replace with lmap for 8.6 - set names {} - foreach elem $val { - lappend names [list [dict* { - 1 other 2 email 3 dns 5 directory 7 url 8 ip 9 registered - } [lindex $elem 0]] [lindex $elem 1]] - } - return $names - } - } - return $val -} - -proc twapi::_crypt_keyspec {keyspec} { - return [dict* {keyexchange 1 signature 2} $keyspec] -} - -proc twapi::_cert_create_parse_options {optvals optsvar} { - upvar 1 $optsvar opts - - # TBD - add -issueraltnames - parseargs optvals { - start.arg - end.arg - serialnumber.arg - altnames.arg - enhkeyusage.arg - keyusage.arg - basicconstraints.arg - {purpose.arg {}} - {capathlen.int -1} - } -ignoreunknown -setvars - - set ca [expr {"ca" in $purpose}] - if {$ca} { - if {[info exists basicconstraints]} { - badargs! "Option -basicconstraints cannot be specified if \"ca\" is included in the -purpose option" - } - if {$capathlen < 0} { - set basicconstraints {{1 0 0} 1}; # No path length constraint - } else { - set basicconstraints [list [list 1 1 $capathlen] 1] - } - } else { - if {![info exists basicconstraints]} { - set basicconstraints {{0 0 0} 1} - } - } - set sslserver [expr {"server" in $purpose}] - set sslclient [expr {"client" in $purpose}] - - if {[info exists serialnumber]} { - if {$serialnumber <= 0 || $serialnumber > 0x7fffffffffffffff} { - badargs! "Serial number must be specified as a positive wide integer." - } - # Format as little endian - set opts(serialnumber) [binary format w $serialnumber] - } else { - # Generate 15 byte random and add high byte (little endian) - # to 0x01 to ensure it is treated as positive - set opts(serialnumber) "[random_bytes 15]\x01" - } - - # Validity period - if {[info exists start]} { - set opts(start) $start - } else { - set opts(start) [_seconds_to_timelist [clock seconds] 1] - } - if {[info exists end]} { - set opts(end) $end - } else { - set opts(end) $opts(start) - lset opts(end) 0 [expr {[lindex $opts(end) 0] + 1}] - # Ensure valid date (Feb 29 leap year -> non-leap year for example) - set opts(end) [clock format [clock scan [lrange $opts(end) 0 2] -format "%Y %N %e"] -format "%Y %N %e"] - lappend opts(end) 23 59 59 0 - } - - # Generate the extensions list - set exts {} - lappend exts [_make_basic_constraints_ext {*}$basicconstraints ] - if {$ca} { - lappend extra_keyusage key_cert_sign crl_sign - } - if {$sslserver || $sslclient} { - # TBD - not clear key_agreement is needed for SSL certs for - # either client or server. See - # https://access.redhat.com/documentation/en-us/red_hat_certificate_system/10/html/administration_guide/standard_x.509_v3_certificate_extensions - lappend extra_keyusage digital_signature key_encipherment key_agreement - if {$sslserver} { - lappend extra_enhkeyusage oid_pkix_kp_server_auth - } - if {$sslclient} { - lappend extra_enhkeyusage oid_pkix_kp_client_auth - } - } - - if {[info exists extra_keyusage]} { - if {[info exists keyusage]} { - # TBD - should it be marked critical or not ? - lset keyusage 0 [concat [lindex $keyusage 0] $extra_keyusage] - } else { - # TBD - should it be marked critical or not ? - set keyusage [list $extra_keyusage 1] - } - } - - if {[info exists keyusage]} { - lappend exts [_make_keyusage_ext {*}$keyusage] - } - - if {[info exists extra_enhkeyusage]} { - if {[info exists enhkeyusage]} { - # TBD - should it be marked critical or not ? - lset enhkeyusage 0 [concat [lindex $enhkeyusage 0] $extra_enhkeyusage] - } else { - # TBD - should it be marked critical or not ? - set enhkeyusage [list $extra_enhkeyusage 1] - } - } - if {[info exists enhkeyusage]} { - lappend exts [_make_enhkeyusage_ext {*}$enhkeyusage] - } - - if {[info exists altnames]} { - lappend exts [_make_altnames_ext {*}$altnames] - } - - set opts(extensions) $exts - - return $optvals -} - -proc twapi::_cert_add_parseargs {vargs} { - upvar 1 $vargs optvals - parseargs optvals { - {disposition.arg preserve {overwrite duplicate update preserve}} - } -maxleftover 0 -setvars - - # 4 -> CERT_STORE_ADD_ALWAYS - # 3 -> CERT_STORE_ADD_REPLACE_EXISTING - # 6 -> CERT_STORE_ADD_NEWER - # 1 -> CERT_STORE_ADD_NEW - - return [list disposition \ - [dict get { - duplicate 4 - overwrite 3 - update 6 - preserve 1 - } $disposition]] -} - -proc twapi::_parse_store_open_opts {optvals} { - array set opts [parseargs optvals { - {commitenable.bool 0 0x00010000} - {readonly.bool 0 0x00008000} - {existing.bool 0 0x00004000} - {create.bool 0 0x00002000} - {includearchived.bool 0 0x00000200} - {maxpermissions.bool 0 0x00001000} - {deferclose.bool 0 0x00000004} - {backupprivilege.bool 0 0x00000800} - } -maxleftover 0 -nulldefault] - - set flags 0 - foreach {opt val} [array get opts] { - incr flags $val - } - return $flags -} - -# Helper to return as der/pem based on encoding option -proc twapi::_as_pem_or_der {bin tag encoding} { - if {$encoding eq "pem"} { - # 1 -> CRYPT_STRING_BASE64 - # 0x80000000 -> LF-only, not CRLF - return "-----BEGIN $tag-----\n[CryptBinaryToString $bin 0x80000001]-----END $tag-----\n" - } else { - return $bin - } -} - -# Helper for converting input parameters if they are in PEM format -# pem_or_der is the data -# enc specifies the type of pem_or_der. If empty, we guess. -# pemtype should generally be -# 0 -> CRYPT_STRING_BASE64HEADER for certificates -# 1 -> CRYPT_STRING_BASE64 (no header) -# 3 -> CRYPT_STRING_BASE64REQUESTHEADER -# 6 -> CRYPT_STRING_BASE64_ANY (actually same as 0 or 1) -proc twapi::_pem_decode {pem_or_der enc {pemtype 6}} { - if {$enc eq "der"} { - return $pem_or_der - } - if {$enc eq "pem" || - [regexp -nocase {^\s*-----\s*BEGIN\s+} $pem_or_der]} { - return [CryptStringToBinary $pem_or_der $pemtype] - } - return $pem_or_der -} - -proc twapi::_is_pem {pem_or_der} { - return [regexp -nocase {^\s*-----\s*BEGIN\s+} $pem_or_der] -} - -# Utility proc to generate certs in a memory store - -# one self signed which is used to sign a client and a server cert -proc twapi::make_test_certs {{hstore {}} args} { - crypt_test_container_cleanup - - parseargs args { - {csp.arg {Microsoft Strong Cryptographic Provider}} - {csptype.arg prov_rsa_full} - unique - {duration.int 5} - } -maxleftover 0 -setvars - - set enddate [clock format [clock seconds] -format "%Y %N %e"] - lset enddate 0 [expr {[lindex $enddate 0]+$duration}] - # Ensure valid date e.g. Feb 29 non-leap year - set enddate [clock format [clock scan $enddate -format "%Y %N %e"] -format "%Y %N %e"] - - if {$unique} { - set uuid [twapi::new_uuid] - } else { - set uuid "" - } - - # Create the self signed CA cert - set container twapitestca$uuid - set crypt [twapi::crypt_acquire $container -csp $csp -csptype $csptype -create 1] - twapi::crypt_key_free [twapi::crypt_generate_key $crypt signature -exportable 1] - set ca_altnames [list [list [list email ${container}@twapitest.com] [list dns ${container}.twapitest.com] [list url http://${container}.twapitest.com] [list directory [cert_name_to_blob "CN=${container}altname"]] [list ip [binary format c4 {127 0 0 2}]]]] - set cert [twapi::cert_create_self_signed_from_crypt_context "CN=$container, C=IN, O=Tcl, OU=twapi" $crypt -purpose {ca} -altnames $ca_altnames -end $enddate] - if {[llength $hstore] == 0} { - set hstore [twapi::cert_temporary_store] - } - set ca_certificate [twapi::cert_store_add_certificate $hstore $cert] - twapi::cert_release $cert - twapi::cert_set_key_prov $ca_certificate $container signature -csp $csp -csptype $csptype - crypt_free $crypt - - # Create the client and server certs - foreach cert_type {intermediate server client altserver full min} { - set container twapitest${cert_type}$uuid - set subject $container - set crypt [twapi::crypt_acquire $container -csp $csp -csptype $csptype -create 1] - twapi::crypt_key_free [twapi::crypt_generate_key $crypt keyexchange -exportable 1] - switch $cert_type { - intermediate { - set req [cert_request_create "CN=$container, C=IN, O=Tcl, OU=twapi" $crypt keyexchange -purpose ca] - set signing_cert $ca_certificate - } - altserver { - # No COMMON name. Used for testing use of DNS altname - set altnames [list [list [list dns ${cert_type}.twapitest.com] [list dns ${cert_type}2.twapitest.com]]] - set req [cert_request_create "C=IN, O=Tcl, OU=twapi, OU=$container" $crypt keyexchange -purpose $cert_type -altnames $altnames] - set signing_cert $ca_certificate - } - client - - server { - set req [cert_request_create "CN=$container, C=IN, O=Tcl, OU=twapi" $crypt keyexchange -purpose $cert_type] - set signing_cert $intermediate_certificate - } - full { - set altnames [list [list [list email ${container}@twapitest.com] [list dns ${cert_type}.twapitest.com] [list url http://${container}.twapitest.com] [list directory [cert_name_to_blob "CN=${container}altname"]] [list ip [binary format c4 {127 0 0 1}]]]] - set req [cert_request_create \ - "CN=$container, C=IN, O=Tcl, OU=twapi" \ - $crypt keyexchange \ - -keyusage [list {crl_sign data_encipherment digital_signature key_agreement key_cert_sign key_encipherment non_repudiation} 1]\ - -enhkeyusage [list {client_auth code_signing email_protection ipsec_end_system ipsec_tunnel ipsec_user server_auth timestamp_signing ocsp_signing} 1] \ - -altnames $altnames] - set signing_cert $ca_certificate - } - min { - set req [cert_request_create "CN=$container" $crypt keyexchange] - set signing_cert $ca_certificate - } - } - crypt_free $crypt - set parsed_req [cert_request_parse $req] - set subject [dict get $parsed_req subject] - set pubkey [dict get $parsed_req pubkey] - set opts {} - foreach optname {-basicconstraints -keyusage -enhkeyusage -altnames} { - if {[dict exists $parsed_req extensions $optname]} { - lappend opts $optname [dict get $parsed_req extensions $optname] - } - } - set encoded_cert [cert_create $subject $pubkey $signing_cert {*}$opts -end $enddate] - set certificate [twapi::cert_store_add_encoded_certificate $hstore $encoded_cert] - twapi::cert_set_key_prov $certificate $container keyexchange -csp $csp -csptype $csptype - if {$cert_type eq "intermediate"} { - set intermediate_certificate $certificate - } else { - cert_release $certificate - } - } - - cert_release $ca_certificate - cert_release $intermediate_certificate - return $hstore -} - -proc twapi::dump_test_certs {hstore dir {pfxfile twapitest.pfx}} { - set fd [open [file join $dir $pfxfile] wb] - puts -nonewline $fd [cert_store_export_pfx $hstore "" -exportprivatekeys 1] - close $fd - cert_store_iterate $hstore c { - set fd [open [file join $dir [cert_subject_name $c -name simpledisplay].cer] wb] - puts -nonewline $fd [cert_export $c] - close $fd - } -} - -proc twapi::crypt_test_containers {} { - set crypt [crypt_acquire "" -verifycontext 1] - twapi::trap { - set names {} - foreach name [crypt_key_container_names $crypt] { - if {[string match -nocase twapitest* $name]} { - lappend names $name - } - } - } finally { - crypt_free $crypt - } - return $names -} - -proc twapi::crypt_test_container_cleanup {} { - foreach c [crypt_test_containers] { - crypt_key_container_delete $c - } -} - - -# If we are not being sourced from a executable resource, need to -# source the remaining support files. In the former case, they are -# automatically combined into one so the sourcing is not needed. -if {![info exists twapi::twapi_crypto_rc_sourced]} { - source [file join [file dirname [info script]] sspi.tcl] - source [file join [file dirname [info script]] tls.tcl] -} - +# +# Copyright (c) 2007-2021, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + variable wintrust_guids + # Array key names match those in softpub.h in SDK + array set wintrust_guids { + action_generic_verify_v2 00AAC56B-CD44-11d0-8CC2-00C04FC295EE + action_trust_provider_test 573E31F8-DDBA-11d0-8CCB-00C04FC295EE + action_generic_cert_verify 189A3842-3041-11d1-85E1-00C04FC295EE + action_generic_chain_verify fc451c16-ac75-11d1-b4b8-00c04fb66ea0 + httpsprov_action 573E31F8-AABA-11d0-8CCB-00C04FC295EE + driver_action_verify F750E6C3-38EE-11d1-85E5-00C04FC295EE + } + + # Dictionaries used by capi_encrypt|decrypt_bytes to store partial blocks of data + # First level key is Crypto key handle + # Second level keys are Blocklen (block size in bytes) and Data (data bytes left over) + variable _capi_encrypt_partials + variable _capi_decrypt_partials + set _capi_encrypt_partials {} + set _capi_decrypt_partials {} +} + +### Hash functions + +proc twapi::capi_hash_create {hcrypt algid {hkey NULL}} { + return [CryptCreateHash $hcrypt [capi_algid $algid] $hkey] +} + +proc twapi::capi_hash_string {hhash s {enc utf-8}} { + return [capi_hash_bytes $hhash [encoding convertto $enc $s] 0] +} + +proc twapi::capi_hash_value {hhash} { + return [CryptGetHashParam $hhash 2]; # HP_HASHVAL +} + +proc twapi::capi_hash_sign {hhash keyspec args} { + # -pad not documented because new Windows version do not support X.931 + # and there are some openssl incompatibilities I cannot figure out + parseargs args { + {nohashoid.bool 0 1} + {pad.arg pkcs1 {pkcs1 x931}} + } -maxleftover 0 -setvars + set flags [expr {[dict get {pkcs1 0 x931 4} $pad] | $nohashoid}] + return [CryptSignHash $hhash [_crypt_keyspec $keyspec] "" $flags] +} + +proc twapi::capi_hash_verify {hhash sig hkey args} { + # -pad not documented because new Windows version do not support X.931 + # and there are some openssl incompatibilities I cannot figure out + parseargs args { + {nohashoid.bool 0 1} + {pad.arg pkcs1 {pkcs1 x931}} + } -maxleftover 0 -setvars + set flags [expr {[dict get {pkcs1 0 x931 4} $pad] | $nohashoid}] + return [CryptVerifySignature $hhash $sig $hkey "" $flags] +} + +proc twapi::_do_hash {csptype alg s {enc ""}} { + if {$enc ne ""} { + set s [encoding convertto $enc $s] + } + set hcrypt [crypt_acquire -csptype $csptype] + trap { + set hhash [capi_hash_create $hcrypt $alg] + capi_hash_bytes $hhash $s + return [capi_hash_value $hhash] + } finally { + if {[info exists hhash]} { + capi_hash_free $hhash + } + crypt_free $hcrypt + } +} + +interp alias {} twapi::md5 {} twapi::_do_hash prov_rsa_full md5 +interp alias {} twapi::sha1 {} twapi::_do_hash prov_rsa_full sha1 +interp alias {} twapi::sha256 {} twapi::_do_hash prov_rsa_aes sha_256 +interp alias {} twapi::sha384 {} twapi::_do_hash prov_rsa_aes sha_384 +interp alias {} twapi::sha512 {} twapi::_do_hash prov_rsa_aes sha_512 + +proc twapi::hmac {data key {prf sha1} {charset {}}} { + if {$charset ne ""} { + set data [encoding convertto $charset $data] + } + + # Choose prov_rsa_aes because older CSP's do not support sha256 + set hcrypt [crypt_acquire -csptype prov_rsa_aes] + try { + # The algorithm specified for importing the key actually is not + # executed at all. It's only used for importing the key. + # However it has to be something that will accept any key size. + # On Windows 8 at least, RC4 seems to require at least 5 byte keys. + # RC2 on the other hand, if the -ipsechmac flag is specifie + # will accept any number. TBD - the pbkdf2 source code implies + # on Win8.1 single byte keys will not be accepted by rc2 and + # keys need to be padded with 0's. Need to check that. + set hkey [crypt_import_key $hcrypt [capi_keyblob_concealed rc2 $key] -ipsechmac 1] + set hhash [capi_hash_create $hcrypt hmac $hkey] + # 5 -> HP_HMAC_INFO + CryptSetHashParam $hhash 5 [list [capi_algid $prf] "" ""] + capi_hash_bytes $hhash $data + return [capi_hash_value $hhash] + } finally { + if {[info exists hhash]} { + capi_hash_free $hhash + } + if {[info exists hkey]} { + capi_key_free $hkey + } + crypt_free $hcrypt + } +} + + +### Data protection + +proc twapi::protect_data {data args} { + + # Not used because doesn't seem to have any effect + # {promptonunprotect.bool 0 0x1} + parseargs args { + {description.arg ""} + {localmachine.bool 0 0x4} + {noui.bool 0 0x1} + {audit.bool 0 0x10} + {hwnd.arg NULL} + prompt.arg + } -setvars -maxleftover 0 + + if {[info exists prompt]} { + # 2 -> PROMPTONPROTECT + set prompt [list 2 $hwnd $prompt] + } else { + set prompt {} + } + + return [CryptProtectData $data $description "" "" $prompt [expr {$localmachine | $noui | $audit}]] +} + +proc twapi::unprotect_data {data args} { + # Do not seem to have any effect + # {promptonunprotect.bool 0 0x1} + # {promptonprotect.bool 0 0x2} + parseargs args { + {withdescription.bool 0} + {noui.bool 0 0x1} + {hwnd.arg NULL} + prompt.arg + } -setvars -maxleftover 0 + + if {[info exists prompt]} { + # 2 -> PROMPTONPROTECT + set prompt [list 2 $hwnd $prompt] + } else { + set prompt {} + } + + set data [CryptUnprotectData $data "" "" $prompt $noui] + if {$withdescription} { + return $data + } else { + return [lindex $data 0] + } +} + + + +################################################################ +# Certificate Stores + +# Close a certificate store +proc twapi::cert_store_release {hstore} { + CertCloseStore $hstore 0 + return +} + +proc twapi::cert_temporary_store {args} { + # TBD - add support for PKCS12_NO_PERSIST_KEY post-XP. If not + # specified and on XP document a means of getting rid of the key + # containers. See https://msdn.microsoft.com/en-us/library/ms867088.aspx#pk_topic6 + # Also CryptAcquireCertificatePrivateKey and GetCryptProvFromCert + # might be useful in this regard + parseargs args { + {encoding.arg {} {der pem {}}} + serialized.arg + pkcs7.arg + {password.arg ""} + pfx.arg + pkcs12.arg + {exportableprivatekeys.bool 0 1} + {userprotected.bool 0 2} + keysettype.arg + } -setvars -maxleftover 0 + + set nformats 0 + foreach format {serialized pkcs7 pfx pkcs12} { + if {[info exists $format]} { + set data [set $format] + incr nformats + } + } + if {$nformats > 1} { + badargs! "At most one of -pfx, -pkcs12, -pkcs7 or -serialized may be specified." + } + if {$nformats == 0} { + # 2 -> CERT_STORE_PROV_MEMORY + return [CertOpenStore 2 0 NULL 0 ""] + } + + # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING + + if {[info exists serialized]} { + # 6 -> CERT_STORE_PROV_SERIALIZED + return [CertOpenStore 6 0x10001 NULL 0 $data] + } + + if {[info exists pkcs7]} { + # 5 -> CERT_STORE_PROV_PKCS7 + return [CertOpenStore 5 0x10001 NULL 0 [_pem_decode $data $encoding]] + } + + # PFX/PKCS12 + if {[string length $password] == 0} { + set password [conceal ""] + } + set flags 0 + if {[info exists keysettype]} { + set flags [dict! {user 0x1000 machine 0x20} $keysettype] + } + + set flags [tcl::mathop::| $flags $exportableprivatekeys $userprotected] + return [PFXImportCertStore $data $password $flags] +} + +proc twapi::cert_file_store_open {path args} { + set flags [_parse_store_open_opts $args] + + if {! ($flags & 0x00008000)} { + # If not readonly, set commitenable + set flags [expr {$flags | 0x00010000}] + } + + # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING + # 8 -> CERT_STORE_PROV_FILENAME_W + return [CertOpenStore 8 0x10001 NULL $flags [file nativename [file normalize $path]]] +} + +proc twapi::cert_serialized_store_open {data args} { + set flags [_parse_store_open_opts $args] + + # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING + # 6 -> CERT_STORE_PROV_SERIALIZED + return [CertOpenStore 6 0x10001 NULL $flags $data] +} + +proc twapi::cert_physical_store_open {name location args} { + variable _system_stores + + set flags [_parse_store_open_opts $args] + incr flags [_system_store_id $location] + # 14 -> CERT_STORE_PROV_PHYSICAL_W + return [CertOpenStore 14 0 NULL $flags $name] +} + +proc twapi::cert_physical_store_delete {name location} { + set flags 0x10; # CERT_STORE_DELETE_FLAG + incr flags [_system_store_id $location] + + # 14 -> CERT_STORE_PROV_PHYSICAL_W + return [CertOpenStore 14 0 NULL $flags $name] +} + +# TBD - document and figure out what format to return data in +proc twapi::cert_physical_stores {system_store_name location} { + return [CertEnumPhysicalStore $system_store_name [_system_store_id $location]] +} + +proc twapi::cert_system_store_open {name args} { + variable _system_stores + + if {[llength $args] == 0} { + return [CertOpenSystemStore $name] + } + + set flags [_parse_store_open_opts [lassign $args location]] + incr flags [_system_store_id $location] + return [CertOpenStore 10 0 NULL $flags $name] +} + +proc twapi::cert_system_store_delete {name location} { + set flags 0x10; # CERT_STORE_DELETE_FLAG + incr flags [_system_store_id $location] + return [CertOpenStore 10 0 NULL $flags $name] +} + +proc twapi::cert_system_store_locations {} { + set l {} + foreach e [CertEnumSystemStoreLocation 0] { + lappend l [lindex $e 0] + } + return $l +} + +proc twapi::cert_system_stores {location} { + set l {} + foreach e [CertEnumSystemStore [_system_store_id $location] ""] { + lappend l [lindex $e 0] + } + return $l +} + +proc twapi::cert_store_iterate {hstore varname script {type any} {term {}}} { + upvar 1 $varname cert + set cert NULL + while {1} { + set cert [cert_store_find_certificate $hstore $type $term $cert] + if {$cert eq ""} break + switch [catch {uplevel 1 $script} result options] { + 0 - + 4 { + # Normal execution or continue. Keep $cert to get next cert + # from store + } + 3 { + # break - get out of loop so free the last cert + cert_release $cert + set cert "" + return + } + 1 - + default { + cert_release $cert + set cert "" + return -options $options $result + } + } + } + return +} + +proc twapi::cert_store_find_certificate {hstore {type any} {term {}} {hcert NULL}} { + + # TBD subject_cert 11<<16 + # TBD key_spec 9<<16 + + set term_types { + any 0 + existing 13<<16 + key_identifier 15<<16 + md5_hash 4<<16 + subject_public_key_md5_hash 18<<16 + sha1_hash 1<<16 + signature_hash 14<<16 + issuer_name (2<<16)|4 + subject_name (2<<16)|7 + issuer_substring (8<<16)|4 + subject_substring (8<<16)|7 + property 5<<16 + public_key 6<<16 + } + + if {$type eq "property"} { + set term [_cert_prop_id $term] + } + set type [expr [dict! $term_types $type 1]] + + # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING + return [CertFindCertificateInStore $hstore 0x10001 0 $type $term $hcert] +} + +proc twapi::cert_store_enum_contents {hstore {hcert NULL}} { + return [CertEnumCertificatesInStore $hstore $hcert] +} + +proc twapi::cert_store_add_certificate {hstore hcert args} { + array set opts [_cert_add_parseargs args] + return [CertAddCertificateContextToStore $hstore $hcert $opts(disposition)] +} + +proc twapi::cert_store_add_encoded_certificate {hstore enccert args} { + parseargs args { + {encoding.arg {} {der pem {}}} + } -ignoreunknown -setvars + array set opts [_cert_add_parseargs args] + return [CertAddEncodedCertificateToStore $hstore 0x10001 [_pem_decode $enccert $encoding] $opts(disposition)] +} + +proc twapi::cert_store_export_pem {hstore} { + set pem {} + cert_store_iterate $hstore c {append pem [cert_export $c]\n} + return $pem +} + +proc twapi::cert_store_export_pfx {hstore password args} { + parseargs args { + {exportprivatekeys.bool 0 0x4} + {failonmissingkey.bool 0 0x1} + {failonunexportablekey.bool 0 0x2} + } -maxleftover 0 -setvars + + if {[string length $password] == 0} { + set password [conceal ""] + } + + # NOTE: the -fail* flags only take effect iff the certificate in the store + # claims to have a private key but does not actually have one. It will + # not fail if the cert does not actually claim to have a private key + + set flags [tcl::mathop::| $exportprivatekeys $failonunexportablekey $failonmissingkey] + + return [PFXExportCertStoreEx $hstore $password {} $flags] +} +interp alias {} twapi::cert_store_export_pkcs12 {} twapi::cert_store_export_pfx + +proc twapi::cert_store_commit {hstore args} { + array set opts [parseargs args { + {force.bool 0} + } -maxleftover 0] + + return [Twapi_CertStoreCommit $hstore $opts(force)] +} + +proc twapi::cert_store_serialize {hstore} { + return [Twapi_CertStoreSerialize $hstore 1] +} + +proc twapi::cert_store_export_pkcs7 {hstore args} { + parseargs args { + {encoding.arg pem {der pem}} + } -setvars -maxleftover 0 + + return [_as_pem_or_der [Twapi_CertStoreSerialize $hstore 2] "PKCS7" $encoding] +} + +################################################################ +# Certificates + +interp alias {} twapi::cert_subject_name {} twapi::_cert_get_name subject +interp alias {} twapi::cert_issuer_name {} twapi::_cert_get_name issuer +proc twapi::_cert_get_name {field hcert args} { + + switch $field { + subject { set field 0 } + issuer { set field 1 } + default { badargs! "Invalid name type '$field': must be \"subject\" or \"issuer\"." + } + } + array set opts [parseargs args { + {name.arg oid_common_name} + {separator.arg comma {comma semicolon newline}} + {reverse.bool 0 0x02000000} + {noquote.bool 0 0x10000000} + {noplus.bool 0 0x20000000} + {format.arg x500 {x500 oid simple}} + } -maxleftover 0] + + set arg "" + switch $opts(name) { + email { set what 1 } + simpledisplay { set what 4 } + friendlydisplay {set what 5 } + dns { set what 6 } + url { set what 7 } + upn { set what 8 } + rdn { + set what 2 + switch $opts(format) { + simple {set arg 1} + oid {set arg 2} + x500 - + default {set arg 3} + } + set arg [expr {$arg | $opts(reverse) | $opts(noquote) | $opts(noplus)}] + switch $opts(separator) { + semicolon { set arg [expr {$arg | 0x40000000}] } + newline { set arg [expr {$arg | 0x08000000}] } + } + } + default { + set what 3; # Assume OID + set arg [oid $opts(name)] + } + } + + return [CertGetNameString $hcert $what $field $arg] + +} + +proc twapi::cert_blob_to_name {blob args} { + array set opts [parseargs args { + {format.arg x500 {x500 oid simple}} + {separator.arg comma {comma semi newline}} + {reverse.bool 0 0x02000000} + {noquote.bool 0 0x10000000} + {noplus.bool 0 0x20000000} + } -maxleftover 0] + + switch $opts(format) { + x500 {set arg 3} + simple {set arg 1} + oid {set arg 2} + } + + set arg [expr {$arg | $opts(reverse) | $opts(noquote) | $opts(noplus)}] + switch $opts(separator) { + semi { set arg [expr {$arg | 0x40000000}] } + newline { set arg [expr {$arg | 0x08000000}] } + } + + return [CertNameToStr $blob $arg] +} + +proc twapi::cert_name_to_blob {name args} { + array set opts [parseargs args { + {format.arg x500 {x500 oid simple}} + {separator.arg any {any comma semicolon newline}} + {reverse.bool 0 0x02000000} + {noquote.bool 0 0x10000000} + {noplus.bool 0 0x20000000} + } -maxleftover 0] + + switch $opts(format) { + x500 {set arg 3} + simple {set arg 1} + oid {set arg 2} + } + + set arg [expr {$arg | $opts(reverse) | $opts(noquote) | $opts(noplus)}] + switch $opts(separator) { + comma { set arg [expr {$arg | 0x04000000}] } + semicolon { set arg [expr {$arg | 0x40000000}] } + newline { set arg [expr {$arg | 0x08000000}] } + } + + return [CertStrToName $name $arg] +} + +proc twapi::cert_enum_properties {hcert args} { + parseargs args { + names + } -setvars -maxleftover 0 + + set id 0 + set ids {} + while {[set id [CertEnumCertificateContextProperties $hcert $id]]} { + if {$names} { + lappend ids [_cert_prop_name $id] + } else { + lappend ids $id + } + } + return $ids +} + +proc twapi::cert_property {hcert prop} { + # TBD - need to cook some properties - enhkey_usage + + if {[string is integer -strict $prop]} { + return [CertGetCertificateContextProperty $hcert $prop] + } else { + return [CertGetCertificateContextProperty $hcert [_cert_prop_id $prop] 1] + } +} + +proc twapi::cert_property_set {hcert prop propval} { + switch $prop { + pvk_file - + friendly_name - + description { + set val [encoding convertto unicode "${propval}\0"] + } + enhkey_usage { + set val [::twapi::CryptEncodeObjectEx 2.5.29.37 [_get_enhkey_usage_oids $propval]] + } + default { + badargs! "Invalid or unsupported property name \"$prop\". Must be one of [join $unicode_props {, }]." + } + } + + CertSetCertificateContextProperty $hcert [_cert_prop_id $prop] 0 $val +} + +proc twapi::cert_property_delete {hcert prop} { + CertSetCertificateContextProperty $hcert [_cert_prop_id $prop] 0 +} + +# TBD - Also add cert_set_key_prov_from_crypt_context +proc twapi::cert_set_key_prov {hcert keycontainer keyspec args} { + parseargs args { + csp.arg + {csptype.arg prov_rsa_full} + {keysettype.arg user {user machine}} + {silent.bool 0 0x40} + } -maxleftover 0 -nulldefault -setvars + + set flags $silent + if {$keysettype eq "machine"} { + incr flags 0x20; # CRYPT_KEYSET_MACHINE + } + + # 2 -> CERT_KEY_PROV_INFO_PROP_ID + # TBD - the provider param is hardcoded as {}. Should that be an option ? + CertSetCertificateContextProperty $hcert 2 0 \ + [list $keycontainer $csp [_csp_type_name_to_id $csptype] $flags {} [_crypt_keyspec $keyspec]] + return +} + +proc twapi::cert_export {hcert args} { + parseargs args { + {encoding.arg pem {der pem}} + } -maxleftover 0 -setvars + + return [_as_pem_or_der [lindex [Twapi_CertGetEncoded $hcert] 1] CERTIFICATE $encoding] +} + +proc twapi::cert_import {enccert args} { + parseargs args { + {encoding.arg {} {der pem {}}} + } -maxleftover 0 -setvars + return [CertCreateCertificateContext 0x10001 [_pem_decode $enccert $encoding]] +} + +proc twapi::cert_enhkey_usage {hcert {loc both}} { + return [_cert_decode_enhkey [CertGetEnhancedKeyUsage $hcert [dict! {property 4 extension 2 both 0} $loc 1]]] +} + +proc twapi::cert_key_usage {hcert} { + # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING + return [_cert_decode_keyusage [Twapi_CertGetIntendedKeyUsage 0x10001 $hcert]] +} + +proc twapi::cert_thumbprint {hcert} { + binary scan [cert_property $hcert sha1_hash] H* hash + return $hash +} + +proc twapi::cert_info {hcert} { + # TBD - add option to cook extensions using _cert_decode_extension + # instead of returning the raw form + set info [twine { + -version -serialnumber -signaturealgorithm -issuer + -start -end -subject -publickey -issuerid -subjectid -extensions} \ + [Twapi_CertGetInfo $hcert]] + dict set info -start \ + [clock format \ + [large_system_time_to_secs_since_1970 [dict get $info -start]] \ + -timezone :UTC \ + -format "%Y-%m-%d %H:%M:%S"] + dict set info -end \ + [clock format \ + [large_system_time_to_secs_since_1970 [dict get $info -end]] \ + -timezone :UTC \ + -format "%Y-%m-%d %H:%M:%S"] + + return $info +} + +proc twapi::cert_extension {hcert oid} { + set ext [CertFindExtension $hcert [oid $oid]] + if {[llength $ext] == 0} { + return $ext + } + lassign $ext oid critical val + return [list $critical [_cert_decode_extension $oid $val]] +} + +proc twapi::cert_create_self_signed {subject keycontainer keyspec args} { + set args [_cert_create_parse_options $args opts] + + array set opts [parseargs args { + {keysettype.arg user {machine user}} + {silent.bool 0 0x40} + {csp.arg {}} + {csptype.arg {prov_rsa_full}} + {signaturealgorithm.arg {}} + } -maxleftover 0 -ignoreunknown] + + set name_blob [cert_name_to_blob $subject] + + set kiflags $opts(silent) + if {$opts(keysettype) eq "machine"} { + incr kiflags 0x20; # CRYPT_MACHINE_KEYSET + } + set keyinfo [list \ + $keycontainer \ + $opts(csp) \ + [_csp_type_name_to_id $opts(csptype)] \ + $kiflags \ + {} \ + [_crypt_keyspec $keyspec]] + + set flags 0; # Always 0 for now + return [CertCreateSelfSignCertificate NULL $name_blob $flags $keyinfo \ + [_make_algorithm_identifier $opts(signaturealgorithm)] \ + $opts(start) $opts(end) $opts(extensions)] +} + +proc twapi::cert_create_self_signed_from_crypt_context {subject hprov args} { + set args [_cert_create_parse_options $args opts] + + array set opts [parseargs args { + {signaturealgorithm.arg {}} + } -maxleftover 0] + + set name_blob [cert_name_to_blob $subject] + + set flags 0; # Always 0 for now + return [CertCreateSelfSignCertificate $hprov $name_blob $flags {} \ + [_make_algorithm_identifier $opts(signaturealgorithm)] \ + $opts(start) $opts(end) $opts(extensions)] +} + +proc twapi::cert_create {subject pubkey cissuer args} { + set args [_cert_create_parse_options $args opts] + + parseargs args { + {encoding.arg pem {der pem}} + } -maxleftover 0 -setvars + + # TBD - check that issuer is a CA - but then what about self-signed? + + set issuer_info [cert_info $cissuer] + set issuer_blob [cert_name_to_blob [dict get $issuer_info -subject] -format x500] + set sigalgo [dict get $issuer_info -signaturealgorithm] + + # If issuer cert has altnames, use they as issuer altnames for new cert + set issuer_altnames [lindex [cert_extension $cissuer 2.5.29.17] 1] + if {[llength $issuer_altnames]} { + lappend opts(extensions) [_make_altnames_ext $issuer_altnames 0 1] + } + + # The subject key id in issuer's cert will become the + # authority key id in the new cert + # TBD - if fail, get the CERT_KEY_IDENTIFIER_PROP_ID + # 2.5.29.14 -> oid_subject_key_identifier + set issuer_subject_key_id [cert_extension $cissuer 2.5.29.14] + if {[string length [lindex $issuer_subject_key_id 1]] } { + # 2.5.29.35 -> oid_authority_key_identifier + lappend opts(extensions) [list 2.5.29.35 0 [list [lindex $issuer_subject_key_id 1] {} {}]] + } + + # Generate a subject key identifier for this cert based on a hash + # of the public key + set subject_key_id [Twapi_HashPublicKeyInfo $pubkey] + lappend opts(extensions) [list 2.5.29.14 0 $subject_key_id] + + set start [timelist_to_large_system_time $opts(start)] + set end [timelist_to_large_system_time $opts(end)] + + # 2 -> CERT_V3 + # issuer_id and subject_id for the certificate are left empty + # as recommended by gutman's X.509 paper + set cert_info [list 2 $opts(serialnumber) $sigalgo $issuer_blob \ + $start $end \ + [cert_name_to_blob $subject] \ + $pubkey {} {} \ + $opts(extensions)] + + # We need to get the crypt provider for the issuer cert since + # that is what will sign the new cert + lassign [cert_property $cissuer key_prov_info] issuer_container issuer_provname issuer_provtype issuer_flags dontcare issuer_keyspec + set hissuerprov [crypt_acquire $issuer_container -csp $issuer_provname -csptype $issuer_provtype -keysettype [expr {$issuer_flags & 0x20 ? "machine" : "user"}]] + trap { + # 0x10001 -> X509_ASN_ENCODING, 2 -> X509_CERT_TO_BE_SIGNED + return [_as_pem_or_der [CryptSignAndEncodeCertificate $hissuerprov \ + $issuer_keyspec \ + 0x10001 2 $cert_info $sigalgo] \ + CERTIFICATE $encoding] + } finally { + # TBD - test to make sure ok to close this if caller had + # it open + crypt_free $hissuerprov + } +} + +# TBD - test +proc twapi::cert_chain_build {hcert args} { + # -timestamp not documented because not clear exactly how it behaves + # -disablepass1*, -returnlower* not documented because not clear how + # useful. + # TBD - what about CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT + parseargs args { + {cacheendcert.bool 0 0x1} + {disableauthrootautoupdate.bool 0 0x100} + {disablepass1qualityfiltering.bool 0 0x40} + {engine.arg user {user machine}} + {hstore.arg NULL} + {returnlowerqualitycontexts.bool 0 0x80} + {revocationcheck.arg all {none all leaf excluderoot}} + {revocationcheckcacheonly.bool 0 0x80000000} + {timestamp.arg ""} + {urlretrievalcacheonly.bool 0 0x4} + usageall.arg + usageany.arg + } -setvars -maxleftover 0 + + set flags [dict! {none 0 all 0x20000000 leaf 0x10000000 excluderoot 0x40000000} $revocationcheck] + set flags [tcl::mathop::| $flags $cacheendcert $revocationcheckcacheonly $urlretrievalcacheonly $disablepass1qualityfiltering $returnlowerqualitycontexts $disableauthrootautoupdate] + + set usage_op 1; # USAGE_MATCH_TYPE_OR + if {[info exists usageall]} { + if {[info exists usageany]} { + error "Only one of -usageall and -usageany may be specified" + } + set usage_op 0; # USAGE_MATCH_TYPE_AND + set usage [_get_enhkey_usage_oids $usageall] + } elseif {[info exists usageany]} { + set usage [_get_enhkey_usage_oids $usageany] + } else { + set usage {} + } + + return [CertGetCertificateChain \ + [dict* {user NULL machine {1 HCERTCHAINENGINE}} $engine] \ + $hcert $timestamp $hstore \ + [list [list $usage_op $usage]] $flags] +} + +proc twapi::cert_ancestors {hcert args} { + # Note - does not care if certs are valid or not + set certs {} + set hchain [cert_chain_build $hcert {*}$args] + trap { + set simple_chain [twapi::Twapi_CertChainSimpleChain $hchain 0] + } finally { + cert_chain_release $hchain + } + foreach elem [dict get $simple_chain chain] { + lappend certs [dict get $elem hcert] + } + return $certs +} + +proc twapi::cert_chain_simple_chain {hchain index} { + set simple_chain [twapi::Twapi_CertChainSimpleChain $hchain $index] + set errors [_map_trust_error [dict get $simple_chain trust_errors]] + dict set simple_chain trust_errors $errors + if {[llength $errors]} { + dict set simple_chain status fail + } else { + dict set simple_chain status ok + } + dict set simple_chain trust_info [_map_trust_info [dict get $simple_chain trust_info]] + set chain_elements {} + foreach elem [dict get $simple_chain chain] { + set errors [_map_trust_error [dict get $elem trust_errors]] + dict set elem trust_errors $errors + if {[llength $errors]} { + dict set elem status fail + } else { + dict set elem status ok + } + dict set elem trust_info [_map_trust_info [dict get $elem trust_info]] + if {[dict exists $elem revocation]} { + set revocation [dict get $elem revocation] + if {$revocation == 0} { + dict unset elem revocation + } else { + dict set elem revocation [_map_cert_verify_error $revocation] + } + } + if {[dict exists $elem application_usage]} { + dict set elem application_usage [_cert_decode_enhkey [dict get $elem application_usage]] + } + lappend chain_elements $elem + } + dict set simple_chain chain $chain_elements + return $simple_chain +} + +# TBD - test +proc twapi::cert_chain_trust_info {hchain} { + return [_map_trust_info [Twapi_CertChainInfo $hchain]] +} + +proc twapi::_map_trust_info {info} { + return [_make_symbolic_bitmask $info { + hasexactmatchissuer 0x00000001 + haskeymatchissuer 0x00000002 + hasnamematchissuer 0x00000004 + isselfsigned 0x00000008 + haspreferredissuer 0x00000100 + hasissuancechainpolicy 0x00000200 + hasvalidnameconstraints 0x00000400 + ispeertrusted 0x00000800 + hascrlvalidityextended 0x00001000 + isfromexclusivetruststore 0x00002000 + iscomplexchain 0x00010000 + }] +} + +# TBD - test +proc twapi::cert_chain_trust_errors {hchain} { + return [_map_trust_error [Twapi_CertChainError $hchain]] +} + +proc twapi::_map_trust_error {errbits} { + return [_make_symbolic_bitmask $errbits { + time 1 + revoked 4 + signature 8 + wrongusage 0x10 + untrustedroot 0x20 + revocationunknown 0x40 + trustcycle 0x80 + extension 0x100 + policy 0x200 + basiconstraints 0x400 + nameconstraints 0x800 + unsupportednameconstraint 0x1000 + undefinednameconstraint 0x2000 + unpermittednameconstraint 0x4000 + excludednameconstraint 0x8000 + revocationoffline 0x01000000 + noissuancechainpolicy 0x02000000 + distrust 0x04000000 + criticalextension 0x08000000 + weaksignature 0x00100000 + partialchain 0x00010000 + ctltime 0x00020000 + ctlsignature 0x00040000 + ctlusage 0x00080000 + }] +} + +proc twapi::cert_verify {hcert policy args} { + # TBD - should we explicitly look for nulls in the subject name? + # The Chrome source at + # https://src.chromium.org/svn/branches/455/src/net/base/x509_certificate_win.cc + # does this though it also uses the same calls as below. See + # CertSubjectCommonNameHasNull in that code. + set policy_id [dict! { + authenticode 2 authenticodets 3 base 1 basicconstraints 5 + extendedvalidation 8 microsoftroot 7 ntauth 6 + ssl 4 tls 4 + } $policy] + + # Construct policy specific options + set optdefs { + {ignoreerrors.arg {}} + policyparams.arg + {trustedroots.arg} + } + switch -exact -- $policy_id { + 4 { + # SSL/TLS + lappend optdefs server.arg + } + 5 { + # basicconstraints + lappend optdefs isa.arg + } + 6 { + # ntauth also accepts -isa as it includes basic constraints checks + lappend optdefs isa.arg + } + 7 { + # microsoftroot + lappend optdefs enabletestroot.bool + } + } + + array set opts [parseargs args $optdefs -ignoreunknown -setvars] + + if {![dict exists $args -usageall] && ![dict exists $args -usageany]} { + switch -exact -- $policy { + authenticodets - + authenticode { + dict lappend args -usageany code_signing + } + ssl - + tls { + if {[info exists server]} { + dict lappend args -usageany server_auth + } else { + dict lappend args -usageany client_auth + } + } + } + } + + set verify_flags 0 + if {[info exists isa]} { + switch -exact -- $isa { + ca { set verify_flags [expr {$verify_flags | 0x80000000}] } + endentity { set verify_flags [expr {$verify_flags | 0x40000000}] } + default { + error "Invalid value \"$isa\" specified for option -isa." + } + } + } + if {[info exists enabletestroot]} { + set verify_flags [expr {$verify_flags | 0x00010000}] + } + + if {$policy eq "basicconstraints"} { + # TBD - peertrust 0x1000, see below + set ignore_options {} + } else { + # Any other policy + # TBD - the meaning of these is not clear. Are they ignore + # error flags or options? + # peertrust 0x1000 + # trusttestroot 0x4000 + # allowtestroot 0x8000 + set ignore_options { + time 0x07 + basicconstraints 0x08 + unknownca 0x10 + usage 0x20 + name 0x40 + policy 0x80 + revocation 0xf00 + criticalextensions 0x2000 + } + } + + foreach ignore $ignoreerrors { + if {![dict exists $ignore_options $ignore]} { + error "Value $ignore for option -ignoreerrors cannot be used with policy $policy." + } + set verify_flags [expr {$verify_flags | [dict get $ignore_options $ignore]}] + } + + if {![info exists policyparams]} { + switch -exact -- $policy_id { + 4 { + # ssl/tls + if {[info exists server]} { + set policyparams [cert_policy_params_tls -ignoreerrors $ignoreerrors -server $server] + } else { + set policyparams [cert_policy_params_tls -ignoreerrors $ignoreerrors] + } + } + default { + set policyparams {} + } + } + } + + if {[info exists ignoreerrors] && "revocation" in $ignoreerrors} { + lappend args -revocationcheck none + } + set chainh [cert_chain_build $hcert {*}$args] + + trap { + # Actually verification is a bit tricky because the caller might + # have asked for certain errors to be ignored. + # Note that CertVerifyChainPolicy below does NOT check for revocation + # of certificates in the certificate chain as per Microsoft docs. + # We therefore check for revocation errors here and abort if present. + set chain_errors [cert_chain_trust_errors $chainh] + if {[llength $chain_errors]} { + if {"revoked" in $chain_errors} { + return revoked + } + if {"revocationoffline" in $chain_errors} { + return revocationoffline + } + if {"revocationunknown" in $chain_errors} { + return revocationunknown + } + + if {0} { + # For other kind of errors, caller might have indicated + # some types are to be ignored. In that case we will proceed + # to use CertVerifyTrustPolicy since that will allow + # control of which errors are to be ignored. As a + # special case, if caller has specified additional trusted + # roots, we will proceed to call CertVerifyTrustPolicy + # even when caller is not ignoring errors but only if + # there are no errors indicated. + if {[llength $chain_errors] > 1 || + [lindex $chain_errors 0] ne "untrustedroot" || + ![info exists trustedroots]} { + return $chain_errors + } + } + } + + set status [Twapi_CertVerifyChainPolicy $policy_id $chainh [list $verify_flags $policyparams]] + + # If caller had provided additional trusted roots that are not + # in the Windows trusted store, and the error is that the root is + # untrusted, see if the root cert is one of the passed trusted ones + # We will only deal when there is a single possible chain else + # the recheck becomes very complicated as we are not sure if + # the recheck will employ the same chain or not. + if {$status == 0x800B0109 && + [info exists trustedroots] && [llength $trustedroots] && + [cert_chain_simple_chain_count $chainh] == 1} { + set simple_chain [cert_chain_simple_chain $chainh 0] + # Double check no errors listed for this chain + set trust_errors [dict get $simple_chain trust_errors] + if {[llength $trust_errors] == 1 && + [lindex $trust_errors 0] eq "untrustedroot"} { + set certs_in_chain [dict get $simple_chain chain] + set root_cert [dict get [lindex $certs_in_chain end] hcert] + set thumbprint [cert_thumbprint $root_cert] + # Match against each trusted root + set trusted 0 + foreach trusted_cert $trustedroots { + if {$thumbprint eq [cert_thumbprint $trusted_cert]} { + set trusted 1 + break + } + } + if {$trusted} { + # Yes, the root is trusted. It is not enough to + # say validation is ok because even if root + # is trusted, other errors might show up + # once untrusted roots are ignored. So we have + # to call the verification again. + # 0x10 -> CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG + set verify_flags [expr {$verify_flags | 0x10}] + if {0} { + TBD - need to redo the policy params? + # 0x100 -> SECURITY_FLAG_IGNORE_UNKNOWN_CA + set checks [expr {$checks | 0x100}] + } + # Retry the call ignoring root errors + set status [Twapi_CertVerifyChainPolicy $policy_id $chainh [list $verify_flags $policyparams]] + } + } + } + + return [_map_cert_verify_error $status] + } finally { + if {[info exists simple_chain]} { + foreach cert [dict get $simple_chain chain] { + cert_release [dict get $cert hcert] + } + } + cert_chain_release $chainh + } + + return $status +} + +proc twapi::_map_cert_verify_error {err} { + if {![string is integer -strict $err]} { + return $err + } + return [dict* { + 0x00000000 ok + 0x80096004 signature + 0x80092010 revoked + 0x800b0109 untrustedroot + 0x800b010d untrustedtestroot + 0x800b010a partialchain + 0x800b0110 wrongusage + 0x800b0101 time + 0x800b0114 name + 0x800b0113 policy + 0x80096019 basicconstraints + 0x800b0105 criticalextension + 0x800b0102 validityperiodnesting + 0x80092011 norevocationdll + 0x80092012 norevocationcheck + 0x80092013 revocationoffline + 0x800b010f cnmatch + 0x800b0106 purpose + 0x800b010e revocationunknown + 0x800b0103 carole + } [format 0x%8.8x $err]] +} + +# TBD - document +proc twapi::cert_policy_params_tls {args} { + + parseargs args { + ignoreerrors.arg + server.arg + } -maxleftover 0 -setvars -ignoreunknown + + if {[info exists server]} { + set role 2; # AUTHTYPE_SERVER + } else { + set role 1; # AUTHTYPE_CLIENT + set server "" + } + + set ignore_options { + time 0x2000 + unknownca 0x100 + usage 0x200 + name 0x1000 + revocation 0x80 + } + set checks 0 + foreach ignore $ignoreerrors { + # Note we use dict*, not dict! so we can skip any ignore tokens + # that we don't know + set checks [expr {$checks | [dict* $ignore_options $ignore 0]}] + } + return [list $role $checks $server] +} + +proc twapi::cert_tls_verify {hcert args} { + return [cert_verify $hcert tls {*}$args] +} + +# TBD - provide a -peersubject option +proc twapi::cert_fetch {addr {port 443}} { + set so [tls_socket $addr $port] + trap { + set sspi_ctx [chan configure $so -context] + return [sspi_remote_cert $sspi_ctx] + } finally { + close $so + } +} + +proc twapi::cert_locate_private_key {hcert args} { + parseargs args { + {keysettype.arg any {any user machine}} + {silent 0 0x40} + } -maxleftover 0 -setvars + + return [CryptFindCertificateKeyProvInfo $hcert \ + [expr {$silent | [dict get {any 0 user 1 machine 2} $keysettype]}]] +} + +proc twapi::cert_request_parse {req args} { + parseargs args { + {encoding.arg {} {der pem {}}} + } -setvars -maxleftover 0 + + # 3 -> CRYPT_STRING_BASE64REQUESTHEADER + # 4 -> X509_CERT_REQUEST_TO_BE_SIGNED + lassign [::twapi::CryptDecodeObjectEx 4 [_pem_decode $req $encoding 3]] ver subject pubkey attrs + lappend reqdict version $ver pubkey $pubkey attributes $attrs + lappend reqdict subject [cert_blob_to_name $subject] + foreach attr $attrs { + lassign $attr oid values + if {$oid eq "1.2.840.113549.1.9.14"} { + # ...1.9.14 -> oid_rsa_certextensions + set extensions {} + foreach ext [lindex $values 0] { + lassign $ext oid critical value + set value [_cert_decode_extension $oid $value] + lappend extensions $oid [list $value $critical] + # Also add "option keyed" values + switch -exact -- $oid { + 2.5.29.15 { + lappend extensions -keyusage [list $value $critical] + } + 2.5.29.17 { + lappend extensions -altnames [list $value $critical] + } + 2.5.29.19 { + lappend extensions -basicconstraints [list $value $critical] + } + 2.5.29.37 { + lappend extensions -enhkeyusage [list $value $critical] + } + } + } + lappend reqdict extensions $extensions + } + } + + return $reqdict +} + + +proc twapi::cert_request_create {subject hprov keyspec args} { + set args [_cert_create_parse_options $args opts] + # TBD - barf if any elements other than extensions is set + # TBD - document signaturealgorithmid + parseargs args { + {signaturealgorithmid.arg oid_rsa_sha1rsa} + {encoding.arg pem {der pem}} + } -setvars -maxleftover 0 + + set sigoid [oid $signaturealgorithmid] + if {$sigoid ni [list [oid oid_rsa_sha1rsa] [oid oid_rsa_md5rsa] [oid oid_x957_sha1dsa]]} { + badargs! "Invalid signature algorithm '$sigalg'" + } + set keyspec [twapi::_crypt_keyspec $keyspec] + # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING + # Pass oid_rsa_rsa as that seems to be what OPENSSL understands in + # a CSR + set pubkeyinfo [crypt_public_key $hprov $keyspec oid_rsa_rsa] + set attrs [list 0 [cert_name_to_blob $subject] $pubkeyinfo] + if {[llength $opts(extensions)]} { + lappend attrs [list [list [oid oid_rsa_certextensions] [list $opts(extensions)]]] + } else { + lappend attrs {} + } + return [_as_pem_or_der [CryptSignAndEncodeCertificate $hprov $keyspec 0x10001 4 $attrs $sigoid] "NEW CERTIFICATE REQUEST" $encoding] +} + + +################################################################ +# Cryptographic context commands + +proc twapi::crypt_acquire {args} { + # Backward compatibility - keycontainer can be specified as first arg + if {[llength $args] & 1} { + set args [lassign $args keycontainer] + } else { + set keycontainer "" + } + + parseargs args { + {csp.arg {}} + {csptype.arg prov_rsa_full} + keycontainer.arg + {keysettype.arg user {user machine}} + {create.bool 0 0x8} + {silent.bool 0 0x40} + verifycontext.bool + } -maxleftover 0 -setvars + + # The defaults for verifycontext are a little confusing. For a named + # key container, at least the MS CSP's require -verifycontext to be 0. + # For the frequent case where private keys are not required, MS recommends + # using the null key container with -verifycontext 1. So accordingly, + # if the keycontainer is empty (or unspecified), then it + # defaults to 1, else defaults to 0. + if {![info exists verifycontext]} { + if {$keycontainer eq ""} { + set verifycontext 1 + } else { + set verifycontext 0 + } + } + + if {$verifycontext} { + set verifycontext 0xf0000000 + } + + set flags [expr {$silent | $verifycontext}] + if {$keysettype eq "machine"} { + incr flags 0x20; # CRYPT_KEYSET_MACHINE + } + + trap { + return [CryptAcquireContext $keycontainer $csp [_csp_type_name_to_id $csptype] $flags] + } onerror {TWAPI_WIN32 0x80090016} { + # NTE_BAD_KEYSET - does not exist. Try to create it. + if {$create} { + set flags [expr {$flags | $create}] + return [CryptAcquireContext $keycontainer $csp [_csp_type_name_to_id $csptype] $flags] + } else { + rethrow + } + } +} + +proc twapi::crypt_free {hcrypt} { + twapi::CryptReleaseContext $hcrypt +} + +proc twapi::crypt_key_container_delete {keycontainer args} { + parseargs args { + csp.arg + {csptype.arg prov_rsa_full} + {keysettype.arg user {machine user}} + force + } -maxleftover 0 -nulldefault -setvars + + if {$keycontainer eq "" && ! $force} { + error "Default container cannot be deleted unless the -force option is specified" + } + + set flags 0x10; # CRYPT_DELETEKEYSET + if {$keysettype eq "machine"} { + incr flags 0x20; # CRYPT_MACHINE_KEYSET + } + + return [CryptAcquireContext $keycontainer $csp [_csp_type_name_to_id $csptype] $flags] +} + +proc twapi::crypt_generate_key {hprov algid args} { + + array set opts [parseargs args { + {archivable.bool 0 0x4000} + {salt.bool 0 4} + {exportable.bool 0 1} + {pregen.bool 0x40} + {userprotected.bool 0 2} + {nosalt40.bool 0 0x10} + {size.int 0} + } -maxleftover 0] + + set algid [capi_algid $algid] + + if {$opts(size) < 0 || $opts(size) > 65535} { + badargs! "Bad key size value '$size': must be positive integer less than 65536" + } + + return [CryptGenKey $hprov $algid [expr {($opts(size) << 16) | $opts(archivable) | $opts(salt) | $opts(exportable) | $opts(pregen) | $opts(userprotected) | $opts(nosalt40)}]] +} + +proc twapi::crypt_keypair {hprov keyspec} { + return [CryptGetUserKey $hprov [dict! {keyexchange 1 signature 2} $keyspec]] +} + +proc twapi::crypt_public_key_import {hprov key args} { + parseargs args { + {algid.arg 0} + {encoding.arg {} {native pem der {}}} + } -setvars + + if {$encoding eq "native"} { + set pub $key + } elseif {$encoding eq "der"} { + set pub [CryptDecodeObjectEx 8 $key] + } elseif {$encoding eq "pem" || + ($encoding eq "" && [string match -nocase "-----BEGIN*" $key])} { + set pub [CryptDecodeObjectEx 8 [CryptStringToBinary $key 0]] + } else { + # encoding is unspecified and is either der or native + if {[catch {set pub [CryptDecodeObjectEx 8 $key]}]} { + # Not DER, assume native + set pub $key + } + } + + return [CryptImportPublicKeyInfoEx $hprov 0x10001 $pub [capi_algid $algid]] +} + +proc twapi::crypt_public_key_export {hprov keyspec args} { + parseargs args { + algoid.arg + {encoding.arg pem {pem der native}} + } -setvars -nulldefault + + if {$algoid ne ""} { + set algoid [oid $algoid] + } + set pubkey [CryptExportPublicKeyInfoEx $hprov \ + [_crypt_keyspec $keyspec] \ + 0x10001 \ + $algoid \ + 0] + if {$encoding eq "native"} { + return $pubkey + } + # Generate SubjectPublicKeyInfo + set der [CryptEncodeObjectEx 8 $pubkey] + if {$encoding eq "der"} { + return $der + } + # 0x80000001 -> No CR (only LF) and headers + return "-----BEGIN PUBLIC KEY-----\n[CryptBinaryToString $der 0x80000001]-----END PUBLIC KEY-----\n" +} + +# For back compat - undocumented +proc twapi::crypt_public_key {hcrypt algid oid} { + return [crypt_public_key_export $hcrypt $algid -encoding native -algoid $oid] +} + +proc twapi::crypt_get_security_descriptor {hprov} { + return [CryptGetProvParam $hprov 8 7] +} + +proc twapi::crypt_set_security_descriptor {hprov secd} { + CryptSetProvParam $hprov 8 $secd +} + +proc twapi::crypt_key_container_name {hprov} { + return [CryptGetProvParam $hprov 6 0] +} + +proc twapi::crypt_key_container_unique_name {hprov} { + return [CryptGetProvParam $hprov 36 0] +} + +proc twapi::crypt_csp {hprov} { + return [CryptGetProvParam $hprov 4 0] +} + +proc twapi::csps {} { + set i 0 + set result {} + while {[llength [set csp [::twapi::CryptEnumProviders $i]]]} { + lappend result [lreplace $csp 0 0 [_csp_type_id_to_name [lindex $csp 0]]] + incr i + } + return $result +} +interp alias {} twapi::crypt_csps {} twapi::csps + +proc twapi::crypt_csp_type {hprov} { + return [_csp_type_id_to_name [CryptGetProvParam $hprov 16 0]] +} + +proc twapi::csp_types {} { + set i 0 + set result {} + while {[llength [set csptype [::twapi::CryptEnumProviderTypes $i]]]} { + lappend result [lreplace $csptype 0 0 [_csp_type_id_to_name [lindex $csptype 0]]] + incr i + } + return $result +} +interp alias {} twapi::crypt_csptypes {} twapi::csp_types + +proc twapi::crypt_key_container_names {hcrypt} { + return [CryptGetProvParam $hcrypt 2 0] +} + +proc twapi::crypt_session_key_size {hcrypt} { + return [CryptGetProvParam $hcrypt 20 0] +} + +proc twapi::crypt_keyx_keysize_increment {hcrypt} { + return [CryptGetProvParam $hcrypt 35 0] +} + +proc twapi::crypt_sig_keysize_increment {hcrypt} { + return [CryptGetProvParam $hcrypt 34 0] +} + +# TBD - Doc and test +proc twapi::crypt_admin_pin {hcrypt} { + return [CryptGetProvParam $hcrypt 31 0] +} + +# TBD - Doc and test +proc twapi::crypt_keyx_pin {hcrypt} { + return [CryptGetProvParam $hcrypt 32 0] +} + +# TBD - Doc and test +proc twapi::crypt_sig_pin {hcrypt} { + return [CryptGetProvParam $hcrypt 33 0] +} + +proc twapi::crypt_csp_version {hcrypt} { + set ver [CryptGetProvParam $hcrypt 5 0] + return [format %d.%d [expr {($ver & 0xff00)>>8}] [expr {$ver & 0xff}]] +} + +proc twapi::crypt_keyset_type {hcrypt} { + return [expr {[CryptGetProvParam $hcrypt 27 0] & 0x20 ? "machine" : "user"}] +} + +proc twapi::crypt_key_specifiers {hcrypt} { + set keyspec [CryptGetProvParam $hcrypt 39 0] + set keyspecs {} + if {$keyspec & 1} { + lappend keyspecs keyexchange + } + if {$keyspec & 2} { + lappend keyspecs signature + } + return $keyspecs +} + +proc twapi::crypt_symmetric_key_size {hcrypt} { + return [CryptGetProvParam $hcrypt 19 0] +} + +proc twapi::capi_key_export {hkey blob_type args} { + parseargs args { + {wrapper.arg NULL} + {v3.bool 0 0x80} + {oeap.bool 0 0x40} + {destroy.bool 0 0x04} + } -setvars -maxleftover 0 + + return [CryptExportKey $hkey $wrapper [_capi_keyblob_type_id $blob_type] [expr {$v3|$oeap}]] +} +interp alias {} twapi::crypt_export_key {} twapi::capi_key_export + + +proc twapi::crypt_import_key {hcrypt keyblob args} { + parseargs args { + {wrapper.arg NULL} + {exportable.bool 1 0x01} + {oaep.bool 0 0x40} + {userprotected.bool 0 0x02} + {ipsechmac.bool 0 0x100} + } -setvars -maxleftover 0 + return [CryptImportKey $hcrypt $keyblob $wrapper \ + [expr {$exportable|$oaep|$userprotected|$ipsechmac}]] +} +interp alias {} twapi::capi_key_import {} twapi::crypt_import_key + +proc twapi::crypt_derive_key {hcrypt algid passphrase args} { + parseargs args { + {size.int 0} + {exportable.bool 1 0x01} + {prf.arg sha1} + {method.arg pbkdf2} + {iterations.int 100000} + {salt.arg ""} + } -maxleftover 0 -setvars + + if {$method eq "pbkdf2"} { + set algnum [capi_algid $algid] + if {$size == 0} { + # Need to figure out the default key size for the algorithm + # The loop below does not work for des/3des/3des_112 because + # it will get the actual key size whereas CryptImportKey + # wants key size with pad/parity bits. So hardcode these + if {$algnum == 0x6601} { + set size 64; # + } elseif {$algnum == 0x6603} { + set size 192; # 3des + } elseif {$algnum == 0x6609} { + set size 128; # 3des_112 + } else { + foreach alg [crypt_algorithms $hcrypt] { + if {[dict get $alg algid] == $algnum} { + set size [dict get $alg defkeylen] + break + } + } + } + if {$size == 0} { + error "Could not figure out default key size for algorithm $algid. Please use the -size option." + } + } + set pbkdf2 [PBKDF2 $passphrase $size [capi_algid $prf] $salt $iterations] + set keyblob [list 0 2 0 $algnum $pbkdf2] + return [crypt_import_key $hcrypt $keyblob -exportable $exportable] + } else { + if {$size < 0 || $size > 65535} { + # Key size of 0 is default. Else it must be within 1-65535 + badargs! "Option -size value \"$size\" is not between 0 and 65535." + } + set hhash [capi_hash_create $hcrypt [capi_algid $method]] + twapi::trap { + capi_hash_password $hhash $passphrase + return [CryptDeriveKey $hcrypt [capi_algid $algid] $hhash \ + [expr {($size << 16) | $exportable}]] + } finally { + capi_hash_free $hhash + } + } +} + +proc twapi::pbkdf2 {pass nbits alg_id salt niters} { + return [PBKDF2 $pass $nbits [capi_algid $alg_id] $salt $niters] +} + + +proc twapi::capi_encrypt_bytes {bytes hkey args} { + variable _capi_encrypt_partials + parseargs args { + {hhash.arg NULL} + {final.bool 1} + {pad.arg oaep {oaep pkcs1}} + } -setvars -maxleftover 0 + + if {[dict exists $_capi_encrypt_partials $hkey Data]} { + append plaintext \ + [dict get $_capi_encrypt_partials $hkey Data] \ + $bytes + } else { + set plaintext $bytes + } + + if {$final} { + dict unset _capi_encrypt_partials $hkey + return [CryptEncrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40} $pad] $plaintext] + } + + # If not the final segment, we have to split it up into the block size multiple. + if {[dict exists $_capi_encrypt_partials $hkey Blocklen]} { + set blocklen [dict get $_capi_encrypt_partials $hkey Blocklen] + } else { + set blocklen [expr {[capi_key_blocklen $hkey] / 8}]; # Bits -> bytes + } + + # len is largest multiple of block size less than data length + set len [expr {([string length $plaintext] / $blocklen) * $blocklen}] + set enc [CryptEncrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40} $pad] [string range $plaintext 0 $len-1]] + # Note following will not happen if CryptEncrypt throws an error. As desired + set remain [string range $plaintext $len end] + if {[string length $remain]} { + # Remember additional data + dict set _capi_encrypt_partials $hkey Data $remain + dict set _capi_encrypt_partials $hkey Blocklen $blocklen + } else { + dict unset _capi_encrypt_partials $hkey + } + + return $enc +} + +proc twapi::capi_encrypt_string {s hkey args} { + # Explicitly parse args, not just pass on because this command + # does not support -final for symmetry with capi_decrypt_string + parseargs args { + {hhash.arg NULL} + {pad.arg oaep {oaep pkcs1}} + } -setvars -maxleftover 0 + return [capi_encrypt_bytes [encoding convertto utf-8 $s] $hkey -hhash $hhash -pad $pad] +} + +proc twapi::capi_decrypt_bytes {bytes hkey args} { + variable _capi_decrypt_partials + parseargs args { + {pad.arg oaep {oaep pkcs1 nopadcheck}} + {final.bool 1} + {hhash.arg NULL} + } -setvars -maxleftover 0 + + if {[dict exists $_capi_decrypt_partials $hkey Data]} { + append enc \ + [dict get $_capi_decrypt_partials $hkey Data] \ + $bytes + } else { + set enc $bytes + } + + if {$final} { + dict unset _capi_decrypt_partials $hkey + return [CryptDecrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40 nopadcheck 0x20} $pad] $enc] + } + + # If not the final segment, we have to split it up into the block size multiple. + if {[dict exists $_capi_decrypt_partials $hkey Blocklen]} { + set blocklen [dict get $_capi_decrypt_partials $hkey Blocklen] + } else { + set blocklen [expr {[capi_key_blocklen $hkey] / 8}]; # Bits -> bytes + } + + # len is largest multiple of block size less than data length + set len [expr {([string length $enc] / $blocklen) * $blocklen}] + set plaintext [CryptDecrypt $hkey $hhash $final [dict! {pkcs1 0 oaep 0x40} $pad] [string range $enc 0 $len-1]] + # Note following will not happen if CryptDecrypt throws an error. As desired + set remain [string range $enc $len end] + if {[string length $remain]} { + # Remember additional data + dict set _capi_decrypt_partials $hkey Data $remain + dict set _capi_decrypt_partials $hkey Blocklen $blocklen + } else { + dict unset _capi_decrypt_partials $hkey + } + + return $plaintext +} + +proc twapi::capi_decrypt_string {s hkey args} { + # Explicitly parse args, not just pass on because this command + # does not support -final for symmetry with capi_decrypt_string + parseargs args { + {hhash.arg NULL} + {pad.arg oaep {oaep pkcs1}} + } -setvars -maxleftover 0 + return [encoding convertfrom utf-8 [capi_decrypt_bytes $s $hkey -hhash $hhash -pad $pad]] +} + +# Returns the most capable CSP +proc twapi::_crypt_acquire_default {} { + if {[catch {crypt_acquire -csptype prov_rsa_aes} hcrypt] && + [catch {crypt_acquire -csptype prov_rsa_full -csp {Microsoft Enhanced Cryptographic Provider v1.0}} hcrypt]} { + set hcrypt [crypt_acquire] + } + set cspname [crypt_csp $hcrypt] + set csptype [crypt_csp_type $hcrypt] + # Redefine ourselves for next call + proc [namespace current]::_crypt_acquire_default {} "crypt_acquire -csp {$cspname} -csptype $csptype" + return $hcrypt +} + +proc twapi::_block_cipher {algo direction bytes keybytes args} { + + # Note: padding mode is not documented since MS providers only support + # one mode anyway + parseargs args { + mode.arg + iv.arg + padding.arg + } -setvars -maxleftover 0 + + set hcrypt [_crypt_acquire_default] + try { + set hkey [crypt_import_key $hcrypt [capi_keyblob_concealed $algo $keybytes]] + if {[info exists mode]} { + capi_key_mode $hkey $mode + } + if {[info exists iv]} { + capi_key_iv $hkey $iv + } + if {$direction eq "encrypt"} { + if {[info exists padding]} { + capi_key_padding $hkey $padding + } + set ciphertext [capi_encrypt_bytes $bytes $hkey] + } else { + set ciphertext [capi_decrypt_bytes $bytes $hkey] + } + } finally { + if {[info exists hkey]} { + capi_key_free $hkey + } + crypt_free $hcrypt + } + return $ciphertext +} + +# apply to avoid global variable pollution +apply {{} { + foreach {algo blocklen} {des 8 3des 8 aes_128 16 aes_192 16 aes_256 16} { + namespace eval twapi::$algo {} + interp alias {} twapi::${algo}::encrypt {} twapi::_block_cipher $algo encrypt + interp alias {} twapi::${algo}::decrypt {} twapi::_block_cipher $algo decrypt + interp alias {} twapi::${algo}::iv {} twapi::random_bytes $blocklen + namespace eval twapi::$algo { + namespace export encrypt decrypt iv + namespace ensemble create + } + } +}} + +### +# PKCS7 commands + +proc twapi::pkcs7_encrypt {bytes recipients encalg args} { + parseargs args { + {encoding.arg pem {pem der}} + {innertype.arg 0} + } -setvars -maxleftover 0 + + # TBD - add support for the following + set flags 0 + set encauxinfo {} + + set params [list \ + 0x10001 \ + NULL \ + [_make_algorithm_identifier $encalg] \ + $encauxinfo \ + $flags \ + $innertype] + return [_as_pem_or_der [CryptEncryptMessage $params $recipients $bytes] PKCS7 $encoding] +} + +proc twapi::pkcs7_decrypt {bytes stores args} { + parseargs args { + {encoding.arg {} {der pem {}}} + {silent.bool 0 0x40} + {certvar.arg ""} + } -maxleftover 0 -setvars + + set params [list \ + 0x10001 \ + $stores \ + $silent] + if {$certvar ne ""} { + upvar 1 $certvar hcert + set certvar hcert + } + + return [CryptDecryptMessage $params [_pem_decode $bytes $encoding] $certvar] +} + +proc twapi::pkcs7_sign {bytes hcert hashalg args} { + # TBD - document crls? + parseargs args { + {detached.bool 0} + {encoding.arg pem {pem der}} + {includecerts.arg all {none leaf all}} + {silent.bool 0 0x40} + {usesignerkeyid.bool 0 0x4} + {crls.arg {}} + {innercontenttype.arg 0} + } -setvars -maxleftover 0 + + set flags [expr {$usesignerkeyid | $silent}] + + switch -exact -- $includecerts { + leaf { set certs [list [cert_duplicate $hcert]] } + none { set certs {} } + all { set certs [cert_ancestors $hcert] } + } + # TBD - add support for the following + set hashaux {} + set authattrs {} + set unauthattrs {} + set encalg "" + set hashencaux "" + # 0x10001 -> PKCS_7_ASN_ENCODING|X509_ASN_ENCODING + set params [list \ + 0x10001 \ + $hcert \ + [_make_algorithm_identifier $hashalg] \ + $hashaux \ + $certs \ + $crls \ + $authattrs \ + $unauthattrs \ + $flags \ + $innercontenttype \ + $encalg \ + $hashencaux] + trap { + return [_as_pem_or_der [CryptSignMessage $params $detached [list $bytes]] PKCS7 $encoding] + } finally { + foreach c $certs { + cert_release $c + } + } +} + +proc twapi::pkcs7_verify {bytes args} { + parseargs args { + {encoding.arg {} {der pem {}}} + {contentvar.arg ""} + {certvar.arg ""} + } -maxleftover 0 -setvars -ignoreunknown + + if {$contentvar ne ""} { + upvar 1 $contentvar content + set contentvar content + } + set status [CryptVerifyMessageSignature [list 0x10001 NULL] 0 [_pem_decode $bytes $encoding] $contentvar hcert] + if {$status == 0} { + trap { + set status [cert_verify $hcert base {*}$args] + if {$status eq "ok"} { + if {$certvar ne ""} { + upvar 1 $certvar cert + set cert $hcert + unset hcert; # So we do not release it below + } + if {$contentvar ne ""} { + upvar 1 $contentvar con + set con content + } + } + } finally { + if {[info exists hcert]} { + cert_release $hcert + } + } + } else { + # Note these codes are different from those in _map_cert_verify_error + if {$status == 0x80090006} { + set status "signature" + } elseif {$status == 0x80090008} { + set status "invalidalgorithm" + } + } + + return $status +} + + +# For backwards compat - deprecated +interp alias {} twapi::crypt_key_free {} twapi::capi_key_free + +proc twapi::crypt_algorithms {hcrypt} { + set algs {} + foreach alg [CryptGetProvParam $hcrypt 22 0] { + lassign $alg algid defaultlen minlen maxlen protos name description + set protos [_make_symbolic_bitmask $protos { + ipsec 0x10 pct1 0x01 signing 0x20 ssl2 0x02 ssl3 0x04 tls1 0x08 + }] + lappend algs [list algid $algid defkeylen $defaultlen minkeylen $minlen maxkeylen $maxlen protocols $protos name $name description $description] + } + return $algs +} + +proc twapi::crypt_implementation_type {hcrypt} { + return [dict* {1 hardware 2 software 3 mixed 4 unknown 8 removable} [CryptGetProvParam $hcrypt 3 0]] +} + +proc twapi::capi_algid {s} { + if {[string is integer -strict $s]} { + return [expr {$s}]; # Return in decimal form + } + set algid [dict* { + 3des 0x00006603 + 3des_112 0x00006609 + aes 0x00006611 + aes_128 0x0000660e + aes_192 0x0000660f + aes_256 0x00006610 + agreedkey_any 0x0000aa03 + keyexchange 1 + signature 2 + cylink_mek 0x0000660c + des 0x00006601 + desx 0x00006604 + dh_ephem 0x0000aa02 + dh_sf 0x0000aa01 + dss_sign 0x00002200 + ecdh 0x0000aa05 + ecdsa 0x00002203 + ecmqv 0x0000a001 + hash_replace_owf 0x0000800b + hughes_md5 0x0000a003 + hmac 0x00008009 + kea_keyx 0x0000aa04 + mac 0x00008005 + md2 0x00008001 + md4 0x00008002 + md5 0x00008003 + no_sign 0x00002000 + pct1_master 0x00004c04 + rc2 0x00006602 + rc4 0x00006801 + rc5 0x0000660d + rsa_keyx 0x0000a400 + rsa_sign 0x00002400 + schannel_enc_key 0x00004c07 + schannel_mac_key 0x00004c03 + schannel_master_hash 0x00004c02 + sha 0x00008004 + sha1 0x00008004 + sha_256 0x0000800c + sha_384 0x0000800d + sha_512 0x0000800e + ssl2_master 0x00004c05 + ssl3_master 0x00004c01 + ssl3_shamd5 0x00008008 + tls1_master 0x00004c06 + tls1prf 0x0000800a + } $s ""] + + if {$algid eq ""} { + set oid [oid $s] + set algid [CertOIDToAlgId $oid] + if {$algid == 0} { + error "Could not map \"$s\" to algorithm id" + } + } + # Return the decimal form + return [expr {$algid}] +} + +# TBD - document +proc twapi::crypt_find_oid_info {key args} { + array set opts [parseargs args { + {restrict.arg any {sign encrypt any}} + keylen.int + {searchds.bool 0} + {oidgroup.arg 0} + } -maxleftover 0] + + # We will try key to be an OID, Alg Id, sign id or a simple + # name in turn + if {[catch { + set key [oid $key] + set keytype 1; # OID + }]} { + if {[catch { + set key [capi_algid $key] + set keytype 3; # Alg Id + }]} { + if {[catch { + # Sign - list of two alg id's + if {[llength $key] == 2} { + set key [list [capi_algid [lindex $key 0]] [capi_algid [lindex $key 1]]] + set keytype 4 + } else { + set keytype 2 ;# Name + } + }]} { + set keytype 2 ;# Name + } + } + } + + set oidgroup [oidgroup $opts(oidgroup)] + if {$opts(restrict) ne "any"} { + if {$oidgroup != 0 && $oidgroup != 3} { + error "The -restrict option can only be used with the oidgroup_pubkey_alg OID group" + } + if {$opts(restrict) eq "sign"} { + set keytype [expr {$keytype | 0x80000000}] + } else { + set keytype [expr {$keytype | 0x40000000}] + } + } + + if {[info exists opts(keylen)]} { + set oidgroup [expr {$oidgroup | ($opts(keylen) << 16)}] + } + + # Because search of active dir can be slow, turn it off unless + # caller explicitly requests it + if {! $opts(searchds)} { + set oidgroup [expr {$oidgroup | 0x80000000}] + } + + return [CryptFindOIDInfo $keytype $key $oidgroup] +} + +# TBD - document +proc twapi::crypt_enumerate_oid_info {{oidgroup 0}} { + # TBD - parse extra based on OID group + set ret {} + foreach info [CryptEnumOIDInfo [oidgroup $oidgroup]] { + lappend ret [twine {oid name oidgroup value extra} $info] + } + return $ret +} + +# TBD - test +proc twapi::_capi_parse {type arg args} { + parseargs args { + {contenttype.arg any} + {formattype.arg any} + {typesonly.bool 0} + } -setvars -maxleftover 0 + + # First try the formats not supported by CryptQueryObject + if {$contenttype in {any rsapublickey subjectpublickeyinfo}} { + if {$formattype eq "binary"} { + set encoding der + } elseif {$formattype eq "base64"} { + set encoding pem + } else { + set encoding "" + } + if {$type == 1} { + # arg is a file + set fd [open $arg] + trap { + fconfigure $fd -translation binary + set content [_pem_decode [read $fd] $encoding] + set is_pem [_is_pem $content] + } finally { + close $fd + } + } + if {$contenttype in {any subjectpublickeyinfo}} { + trap { + set data [CryptDecodeObjectEx 8 $content] + dict set ret contenttype subjectpublickeyinfo + dict set ret formattype [lindex {binary base64} $is_pem] + if {! $typesonly} { + dict set ret subjectpublickeyinfo $data + } + return $ret + } onerror {} { + if {$contenttype eq "subjectpublickeyinfo"} { + rethrow + } + # Go on to try other types + } + } + if {$contenttype in {any rsapublickey}} { + trap { + set data [CryptDecodeObjectEx 19 $content] + dict set ret contenttype rsapublickey + dict set ret formattype [lindex {binary base64} $is_pem] + if {! $typesonly} { + dict set ret rsapublickey $data + } + return $ret + } onerror {} { + if {$contenttype eq "rsapublickey"} { + rethrow + } + # Go on to try other types + } + } + } + + # No joy. Go on to try CryptQueryObject + + # Note - CERT_QUERY_CONTENT_FLAG_PFX_AND_LOAD not supported + # on XP/2k3 hence not included in expected_content_type + set contenttype [dict! { + cert 2 + ctl 4 + crl 8 + serializedstore 16 + serializedcert 32 + serializedctl 64 + serializedcrl 128 + pkcs7signed 256 + pkcs7unsigned 512 + pkcs7signedembed 1024 + pkcs10 2048 + pfx 4096 + certpair 8192 + any 0x3FFE + } $contenttype] + + set formattype [dict! { + binary 2 + base64 4 + asn1hex 8 + any 14 + } $formattype] + + set ret [CryptQueryObject $type $arg \ + $contenttype $formattype 0 $typesonly] + # We don't mention PKCS7_ASN v/s X509_ASN anywhere and use encoding + # to refer to PEM/DER so leave it off for now + dict unset ret encoding + dict set ret formattype [dict* { + 1 binary + 2 base64 + 3 asn1hex + } [dict get $ret formattype]] + dict set ret contenttype [dict* { + 1 cert + 2 ctl + 3 crl + 4 serializedstore + 5 serializedcert + 6 serializedctl + 7 serializedcrl + 8 pkcs7signed + 9 pkcs7unsigned + 10 pkcs7signedembed + 11 pkcs10 + 12 pfx + 13 certpair + } [dict get $ret contenttype]] + + return $ret +} +interp alias {} twapi::capi_parse_file {} twapi::_capi_parse 1 +interp alias {} twapi::capi_parse {} twapi::_capi_parse 2 + +### +# ASN.1 procs + +# TBD - document +proc twapi::asn1_decode_string {bin} { + # 24 -> X509_UNICODE_ANY_STRING + return [lindex [twapi::CryptDecodeObjectEx 24 $bin] 1] +} + +# TBD - document +proc twapi::asn1_encode_string {s {encformat utf8}} { + # 24 -> X509_UNICODE_ANY_STRING + return [twapi::CryptEncodeObjectEx 24 [list [dict! { + numeric 3 printable 4 teletex 5 t61 5 videotex 6 ia5 7 graphic 8 + visible 9 iso646 9 general 10 universal 11 int4 11 + bmp 12 unicode 12 utf8 13 + } $encformat] $s]] +} + +### +# Key procs + +proc twapi::_capi_key_param {param_id hkey args} { + if {[llength $args] == 0} { + return [CryptGetKeyParam $hkey $param_id] + } + if {[llength $args] == 1} { + return [CryptSetKeyParam $hkey $param_id [lindex $args 0]] + } + badargs! "Invalid syntax. Should be [lindex [info level -1] 0] HKEY ?VALUE?" 3 +} + +proc twapi::capi_key_iv {args} {return [_capi_key_param 1 {*}$args]} +proc twapi::capi_key_mode_bits {args} {return [_capi_key_param 5 {*}$args]} +proc twapi::capi_key_dss_p {args} {return [_capi_key_param 11 {*}$args]} +proc twapi::capi_key_dss_q {args} {return [_capi_key_param 13 {*}$args]} +proc twapi::capi_key_dss_g {args} {return [_capi_key_param 12 {*}$args]} +proc twapi::capi_key_effective_keylen {args} {return [_capi_key_param 19 {*}$args]} + +proc twapi::capi_key_blocklen {hkey} {return [CryptGetKeyParam $hkey 8]} +proc twapi::capi_key_certificate {hkey} {return [CryptGetKeyParam $hkey 26]} +proc twapi::capi_key_keylen {hkey} {return [CryptGetKeyParam $hkey 9]} + +proc twapi::capi_key_algid {hkey args} { + if {[llength $args] == 0} { + return [CryptGetKeyParam $hkey 7] + } + set args [lassign $args algid] + set algid [capi_algid $algid] + array set opts [parseargs args { + {archivable.bool 0 0x4000} + {salt.bool 0 4} + {exportable.bool 0 1} + {pregen.bool 0x40} + {userprotected.bool 0 2} + {nosalt40.bool 0 0x10} + {size.int 0} + } -maxleftover 0] + if {$opts(size) < 0 || $opts(size) > 65535} { + badargs! "Bad key size value '$size': must be positive integer less than 65536" + } + set flags [expr {($opts(size) << 16) | $opts(archivable) | $opts(salt) | $opts(exportable) | $opts(pregen) | $opts(userprotected) | $opts(nosalt40)}] + return [CryptSetKeyParam $hkey 7 $algid $flags] +} + +proc twapi::capi_key_mode {hkey args} { + if {[llength $args] == 0} { + return [dict* {1 cbc 2 ecb 3 ofb 4 cfb 5 cts} [CryptGetKeyParam $hkey 4]] + } + if {[llength $args] == 1} { + set val [dict* {cbc 1 ecb 2 ofb 3 cfb 4 cts 5} [lindex $args 0]] + return [CryptSetKeyParam $hkey 4 $val] + } + badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" +} + +proc twapi::capi_key_padding {hkey args} { + if {[llength $args] == 0} { + return [dict* {1 pkcs5 2 random 3 zeroes} [CryptGetKeyParam $hkey 3]] + } + if {[llength $args] == 1} { + set val [dict* {pkcs5 1 random 2 zeroes 3} [lindex $args 0]] + return [CryptSetKeyParam $hkey 3 $val] + } + badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" +} + +proc twapi::capi_key_permissions {hkey args} { + set bitmasks { + encrypt 0x01 decrypt 0x02 export 0x04 read 0x08 write 0x10 + mac 0x20 export_key 0x40 import_key 0x80 archive 0x100 + } + if {[llength $args] == 0} { + return [_make_symbolic_bitmask [CryptGetKeyParam $hkey 6] $bitmasks] + } + if {[llength $args] == 1} { + set val [_parse_symbolic_bitmask [lindex $args 0] $bitmasks] + return [CryptSetKeyParam $hkey 6 $val] + } + badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" +} + +proc twapi::capi_key_salt {hkey args} { + if {[llength $args] == 0} { + # 2 -> KP_SALT + return [CryptGetKeyParam $hkey 2] + } + if {[llength $args] == 1} { + # 10 -> KP_SALT_EX + return [CryptSetKeyParam $hkey 10 [lindex $args 0]] + } + badargs! "Invalid syntax. Should be [lindex [info level 0] 0] HKEY ?VALUE?" +} + +proc twapi::capi_keyblob_create {ver algid blob_type key} { + # 0 -> reserved field + return [list [_capi_keyblob_type_id $blob_type] $ver 0 [capi_algid $algid] $key] +} + +proc twapi::capi_keyblob_concealed {algid concealed_key} { + # 2 -> bVersion + # 0 -> concealed plaintextkeyblob + # Note: for our own home grown concealed type there is no + # BLOBHEADER + return [capi_keyblob_create 2 $algid concealed $concealed_key] +} + +proc twapi::capi_keyblob_plaintext {algid binkey} { + # typedef struct _PUBLICKEYSTRUC { + # BYTE bType; + # BYTE bVersion; + # WORD reserved; + # ALG_ID aiKeyAlg; + # } BLOBHEADER; + # 2 -> bVersion + set algnum [capi_algid $algid] + set blob_type [_capi_keyblob_type_id plaintext] + set len [string length $binkey] + set blob "[binary format ccsii $blob_type 2 0 $algnum $len]$binkey" + return [capi_keyblob_create 2 $algid plaintext $blob] +} + +proc twapi::capi_keyblob_version {kblob} { + return [lindex $kblob 1] +} + +proc twapi::capi_keyblob_algid {kblob} { + return [lindex $kblob 3] +} + +proc twapi::capi_keyblob_type {kblob} { + return [_capi_keyblob_type_name [lindex $kblob 0]] +} + +proc twapi::capi_keyblob_blob {kblob} { + return [lindex $kblob 4] +} + +proc twapi::_capi_keyblob_type_id {name} { + set blob_type [dict* { + concealed 0 + keystate 12 + opaque 9 + plaintext 8 + privatekey 7 + publickey 6 + publickeyex 10 + rfc3217 11 + simple 1 + } $name] +} + +proc twapi::_capi_keyblob_type_name {id} { + set blob_type [dict* { + 0 concealed + 1 simple + 6 publickey + 7 privatekey + 8 plaintext + 9 opaque + 10 publickeyex + 11 rfc3217 + 12 keystate + } [incr id 0]]; # incr to convert hex etc. to decimal + +} + +### +# Utility procs + +proc twapi::_make_algorithm_identifier {oid {param {}}} { + if {[string length $oid] == 0} { + return "" + } + if {0} { + # TBD - what modes to default to ? + switch -exact -- $oid { +#define szOID_NIST_AES128_CBC "2.16.840.1.101.3.4.1.2" +#define szOID_NIST_AES192_CBC "2.16.840.1.101.3.4.1.22" +#define szOID_NIST_AES256_CBC "2.16.840.1.101.3.4.1.42" + +#// For the above Algorithms, the AlgorithmIdentifier parameters must be +#// present and the parameters field MUST contain an AES-IV: +#// +#// AES-IV ::= OCTET STRING (SIZE(16)) + +#// NIST AES WRAP Algorithms +#define szOID_NIST_AES128_WRAP "2.16.840.1.101.3.4.1.5" +#define szOID_NIST_AES192_WRAP "2.16.840.1.101.3.4.1.25" +#define szOID_NIST_AES256_WRAP "2.16.840.1.101.3.4.1.45" + des { set oid "oid_rsa_des_ede3_cbc" } + des { set oid "oid_oiwsec_descbc" } + aes128 { TBD } + aes192 { TBD } + aes256 { TBD } + rc2 { set oid "oid_rsa_rc2cbc" } + rc4 { set oid "oid_rsa_rc4" } + } + } + set oid [oid $oid] + if {[string length $param]} { + return [list $oid $param] + } else { + return [list $oid] + } +} + +twapi::proc* twapi::_cert_prop_id {prop} { + # Certificate property menomics + variable _cert_prop_name_id_map + array set _cert_prop_name_id_map { + key_prov_handle 1 + key_prov_info 2 + sha1_hash 3 + hash 3 + md5_hash 4 + key_context 5 + key_spec 6 + ie30_reserved 7 + pubkey_hash_reserved 8 + enhkey_usage 9 + ctl_usage 9 + next_update_location 10 + friendly_name 11 + pvk_file 12 + description 13 + access_state 14 + signature_hash 15 + smart_card_data 16 + efs 17 + fortezza_data 18 + archived 19 + key_identifier 20 + auto_enroll 21 + pubkey_alg_para 22 + cross_cert_dist_points 23 + issuer_public_key_md5_hash 24 + subject_public_key_md5_hash 25 + id 26 + date_stamp 27 + issuer_serial_number_md5_hash 28 + subject_name_md5_hash 29 + extended_error_info 30 + + renewal 64 + archived_key_hash 65 + auto_enroll_retry 66 + aia_url_retrieved 67 + authority_info_access 68 + backed_up 69 + ocsp_response 70 + request_originator 71 + source_location 72 + source_url 73 + new_key 74 + ocsp_cache_prefix 75 + smart_card_root_info 76 + no_auto_expire_check 77 + ncrypt_key_handle 78 + hcryptprov_or_ncrypt_key_handle 79 + + subject_info_access 80 + ca_ocsp_authority_info_access 81 + ca_disable_crl 82 + root_program_cert_policies 83 + root_program_name_constraints 84 + subject_ocsp_authority_info_access 85 + subject_disable_crl 86 + cep 87 + + sign_hash_cng_alg 89 + + scard_pin_id 90 + scard_pin_info 91 + } +} { + variable _cert_prop_name_id_map + + if {[string is integer -strict $prop]} { + return $prop + } + if {![info exists _cert_prop_name_id_map($prop)]} { + badargs! "Unknown certificate property id '$prop'" 3 + } + + return $_cert_prop_name_id_map($prop) +} + +twapi::proc* twapi::_cert_prop_name {id} { + variable _cert_prop_name_id_map + variable _cert_prop_id_name_map + + _cert_prop_id key_prov_handle; # Just to init _cert_prop_name_id_map + array set _cert_prop_id_name_map [swapl [array get _cert_prop_name_id_map]] +} { + variable _cert_prop_id_name_map + if {[info exists _cert_prop_id_name_map($id)]} { + return $_cert_prop_id_name_map($id) + } + if {[string is integer -strict $id]} { + return $id + } + badargs! "Unknown certificate property id '$id'" 3 +} + +twapi::proc* twapi::_system_store_id {name} { + variable _system_store_locations + + set _system_store_locations { + service 0x40000 + "" 0x10000 + user 0x10000 + usergrouppolicy 0x70000 + localmachine 0x20000 + localmachineenterprise 0x90000 + localmachinegrouppolicy 0x80000 + services 0x50000 + users 0x60000 + } + + foreach loc [CertEnumSystemStoreLocation 0] { + dict set _system_store_locations {*}$loc + } +} { + variable _system_store_locations + + if {[string is integer -strict $name]} { + if {$name < 65536} { + badargs! "Invalid system store name $name" 3 + } + return $name + } + + return [dict! $_system_store_locations $name 2] +} + +twapi::proc* twapi::_csp_type_name_to_id prov { + variable _csp_type_name_id_map + + array set _csp_type_name_id_map { + prov_rsa_full 1 + prov_rsa_sig 2 + prov_dss 3 + prov_fortezza 4 + prov_ms_exchange 5 + prov_ssl 6 + prov_rsa_schannel 12 + prov_dss_dh 13 + prov_ec_ecdsa_sig 14 + prov_ec_ecnra_sig 15 + prov_ec_ecdsa_full 16 + prov_ec_ecnra_full 17 + prov_dh_schannel 18 + prov_spyrus_lynks 20 + prov_rng 21 + prov_intel_sec 22 + prov_replace_owf 23 + prov_rsa_aes 24 + } +} { + variable _csp_type_name_id_map + + set key [string tolower $prov] + + if {[info exists _csp_type_name_id_map($key)]} { + return $_csp_type_name_id_map($key) + } + + if {[string is integer -strict $prov]} { + return $prov + } + + badargs! "Invalid or unknown provider type '$prov'" 3 +} + +twapi::proc* twapi::_csp_type_id_to_name prov { + variable _csp_type_name_id_map + variable _csp_id_type_name_map + + _csp_type_name_to_id prov_rsa_full; # Just to ensure _csp_type_name_id_map exists + array set _csp_id_type_name_map [swapl [array get _csp_type_name_id_map]] +} { + variable _csp_id_type_name_map + if {[info exists _csp_id_type_name_map($prov)]} { + return $_csp_id_type_name_map($prov) + } + + if {[string is integer -strict $prov]} { + return $prov + } + + badargs! "Invalid or unknown CSP type id '$prov'" 3 +} + +twapi::proc* twapi::oid {name} { + variable _name_oid_map + if {![info exists _name_oid_map]} { + oids; # To init the map + } +} { + variable _name_oid_map + + if {[regexp {^\d+\.\d+(\.\d+)*$} $name]} { + return $name; # OID literal n.n... + } + if {[info exists _name_oid_map($name)]} { + return $_name_oid_map($name) + } + # Try by adding oid_ + if {[info exists _name_oid_map(oid_$name)]} { + return $_name_oid_map(oid_$name) + } + + badargs! "Invalid OID '$name'" + +} + +twapi::proc* twapi::oidname {oid} { + variable _oid_name_map + if {![info exists _oid_name_map]} { + oids; # To init the map + } +} { + variable _oid_name_map + + if {[info exists _oid_name_map($oid)]} { + return $_oid_name_map($oid) + } + if {[regexp {^\d([\d\.]*\d)?$} $oid]} { + return $oid + } else { + badargs! "Invalid OID '$oid'" + } +} + +# TBD - change OID mnemonics to those in RFC (see pki.tcl in tcllib) +twapi::proc* twapi::oids {{pattern *}} { + variable _oid_name_map + variable _name_oid_map + + # TBD - clean up table for rarely used OIDs + array set _name_oid_map { + oid_common_name "2.5.4.3" + oid_sur_name "2.5.4.4" + oid_device_serial_number "2.5.4.5" + oid_country_name "2.5.4.6" + oid_locality_name "2.5.4.7" + oid_state_or_province_name "2.5.4.8" + oid_street_address "2.5.4.9" + oid_organization_name "2.5.4.10" + oid_organizational_unit_name "2.5.4.11" + oid_title "2.5.4.12" + oid_description "2.5.4.13" + oid_search_guide "2.5.4.14" + oid_business_category "2.5.4.15" + oid_postal_address "2.5.4.16" + oid_postal_code "2.5.4.17" + oid_post_office_box "2.5.4.18" + oid_physical_delivery_office_name "2.5.4.19" + oid_telephone_number "2.5.4.20" + oid_telex_number "2.5.4.21" + oid_teletext_terminal_identifier "2.5.4.22" + oid_facsimile_telephone_number "2.5.4.23" + oid_x21_address "2.5.4.24" + oid_international_isdn_number "2.5.4.25" + oid_registered_address "2.5.4.26" + oid_destination_indicator "2.5.4.27" + oid_user_password "2.5.4.35" + oid_user_certificate "2.5.4.36" + oid_ca_certificate "2.5.4.37" + oid_authority_revocation_list "2.5.4.38" + oid_certificate_revocation_list "2.5.4.39" + oid_cross_certificate_pair "2.5.4.40" + + oid_rsa "1.2.840.113549" + oid_pkcs "1.2.840.113549.1" + oid_rsa_hash "1.2.840.113549.2" + oid_rsa_encrypt "1.2.840.113549.3" + + oid_pkcs_1 "1.2.840.113549.1.1" + oid_pkcs_2 "1.2.840.113549.1.2" + oid_pkcs_3 "1.2.840.113549.1.3" + oid_pkcs_4 "1.2.840.113549.1.4" + oid_pkcs_5 "1.2.840.113549.1.5" + oid_pkcs_6 "1.2.840.113549.1.6" + oid_pkcs_7 "1.2.840.113549.1.7" + oid_pkcs_8 "1.2.840.113549.1.8" + oid_pkcs_9 "1.2.840.113549.1.9" + oid_pkcs_10 "1.2.840.113549.1.10" + oid_pkcs_12 "1.2.840.113549.1.12" + + oid_rsa_rsa "1.2.840.113549.1.1.1" + oid_rsa_md2rsa "1.2.840.113549.1.1.2" + oid_rsa_md4rsa "1.2.840.113549.1.1.3" + oid_rsa_md5rsa "1.2.840.113549.1.1.4" + oid_rsa_sha1rsa "1.2.840.113549.1.1.5" + oid_rsa_setoaep_rsa "1.2.840.113549.1.1.6" + + oid_rsa_dh "1.2.840.113549.1.3.1" + + oid_rsa_data "1.2.840.113549.1.7.1" + oid_rsa_signeddata "1.2.840.113549.1.7.2" + oid_rsa_envelopeddata "1.2.840.113549.1.7.3" + oid_rsa_signenvdata "1.2.840.113549.1.7.4" + oid_rsa_digesteddata "1.2.840.113549.1.7.5" + oid_rsa_hasheddata "1.2.840.113549.1.7.5" + oid_rsa_encrypteddata "1.2.840.113549.1.7.6" + + oid_rsa_emailaddr "1.2.840.113549.1.9.1" + oid_rsa_unstructname "1.2.840.113549.1.9.2" + oid_rsa_contenttype "1.2.840.113549.1.9.3" + oid_rsa_messagedigest "1.2.840.113549.1.9.4" + oid_rsa_signingtime "1.2.840.113549.1.9.5" + oid_rsa_countersign "1.2.840.113549.1.9.6" + oid_rsa_challengepwd "1.2.840.113549.1.9.7" + oid_rsa_unstructaddr "1.2.840.113549.1.9.8" + oid_rsa_extcertattrs "1.2.840.113549.1.9.9" + oid_rsa_certextensions "1.2.840.113549.1.9.14" + oid_rsa_smimecapabilities "1.2.840.113549.1.9.15" + oid_rsa_prefersigneddata "1.2.840.113549.1.9.15.1" + + oid_rsa_smimealg "1.2.840.113549.1.9.16.3" + oid_rsa_smimealgesdh "1.2.840.113549.1.9.16.3.5" + oid_rsa_smimealgcms3deswrap "1.2.840.113549.1.9.16.3.6" + oid_rsa_smimealgcmsrc2wrap "1.2.840.113549.1.9.16.3.7" + + oid_rsa_md2 "1.2.840.113549.2.2" + oid_rsa_md4 "1.2.840.113549.2.4" + oid_rsa_md5 "1.2.840.113549.2.5" + + oid_rsa_rc2cbc "1.2.840.113549.3.2" + oid_rsa_rc4 "1.2.840.113549.3.4" + oid_rsa_des_ede3_cbc "1.2.840.113549.3.7" + oid_rsa_rc5_cbcpad "1.2.840.113549.3.9" + + + oid_ansi_x942 "1.2.840.10046" + oid_ansi_x942_dh "1.2.840.10046.2.1" + + oid_x957 "1.2.840.10040" + oid_x957_dsa "1.2.840.10040.4.1" + oid_x957_sha1dsa "1.2.840.10040.4.3" + + oid_ds "2.5" + oid_dsalg "2.5.8" + oid_dsalg_crpt "2.5.8.1" + oid_dsalg_hash "2.5.8.2" + oid_dsalg_sign "2.5.8.3" + oid_dsalg_rsa "2.5.8.1.1" + + oid_pkix_kp_server_auth "1.3.6.1.5.5.7.3.1" + oid_pkix_kp_client_auth "1.3.6.1.5.5.7.3.2" + oid_pkix_kp_code_signing "1.3.6.1.5.5.7.3.3" + oid_pkix_kp_email_protection "1.3.6.1.5.5.7.3.4" + oid_pkix_kp_ipsec_end_system "1.3.6.1.5.5.7.3.5" + oid_pkix_kp_ipsec_tunnel "1.3.6.1.5.5.7.3.6" + oid_pkix_kp_ipsec_user "1.3.6.1.5.5.7.3.7" + oid_pkix_kp_timestamp_signing "1.3.6.1.5.5.7.3.8" + oid_pkix_kp_ocsp_signing "1.3.6.1.5.5.7.3.9" + + oid_oiw "1.3.14" + + oid_oiwsec "1.3.14.3.2" + oid_oiwsec_md4rsa "1.3.14.3.2.2" + oid_oiwsec_md5rsa "1.3.14.3.2.3" + oid_oiwsec_md4rsa2 "1.3.14.3.2.4" + oid_oiwsec_desecb "1.3.14.3.2.6" + oid_oiwsec_descbc "1.3.14.3.2.7" + oid_oiwsec_desofb "1.3.14.3.2.8" + oid_oiwsec_descfb "1.3.14.3.2.9" + oid_oiwsec_desmac "1.3.14.3.2.10" + oid_oiwsec_rsasign "1.3.14.3.2.11" + oid_oiwsec_dsa "1.3.14.3.2.12" + oid_oiwsec_shadsa "1.3.14.3.2.13" + oid_oiwsec_mdc2rsa "1.3.14.3.2.14" + oid_oiwsec_sharsa "1.3.14.3.2.15" + oid_oiwsec_dhcommmod "1.3.14.3.2.16" + oid_oiwsec_desede "1.3.14.3.2.17" + oid_oiwsec_sha "1.3.14.3.2.18" + oid_oiwsec_mdc2 "1.3.14.3.2.19" + oid_oiwsec_dsacomm "1.3.14.3.2.20" + oid_oiwsec_dsacommsha "1.3.14.3.2.21" + oid_oiwsec_rsaxchg "1.3.14.3.2.22" + oid_oiwsec_keyhashseal "1.3.14.3.2.23" + oid_oiwsec_md2rsasign "1.3.14.3.2.24" + oid_oiwsec_md5rsasign "1.3.14.3.2.25" + oid_oiwsec_sha1 "1.3.14.3.2.26" + oid_oiwsec_dsasha1 "1.3.14.3.2.27" + oid_oiwsec_dsacommsha1 "1.3.14.3.2.28" + oid_oiwsec_sha1rsasign "1.3.14.3.2.29" + + oid_oiwdir "1.3.14.7.2" + oid_oiwdir_crpt "1.3.14.7.2.1" + oid_oiwdir_hash "1.3.14.7.2.2" + oid_oiwdir_sign "1.3.14.7.2.3" + oid_oiwdir_md2 "1.3.14.7.2.2.1" + oid_oiwdir_md2rsa "1.3.14.7.2.3.1" + + oid_infosec "2.16.840.1.101.2.1" + oid_infosec_sdnssignature "2.16.840.1.101.2.1.1.1" + oid_infosec_mosaicsignature "2.16.840.1.101.2.1.1.2" + oid_infosec_sdnsconfidentiality "2.16.840.1.101.2.1.1.3" + oid_infosec_mosaicconfidentiality "2.16.840.1.101.2.1.1.4" + oid_infosec_sdnsintegrity "2.16.840.1.101.2.1.1.5" + oid_infosec_mosaicintegrity "2.16.840.1.101.2.1.1.6" + oid_infosec_sdnstokenprotection "2.16.840.1.101.2.1.1.7" + oid_infosec_mosaictokenprotection "2.16.840.1.101.2.1.1.8" + oid_infosec_sdnskeymanagement "2.16.840.1.101.2.1.1.9" + oid_infosec_mosaickeymanagement "2.16.840.1.101.2.1.1.10" + oid_infosec_sdnskmandsig "2.16.840.1.101.2.1.1.11" + oid_infosec_mosaickmandsig "2.16.840.1.101.2.1.1.12" + oid_infosec_suiteasignature "2.16.840.1.101.2.1.1.13" + oid_infosec_suiteaconfidentiality "2.16.840.1.101.2.1.1.14" + oid_infosec_suiteaintegrity "2.16.840.1.101.2.1.1.15" + oid_infosec_suiteatokenprotection "2.16.840.1.101.2.1.1.16" + oid_infosec_suiteakeymanagement "2.16.840.1.101.2.1.1.17" + oid_infosec_suiteakmandsig "2.16.840.1.101.2.1.1.18" + oid_infosec_mosaicupdatedsig "2.16.840.1.101.2.1.1.19" + oid_infosec_mosaickmandupdsig "2.16.840.1.101.2.1.1.20" + oid_infosec_mosaicupdatedinteg "2.16.840.1.101.2.1.1.21" + } + + # OIDs for certificate extensions + array set _name_oid_map { + oid_authority_key_identifier_old "2.5.29.1" + oid_key_attributes "2.5.29.2" + oid_cert_policies_95 "2.5.29.3" + oid_key_usage_restriction "2.5.29.4" + oid_subject_alt_name_old "2.5.29.7" + oid_issuer_alt_name_old "2.5.29.8" + oid_basic_constraints_old "2.5.29.10" + oid_key_usage "2.5.29.15" + oid_privatekey_usage_period "2.5.29.16" + oid_basic_constraints "2.5.29.19" + + oid_cert_policies "2.5.29.32" + oid_any_cert_policy "2.5.29.32.0" + oid_inhibit_any_policy "2.5.29.54" + + oid_authority_key_identifier "2.5.29.35" + oid_subject_key_identifier "2.5.29.14" + oid_subject_alt_name2 "2.5.29.17" + oid_issuer_alt_name "2.5.29.18" + oid_crl_reason_code "2.5.29.21" + oid_reason_code_hold "2.5.29.23" + oid_crl_dist_points "2.5.29.31" + oid_enhanced_key_usage "2.5.29.37" + + oid_any_enhanced_key_usage "2.5.29.37.0" + + oid_crl_number "2.5.29.20" + oid_delta_crl_indicator "2.5.29.27" + oid_issuing_dist_point "2.5.29.28" + oid_freshest_crl "2.5.29.46" + oid_name_constraints "2.5.29.30" + + oid_policy_mappings "2.5.29.33" + oid_legacy_policy_mappings "2.5.29.5" + oid_policy_constraints "2.5.29.36" + } + + array set _oid_name_map [swapl [array get _name_oid_map]] +} { + variable _name_oid_map + return [array get _name_oid_map $pattern] +} + +# TBD - document +proc twapi::oidgroup {oidgroup} { + if {[string is integer -strict $oidgroup]} { + return $oidgroup + } + return [dict! { + oidgroup_hash_alg 1 + oidgroup_encrypt_alg 2 + oidgroup_pubkey_alg 3 + oidgroup_sign_alg 4 + oidgroup_rdn_attr 5 + oidgroup_ext_or_attr 6 + oidgroup_enhkey_usage 7 + oidgroup_policy 8 + oidgroup_template 9 + } $oidgroup] +} + +# TBD - document +proc twapi::oidgroup_token {oidgroup} { + return [lindex { + {} + oidgroup_hash_alg + oidgroup_encrypt_alg + oidgroup_pubkey_alg + oidgroup_sign_alg + oidgroup_rdn_attr + oidgroup_ext_or_attr + oidgroup_enhkey_usage + oidgroup_policy + oidgroup_template + } $oidgroup] +} + +proc twapi::_make_altnames_ext {altnames {critical 0} {issuer 0}} { + set names {} + foreach pair $altnames { + lassign $pair alttype altname + lappend names [list \ + [dict get { + other 1 + email 2 + dns 3 + directory 5 + url 7 + ip 8 + registered 9 + } $alttype] $altname] + } + + return [list [expr {$issuer ? "2.5.29.18" : "2.5.29.17"}] $critical $names] +} + +proc twapi::_get_enhkey_usage_oids {names} { + array set map [oids oid_pkix_kp_*] + + # We use an array to remove duplicates + array set oids {} + foreach name $names { + if {[info exists map($name)]} { + set oids($map($name)) 1 + } elseif {[info exists map(oid_pkix_kp_$name)]} { + set oids($map(oid_pkix_kp_$name)) 1 + } elseif {[regexp {^\d([\d\.]*\d)?$} $name]} { + # Any OID will do + set oids($name) 1 + } else { + error "Invalid Enhanced Key Usage OID \"$name\"" + } + } + return [array names oids] +} + +proc twapi::_make_enhkeyusage_ext {enhkeyusage {critical 0}} { + return [list "2.5.29.37" $critical [_get_enhkey_usage_oids $enhkeyusage]] +} + +twapi::proc* twapi::_init_keyusage_names {} { + variable _keyusage_byte1 + variable _keyusage_byte2 + set _keyusage_byte1 { + digital_signature 0x80 + non_repudiation 0x40 + key_encipherment 0x20 + data_encipherment 0x10 + key_agreement 0x08 + key_cert_sign 0x04 + crl_sign 0x02 + encipher_only 0x01 + } + set _keyusage_byte2 { + decipher_only 0x80 + } +} {} + +proc twapi::_make_basic_constraints_ext {basicconstraints {critical 1}} { + lassign $basicconstraints isca capathlenvalid capathlen + if {[string is boolean $isca] && [string is boolean $capathlenvalid] && + [string is integer -strict $capathlen] && $capathlen >= 0} { + return [list "2.5.29.19" $critical [list $isca $capathlenvalid $capathlen]] + } + error "Invalid basicconstraints value" +} + +proc twapi::_make_keyusage_ext {keyusage {critical 0}} { + variable _keyusage_byte1 + variable _keyusage_byte2 + + _init_keyusage_names + set byte1 0 + set byte2 0 + foreach usage $keyusage { + if {[dict exists $_keyusage_byte1 $usage]} { + set byte1 [expr {$byte1 | [dict get $_keyusage_byte1 $usage]}] + } elseif {[dict exists $_keyusage_byte2 $usage]} { + set byte2 [expr {$byte2 | [dict get $_keyusage_byte2 $usage]}] + } else { + error "Invalid key usage value \"$keyusage\"" + } + } + + set bin [binary format cc $byte1 $byte2] + # 7 -> # unused bits in last byte + return [list "2.5.29.15" $critical [list $bin 7]] +} + +# Given a byte array, decode to key usage flags +proc twapi::_cert_decode_keyusage {bin} { + variable _keyusage_byte1 + variable _keyusage_byte2 + + _init_keyusage_names + + binary scan $bin c* bytes + + if {[llength $bytes] == 0} { + return *; # Field not present, TBD + } + + set usages {} + set byte [lindex $bytes 0] + dict for {key val} $_keyusage_byte1 { + if {$byte & $val} { + lappend usages $key + } + } + + set byte [lindex $bytes 1] + dict for {key val} $_keyusage_byte2 { + if {$byte & $val} { + lappend usages $key + set byte [expr {$byte & ~$val}] + } + } + + if {0} { + # Commented out because some certificates seem to contain + # bits not defined by RF5280. Do not barf on these + + # For the second byte, not all bits are defined. Error if any + # that we do not understand + if {$byte} { + error "Key usage sequence $bytes includes unsupported bits" + } + + # If there are more bytes, they should all be 0 as well + foreach byte [lrange $bytes 2 end] { + if {$byte} { + error "Key usage sequence $bytes includes unsupported bits" + } + } + } + + return $usages +} + +proc twapi::_cert_decode_enhkey {vals} { + set result {} + set symmap [swapl [oids oid_pkix_kp_*]] + foreach val $vals { + if {[dict exists $symmap $val]} { + lappend result [string range [dict get $symmap $val] 12 end] + } else { + lappend result $val + } + } + return $result +} + +proc twapi::_cert_decode_extension {oid val} { + # TBD - see what other types need to be decoded + # 2.5.29.19 - basic constraints + # + switch $oid { + 2.5.29.15 { return [_cert_decode_keyusage $val] } + 2.5.29.37 { return [_cert_decode_enhkey $val] } + 2.5.29.17 - + 2.5.29.18 { + # TBD - replace with lmap for 8.6 + set names {} + foreach elem $val { + lappend names [list [dict* { + 1 other 2 email 3 dns 5 directory 7 url 8 ip 9 registered + } [lindex $elem 0]] [lindex $elem 1]] + } + return $names + } + } + return $val +} + +proc twapi::_crypt_keyspec {keyspec} { + return [dict* {keyexchange 1 signature 2} $keyspec] +} + +proc twapi::_cert_create_parse_options {optvals optsvar} { + upvar 1 $optsvar opts + + # TBD - add -issueraltnames + parseargs optvals { + start.arg + end.arg + serialnumber.arg + altnames.arg + enhkeyusage.arg + keyusage.arg + basicconstraints.arg + {purpose.arg {}} + {capathlen.int -1} + } -ignoreunknown -setvars + + set ca [expr {"ca" in $purpose}] + if {$ca} { + if {[info exists basicconstraints]} { + badargs! "Option -basicconstraints cannot be specified if \"ca\" is included in the -purpose option" + } + if {$capathlen < 0} { + set basicconstraints {{1 0 0} 1}; # No path length constraint + } else { + set basicconstraints [list [list 1 1 $capathlen] 1] + } + } else { + if {![info exists basicconstraints]} { + set basicconstraints {{0 0 0} 1} + } + } + set sslserver [expr {"server" in $purpose}] + set sslclient [expr {"client" in $purpose}] + + if {[info exists serialnumber]} { + if {$serialnumber <= 0 || $serialnumber > 0x7fffffffffffffff} { + badargs! "Serial number must be specified as a positive wide integer." + } + # Format as little endian + set opts(serialnumber) [binary format w $serialnumber] + } else { + # Generate 15 byte random and add high byte (little endian) + # to 0x01 to ensure it is treated as positive + set opts(serialnumber) "[random_bytes 15]\x01" + } + + # Validity period + if {[info exists start]} { + set opts(start) $start + } else { + set opts(start) [_seconds_to_timelist [clock seconds] 1] + } + if {[info exists end]} { + set opts(end) $end + } else { + set opts(end) $opts(start) + lset opts(end) 0 [expr {[lindex $opts(end) 0] + 1}] + # Ensure valid date (Feb 29 leap year -> non-leap year for example) + set opts(end) [clock format [clock scan [lrange $opts(end) 0 2] -format "%Y %N %e"] -format "%Y %N %e"] + lappend opts(end) 23 59 59 0 + } + + # Generate the extensions list + set exts {} + lappend exts [_make_basic_constraints_ext {*}$basicconstraints ] + if {$ca} { + lappend extra_keyusage key_cert_sign crl_sign + } + if {$sslserver || $sslclient} { + # TBD - not clear key_agreement is needed for SSL certs for + # either client or server. See + # https://access.redhat.com/documentation/en-us/red_hat_certificate_system/10/html/administration_guide/standard_x.509_v3_certificate_extensions + lappend extra_keyusage digital_signature key_encipherment key_agreement + if {$sslserver} { + lappend extra_enhkeyusage oid_pkix_kp_server_auth + } + if {$sslclient} { + lappend extra_enhkeyusage oid_pkix_kp_client_auth + } + } + + if {[info exists extra_keyusage]} { + if {[info exists keyusage]} { + # TBD - should it be marked critical or not ? + lset keyusage 0 [concat [lindex $keyusage 0] $extra_keyusage] + } else { + # TBD - should it be marked critical or not ? + set keyusage [list $extra_keyusage 1] + } + } + + if {[info exists keyusage]} { + lappend exts [_make_keyusage_ext {*}$keyusage] + } + + if {[info exists extra_enhkeyusage]} { + if {[info exists enhkeyusage]} { + # TBD - should it be marked critical or not ? + lset enhkeyusage 0 [concat [lindex $enhkeyusage 0] $extra_enhkeyusage] + } else { + # TBD - should it be marked critical or not ? + set enhkeyusage [list $extra_enhkeyusage 1] + } + } + if {[info exists enhkeyusage]} { + lappend exts [_make_enhkeyusage_ext {*}$enhkeyusage] + } + + if {[info exists altnames]} { + lappend exts [_make_altnames_ext {*}$altnames] + } + + set opts(extensions) $exts + + return $optvals +} + +proc twapi::_cert_add_parseargs {vargs} { + upvar 1 $vargs optvals + parseargs optvals { + {disposition.arg preserve {overwrite duplicate update preserve}} + } -maxleftover 0 -setvars + + # 4 -> CERT_STORE_ADD_ALWAYS + # 3 -> CERT_STORE_ADD_REPLACE_EXISTING + # 6 -> CERT_STORE_ADD_NEWER + # 1 -> CERT_STORE_ADD_NEW + + return [list disposition \ + [dict get { + duplicate 4 + overwrite 3 + update 6 + preserve 1 + } $disposition]] +} + +proc twapi::_parse_store_open_opts {optvals} { + array set opts [parseargs optvals { + {commitenable.bool 0 0x00010000} + {readonly.bool 0 0x00008000} + {existing.bool 0 0x00004000} + {create.bool 0 0x00002000} + {includearchived.bool 0 0x00000200} + {maxpermissions.bool 0 0x00001000} + {deferclose.bool 0 0x00000004} + {backupprivilege.bool 0 0x00000800} + } -maxleftover 0 -nulldefault] + + set flags 0 + foreach {opt val} [array get opts] { + incr flags $val + } + return $flags +} + +# Helper to return as der/pem based on encoding option +proc twapi::_as_pem_or_der {bin tag encoding} { + if {$encoding eq "pem"} { + # 1 -> CRYPT_STRING_BASE64 + # 0x80000000 -> LF-only, not CRLF + return "-----BEGIN $tag-----\n[CryptBinaryToString $bin 0x80000001]-----END $tag-----\n" + } else { + return $bin + } +} + +# Helper for converting input parameters if they are in PEM format +# pem_or_der is the data +# enc specifies the type of pem_or_der. If empty, we guess. +# pemtype should generally be +# 0 -> CRYPT_STRING_BASE64HEADER for certificates +# 1 -> CRYPT_STRING_BASE64 (no header) +# 3 -> CRYPT_STRING_BASE64REQUESTHEADER +# 6 -> CRYPT_STRING_BASE64_ANY (actually same as 0 or 1) +proc twapi::_pem_decode {pem_or_der enc {pemtype 6}} { + if {$enc eq "der"} { + return $pem_or_der + } + if {$enc eq "pem" || [_is_pem $pem_or_der]} { + return [CryptStringToBinary $pem_or_der $pemtype] + } + return $pem_or_der +} + +proc twapi::_is_pem {pem_or_der} { + return [regexp -nocase {^\s*-----\s*BEGIN\s+} $pem_or_der] +} + +# Utility proc to generate certs in a memory store - +# one self signed which is used to sign a client and a server cert +proc twapi::make_test_certs {{hstore {}} args} { + crypt_test_container_cleanup + + parseargs args { + {csp.arg {Microsoft Strong Cryptographic Provider}} + {csptype.arg prov_rsa_full} + unique + {duration.int 5} + } -maxleftover 0 -setvars + + set enddate [clock format [clock seconds] -format "%Y %N %e"] + lset enddate 0 [expr {[lindex $enddate 0]+$duration}] + # Ensure valid date e.g. Feb 29 non-leap year + set enddate [clock format [clock scan $enddate -format "%Y %N %e"] -format "%Y %N %e"] + + if {$unique} { + set uuid [twapi::new_uuid] + } else { + set uuid "" + } + + # Create the self signed CA cert + set container twapitestca$uuid + set crypt [twapi::crypt_acquire $container -csp $csp -csptype $csptype -create 1] + twapi::crypt_key_free [twapi::crypt_generate_key $crypt signature -exportable 1] + set ca_altnames [list [list [list email ${container}@twapitest.com] [list dns ${container}.twapitest.com] [list url http://${container}.twapitest.com] [list directory [cert_name_to_blob "CN=${container}altname"]] [list ip [binary format c4 {127 0 0 2}]]]] + set cert [twapi::cert_create_self_signed_from_crypt_context "CN=$container, C=IN, O=Tcl, OU=twapi" $crypt -purpose {ca} -altnames $ca_altnames -end $enddate] + if {[llength $hstore] == 0} { + set hstore [twapi::cert_temporary_store] + } + set ca_certificate [twapi::cert_store_add_certificate $hstore $cert] + twapi::cert_release $cert + twapi::cert_set_key_prov $ca_certificate $container signature -csp $csp -csptype $csptype + crypt_free $crypt + + # Create the client and server certs + foreach cert_type {intermediate server client altserver full min} { + set container twapitest${cert_type}$uuid + set subject $container + set crypt [twapi::crypt_acquire $container -csp $csp -csptype $csptype -create 1] + twapi::crypt_key_free [twapi::crypt_generate_key $crypt keyexchange -exportable 1] + switch $cert_type { + intermediate { + set req [cert_request_create "CN=$container, C=IN, O=Tcl, OU=twapi" $crypt keyexchange -purpose ca] + set signing_cert $ca_certificate + } + altserver { + # No COMMON name. Used for testing use of DNS altname + set altnames [list [list [list dns ${cert_type}.twapitest.com] [list dns ${cert_type}2.twapitest.com]]] + set req [cert_request_create "C=IN, O=Tcl, OU=twapi, OU=$container" $crypt keyexchange -purpose $cert_type -altnames $altnames] + set signing_cert $ca_certificate + } + client - + server { + set req [cert_request_create "CN=$container, C=IN, O=Tcl, OU=twapi" $crypt keyexchange -purpose $cert_type] + set signing_cert $intermediate_certificate + } + full { + set altnames [list [list [list email ${container}@twapitest.com] [list dns ${cert_type}.twapitest.com] [list url http://${container}.twapitest.com] [list directory [cert_name_to_blob "CN=${container}altname"]] [list ip [binary format c4 {127 0 0 1}]]]] + set req [cert_request_create \ + "CN=$container, C=IN, O=Tcl, OU=twapi" \ + $crypt keyexchange \ + -keyusage [list {crl_sign data_encipherment digital_signature key_agreement key_cert_sign key_encipherment non_repudiation} 1]\ + -enhkeyusage [list {client_auth code_signing email_protection ipsec_end_system ipsec_tunnel ipsec_user server_auth timestamp_signing ocsp_signing} 1] \ + -altnames $altnames] + set signing_cert $ca_certificate + } + min { + set req [cert_request_create "CN=$container" $crypt keyexchange] + set signing_cert $ca_certificate + } + } + crypt_free $crypt + set parsed_req [cert_request_parse $req] + set subject [dict get $parsed_req subject] + set pubkey [dict get $parsed_req pubkey] + set opts {} + foreach optname {-basicconstraints -keyusage -enhkeyusage -altnames} { + if {[dict exists $parsed_req extensions $optname]} { + lappend opts $optname [dict get $parsed_req extensions $optname] + } + } + set encoded_cert [cert_create $subject $pubkey $signing_cert {*}$opts -end $enddate] + set certificate [twapi::cert_store_add_encoded_certificate $hstore $encoded_cert] + twapi::cert_set_key_prov $certificate $container keyexchange -csp $csp -csptype $csptype + if {$cert_type eq "intermediate"} { + set intermediate_certificate $certificate + } else { + cert_release $certificate + } + } + + cert_release $ca_certificate + cert_release $intermediate_certificate + return $hstore +} + +proc twapi::dump_test_certs {hstore dir {pfxfile twapitest.pfx}} { + set fd [open [file join $dir $pfxfile] wb] + puts -nonewline $fd [cert_store_export_pfx $hstore "" -exportprivatekeys 1] + close $fd + cert_store_iterate $hstore c { + set fd [open [file join $dir [cert_subject_name $c -name simpledisplay].cer] wb] + puts -nonewline $fd [cert_export $c] + close $fd + } +} + +proc twapi::crypt_test_containers {} { + set crypt [crypt_acquire "" -verifycontext 1] + twapi::trap { + set names {} + foreach name [crypt_key_container_names $crypt] { + if {[string match -nocase twapitest* $name]} { + lappend names $name + } + } + } finally { + crypt_free $crypt + } + return $names +} + +proc twapi::crypt_test_container_cleanup {} { + foreach c [crypt_test_containers] { + crypt_key_container_delete $c + } +} + + +# If we are not being sourced from a executable resource, need to +# source the remaining support files. In the former case, they are +# automatically combined into one so the sourcing is not needed. +if {![info exists twapi::twapi_crypto_rc_sourced]} { + source [file join [file dirname [info script]] sspi.tcl] + source [file join [file dirname [info script]] tls.tcl] +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/device.tcl b/src/vendorlib_tcl8/twapi-5.0b1/device.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/device.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/device.tcl index 3daf681e..561f3d14 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/device.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/device.tcl @@ -1,624 +1,624 @@ -# -# Copyright (c) 2008-2014 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - struct _PREVENT_MEDIA_REMOVAL { - BOOLEAN PreventMediaRemoval; - } - record device_element { class_guid device_instance reserved } -} - -interp alias {} close_devinfoset {} devinfoset_close - -proc twapi::rescan_devices {} { - CM_Reenumerate_DevNode_Ex [CM_Locate_DevNode_Ex "" 0] 0 -} - - -# Callback invoked for device changes. -# Does some processing of passed data and then invokes the -# real callback script -proc twapi::_device_notification_handler {id args} { - variable _device_notifiers - set idstr "devnotifier#$id" - if {![info exists _device_notifiers($idstr)]} { - # Notifications that expect a response default to "true" - return 1 - } - set script [lindex $_device_notifiers($idstr) 1] - - # For volume notifications, change drive bitmask to - # list of drives before passing back to script - set event [lindex $args 0] - if {[lindex $args 1] eq "volume" && - ($event eq "deviceremovecomplete" || $event eq "devicearrival")} { - lset args 2 [_drivemask_to_drivelist [lindex $args 2]] - - # Also indicate whether network volume and whether change is a media - # change or physical change - set attrs [list ] - set flags [lindex $args 3] - if {$flags & 1} { - lappend attrs mediachange - } - if {$flags & 2} { - lappend attrs networkvolume - } - lset args 3 $attrs - } - - return [uplevel #0 [linsert $script end $idstr {*}$args]] -} - -proc twapi::start_device_notifier {script args} { - variable _device_notifiers - - set script [lrange $script 0 end]; # Verify syntactically a list - - array set opts [parseargs args { - deviceinterface.arg - handle.arg - } -maxleftover 0] - - # For reference - some common device interface classes - # NOTE: NOT ALL HAVE BEEN VERIFIED! - # Network Card {ad498944-762f-11d0-8dcb-00c04fc3358c} - # Human Interface Device (HID) {4d1e55b2-f16f-11cf-88cb-001111000030} - # GUID_DEVINTERFACE_DISK - {53f56307-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_CDROM - {53f56308-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_PARTITION - {53f5630a-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_TAPE - {53f5630b-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_WRITEONCEDISK - {53f5630c-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_VOLUME - {53f5630d-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_MEDIUMCHANGER - {53f56310-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_FLOPPY - {53f56311-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_CDCHANGER - {53f56312-b6bf-11d0-94f2-00a0c91efb8b} - # GUID_DEVINTERFACE_STORAGEPORT - {2accfe60-c130-11d2-b082-00a0c91efb8b} - # GUID_DEVINTERFACE_KEYBOARD - {884b96c3-56ef-11d1-bc8c-00a0c91405dd} - # GUID_DEVINTERFACE_MOUSE - {378de44c-56ef-11d1-bc8c-00a0c91405dd} - # GUID_DEVINTERFACE_PARALLEL - {97F76EF0-F883-11D0-AF1F-0000F800845C} - # GUID_DEVINTERFACE_COMPORT - {86e0d1e0-8089-11d0-9ce4-08003e301f73} - # GUID_DEVINTERFACE_DISPLAY_ADAPTER - {5b45201d-f2f2-4f3b-85bb-30ff1f953599} - # GUID_DEVINTERFACE_USB_HUB - {f18a0e88-c30c-11d0-8815-00a0c906bed8} - # GUID_DEVINTERFACE_USB_DEVICE - {A5DCBF10-6530-11D2-901F-00C04FB951ED} - # GUID_DEVINTERFACE_USB_HOST_CONTROLLER - {3abf6f2d-71c4-462a-8a92-1e6861e6af27} - - - if {[info exists opts(deviceinterface)] && [info exists opts(handle)]} { - error "Options -deviceinterface and -handle are mutually exclusive." - } - - if {![info exists opts(deviceinterface)]} { - set opts(deviceinterface) "" - } - if {[info exists opts(handle)]} { - set type 6 - } else { - set opts(handle) NULL - switch -exact -- $opts(deviceinterface) { - port { set type 3 ; set opts(deviceinterface) "" } - volume { set type 2 ; set opts(deviceinterface) "" } - default { - # device interface class guid or empty string (for all device interfaces) - set type 5 - } - } - } - - set id [Twapi_RegisterDeviceNotification $type $opts(deviceinterface) $opts(handle)] - set idstr "devnotifier#$id" - - set _device_notifiers($idstr) [list $id $script] - return $idstr -} - -proc twapi::stop_device_notifier {idstr} { - variable _device_notifiers - - if {![info exists _device_notifiers($idstr)]} { - return; - } - - Twapi_UnregisterDeviceNotification [lindex $_device_notifiers($idstr) 0] - unset _device_notifiers($idstr) -} - -proc twapi::devinfoset {args} { - array set opts [parseargs args { - {guid.arg ""} - {classtype.arg setup {interface setup}} - {presentonly.bool false 0x2} - {currentprofileonly.bool false 0x8} - {deviceinfoset.arg NULL} - {hwin.int 0} - {system.arg ""} - {pnpenumerator.arg ""} - } -maxleftover 0] - - # DIGCF_ALLCLASSES is bitmask 4 - set flags [expr {$opts(guid) eq "" ? 0x4 : 0}] - if {$opts(classtype) eq "interface"} { - if {$opts(pnpenumerator) ne ""} { - error "The -pnpenumerator option cannot be used when -classtype interface is specified." - } - # DIGCF_DEVICEINTERFACE - set flags [expr {$flags | 0x10}] - } - - # DIGCF_PRESENT - set flags [expr {$flags | $opts(presentonly)}] - - # DIGCF_PRESENT - set flags [expr {$flags | $opts(currentprofileonly)}] - - return [SetupDiGetClassDevsEx \ - $opts(guid) \ - $opts(pnpenumerator) \ - $opts(hwin) \ - $flags \ - $opts(deviceinfoset) \ - $opts(system)] -} - - -# Given a device information set, returns the device elements within it -proc twapi::devinfoset_elements {hdevinfo} { - set result [list ] - set i 0 - trap { - while {true} { - lappend result [SetupDiEnumDeviceInfo $hdevinfo $i] - incr i - } - } onerror {TWAPI_WIN32 0x103} { - # Fine, Just means no more items - } onerror {TWAPI_WIN32 0x80070103} { - # Fine, Just means no more items (HRESULT version of above code) - } - - return $result -} - -# Given a device information set, returns the device elements within it -proc twapi::devinfoset_instance_ids {hdevinfo} { - set result [list ] - set i 0 - trap { - while {true} { - lappend result [device_element_instance_id $hdevinfo [SetupDiEnumDeviceInfo $hdevinfo $i]] - incr i - } - } onerror {TWAPI_WIN32 0x103} { - # Fine, Just means no more items - } onerror {TWAPI_WIN32 0x80070103} { - # Fine, Just means no more items (HRESULT version of above code) - } - - return $result -} - -# Returns a device instance element from a devinfoset -proc twapi::devinfoset_element {hdevinfo instance_id} { - return [SetupDiOpenDeviceInfo $hdevinfo $instance_id 0 0] -} - -# Get the registry property for a devinfoset element -proc twapi::devinfoset_element_registry_property {hdevinfo develem prop} { - Twapi_SetupDiGetDeviceRegistryProperty $hdevinfo $develem [_device_registry_sym_to_code $prop] -} - -# Given a device information set, returns a list of specified registry -# properties for all elements of the set -# args is list of properties to retrieve -proc twapi::devinfoset_registry_properties {hdevinfo args} { - set result [list ] - trap { - # Keep looping until there is an error saying no more items - set i 0 - while {true} { - - # First element is the DEVINFO_DATA element - set devinfo_data [SetupDiEnumDeviceInfo $hdevinfo $i] - set item [list -deviceelement $devinfo_data ] - - # Get all specified property values - foreach prop $args { - set intprop [_device_registry_sym_to_code $prop] - trap { - lappend item $prop \ - [list success \ - [Twapi_SetupDiGetDeviceRegistryProperty \ - $hdevinfo $devinfo_data $intprop]] - } onerror {} { - lappend item $prop [list fail [list [trapresult] $::errorCode]] - } - } - lappend result $item - - incr i - } - } onerror {TWAPI_WIN32 0x103} { - # Fine, Just means no more items - } onerror {TWAPI_WIN32 0x80070103} { - # Fine, Just means no more items (HRESULT version of above code) - } - - return $result -} - - -# Given a device information set, returns specified device interface -# properties -# TBD - document ? -proc twapi::devinfoset_interface_details {hdevinfo guid args} { - set result [list ] - - array set opts [parseargs args { - {matchdeviceelement.arg {}} - interfaceclass - flags - devicepath - deviceelement - ignoreerrors - } -maxleftover 0] - - trap { - # Keep looping until there is an error saying no more items - set i 0 - while {true} { - set interface_data [SetupDiEnumDeviceInterfaces $hdevinfo \ - $opts(matchdeviceelement) $guid $i] - set item [list ] - if {$opts(interfaceclass)} { - lappend item -interfaceclass [lindex $interface_data 0] - } - if {$opts(flags)} { - set flags [lindex $interface_data 1] - set symflags [_make_symbolic_bitmask $flags {active 1 default 2 removed 4} false] - lappend item -flags [linsert $symflags 0 $flags] - } - - if {$opts(devicepath) || $opts(deviceelement)} { - # Need to get device interface detail. - trap { - foreach {devicepath deviceelement} \ - [SetupDiGetDeviceInterfaceDetail \ - $hdevinfo \ - $interface_data \ - $opts(matchdeviceelement)] \ - break - - if {$opts(deviceelement)} { - lappend item -deviceelement $deviceelement - } - if {$opts(devicepath)} { - lappend item -devicepath $devicepath - } - } onerror {} { - if {! $opts(ignoreerrors)} { - rethrow - } - } - } - lappend result $item - - incr i - } - } onerror {TWAPI_WIN32 0x103} { - # Fine, Just means no more items - } onerror {TWAPI_WIN32 0x80070103} { - # Fine, Just means no more items (HRESULT version of above code) - } - - return $result -} - - -# Return the guids associated with a device class set name. Note -# the latter is not unique so multiple guids may be associated. -proc twapi::device_setup_class_name_to_guids {name args} { - array set opts [parseargs args { - system.arg - } -maxleftover 0 -nulldefault] - - return [twapi::SetupDiClassGuidsFromNameEx $name $opts(system)] -} - -# Utility functions - -proc twapi::_init_device_registry_code_maps {} { - variable _device_registry_syms - variable _device_registry_codes - - # Note this list is ordered based on the corresponding integer codes - set _device_registry_code_syms { - devicedesc hardwareid compatibleids unused0 service unused1 - unused2 class classguid driver configflags mfg friendlyname - location_information physical_device_object_name capabilities - ui_number upperfilters lowerfilters - bustypeguid legacybustype busnumber enumerator_name security - security_sds devtype exclusive characteristics address - ui_number_desc_format device_power_data - removal_policy removal_policy_hw_default removal_policy_override - install_state location_paths base_containerid - } - - set i 0 - foreach sym $_device_registry_code_syms { - set _device_registry_codes($sym) $i - incr i - } -} - -# Map a device registry property to a symbol -proc twapi::_device_registry_code_to_sym {code} { - _init_device_registry_code_maps - - # Once we have initialized, redefine ourselves so we do not do so - # every time. Note define at global ::twapi scope! - proc ::twapi::_device_registry_code_to_sym {code} { - variable _device_registry_code_syms - if {$code >= [llength $_device_registry_code_syms]} { - return $code - } else { - return [lindex $_device_registry_code_syms $code] - } - } - # Call the redefined proc - return [_device_registry_code_to_sym $code] -} - -# Map a device registry property symbol to a numeric code -proc twapi::_device_registry_sym_to_code {sym} { - _init_device_registry_code_maps - - # Once we have initialized, redefine ourselves so we do not do so - # every time. Note define at global ::twapi scope! - proc ::twapi::_device_registry_sym_to_code {sym} { - variable _device_registry_codes - # Return the value. If non-existent, an error will be raised - if {[info exists _device_registry_codes($sym)]} { - return $_device_registry_codes($sym) - } elseif {[string is integer -strict $sym]} { - return $sym - } else { - error "Unknown or unsupported device registry property symbol '$sym'" - } - } - # Call the redefined proc - return [_device_registry_sym_to_code $sym] -} - -# Do a device ioctl, returning result as a binary -# TBD - document that caller has to handle errors 122 (ERROR_INSUFFICIENT_BUFFER) and (ERROR_MORE_DATA) -proc twapi::device_ioctl {h code args} { - array set opts [parseargs args { - {input.arg {}} - {outputcount.int 0} - } -maxleftover 0] - - return [DeviceIoControl $h $code $opts(input) $opts(outputcount)] -} - - -# Return a list of physical disks. Note CD-ROMs and floppies not included -proc twapi::find_physical_disks {} { - # Disk interface class guid - set guid {{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}} - set hdevinfo [devinfoset \ - -guid $guid \ - -presentonly true \ - -classtype interface] - trap { - return [kl_flatten [devinfoset_interface_details $hdevinfo $guid -devicepath] -devicepath] - } finally { - devinfoset_close $hdevinfo - } -} - -# Return information about a physical disk -proc twapi::get_physical_disk_info {disk args} { - set result [list ] - - array set opts [parseargs args { - geometry - layout - all - } -maxleftover 0] - - if {$opts(all) || $opts(geometry) || $opts(layout)} { - set h [create_file $disk -createdisposition open_existing] - } - - trap { - if {$opts(all) || $opts(geometry)} { - # IOCTL_DISK_GET_DRIVE_GEOMETRY - 0x70000 - if {[binary scan [device_ioctl $h 0x70000 -outputcount 24] "wiiii" geom(-cylinders) geom(-mediatype) geom(-trackspercylinder) geom(-sectorspertrack) geom(-bytespersector)] != 5} { - error "DeviceIoControl 0x70000 on disk '$disk' returned insufficient data." - } - lappend result -geometry [array get geom] - } - - if {$opts(all) || $opts(layout)} { - # XP and later - IOCTL_DISK_GET_DRIVE_LAYOUT_EX - set data [device_ioctl $h 0x70050 -outputcount 624] - if {[binary scan $data "i i" partstyle layout(-partitioncount)] != 2} { - error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." - } - set layout(-partitionstyle) [_partition_style_sym $partstyle] - switch -exact -- $layout(-partitionstyle) { - mbr { - if {[binary scan $data "@8 i" layout(-signature)] != 1} { - error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." - } - } - gpt { - set pi(-diskid) [_binary_to_guid $data 32] - if {[binary scan $data "@8 w w i" layout(-startingusableoffset) layout(-usablelength) layout(-maxpartitioncount)] != 3} { - error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." - } - } - raw - - unknown { - # No fields to add - } - } - - set layout(-partitions) [list ] - for {set i 0} {$i < $layout(-partitioncount)} {incr i} { - # Decode each partition in turn. Sizeof of PARTITION_INFORMATION_EX is 144 - lappend layout(-partitions) [_decode_PARTITION_INFORMATION_EX_binary $data [expr {48 + (144*$i)}]] - } - lappend result -layout [array get layout] - } - - } finally { - if {[info exists h]} { - CloseHandle $h - } - } - - return $result -} - -# Given a Tcl binary and offset, decode the PARTITION_INFORMATION_EX record -proc twapi::_decode_PARTITION_INFORMATION_EX_binary {bin off} { - if {[binary scan $bin "@$off i x4 w w i c" \ - pi(-partitionstyle) \ - pi(-startingoffset) \ - pi(-partitionlength) \ - pi(-partitionnumber) \ - pi(-rewritepartition)] != 5} { - error "Truncated partition structure." - } - - set pi(-partitionstyle) [_partition_style_sym $pi(-partitionstyle)] - - # MBR/GPT are at offset 32 in the structure - switch -exact -- $pi(-partitionstyle) { - mbr { - if {[binary scan $bin "@$off x32 c c c x i" pi(-partitiontype) pi(-bootindicator) pi(-recognizedpartition) pi(-hiddensectors)] != 4} { - error "Truncated partition structure." - } - # Show partition type in hex, not negative number - set pi(-partitiontype) [format 0x%2.2x [expr {0xff & $pi(-partitiontype)}]] - } - gpt { - set pi(-partitiontype) [_binary_to_guid $bin [expr {$off+32}]] - set pi(-partitionif) [_binary_to_guid $bin [expr {$off+48}]] - if {[binary scan $bin "@$off x64 w" pi(-attributes)] != 1} { - error "Truncated partition structure." - } - set pi(-name) [_ucs16_binary_to_string [string range $bin [expr {$off+72}] end]] - } - raw - - unknown { - # No fields to add - } - - } - - return [array get pi] -} - -# IOCTL_STORAGE_EJECT_MEDIA -interp alias {} twapi::eject {} twapi::eject_media -proc twapi::eject_media device { - # http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q165721& - set h [_open_disk_device $device] - trap { - device_ioctl $h 0x90018; # FSCTL_LOCK_VOLUME - device_ioctl $h 0x90020; # FSCTL_DISMOUNT_VOLUME - # IOCTL_STORAGE_MEDIA_REMOVAL (0) - device_ioctl $h 0x2d4804 -input [_PREVENT_MEDIA_REMOVAL 0] - device_ioctl $h 0x2d4808; # IOCTL_STORAGE_EJECT_MEDIA - } finally { - close_handle $h - } -} - -# IOCTL_DISK_LOAD_MEDIA -# TBD - should we use IOCTL_DISK_LOAD_MEDIA2 instead (0x2d080c) see -# SDK, faster if read / write access not necessary. We are closing -# the handle right away anyway but would that stop other apps from -# acessing the file system on the CD ? Need to try (note device -# has to be opened with FILE_READ_ATTRIBUTES only in that case) - -interp alias {} twapi::load_media {} twapi::_issue_disk_ioctl 0x2d480c - -# FSCTL_LOCK_VOLUME -# TBD - interp alias {} twapi::lock_volume {} twapi::_issue_disk_ioctl 0x90018 -# FSCTL_LOCK_VOLUME -# TBD - interp alias {} twapi::unlock_volume {} twapi::_issue_disk_ioctl 0x9001c - -proc twapi::_lock_media {lock device} { - # IOCTL_STORAGE_MEDIA_REMOVAL - _issue_disk_ioctl 0x2d4804 $device -input [_PREVENT_MEDIA_REMOVAL $lock] -} -interp alias {} twapi::lock_media {} twapi::_lock_media 1 -interp alias {} twapi::unlock_media {} twapi::_lock_media 0 - -proc twapi::_issue_disk_ioctl {ioctl device args} { - set h [_open_disk_device $device] - trap { - device_ioctl $h $ioctl {*}$args - } finally { - close_handle $h - } -} - -twapi::proc* twapi::_open_disk_device {device} { - package require twapi_storage -} { - # device must be "cdrom", X:, X:\\, X:/, a volume or a physical disk as - # returned from find_physical_disks - switch -regexp -nocase -- $device { - {^cdrom$} { - foreach drive [find_logical_drives] { - if {![catch {get_drive_type $drive} drive_type]} { - if {$drive_type eq "cdrom"} { - set device "\\\\.\\$drive" - break - } - } - } - if {$device eq "cdrom"} { - error "Could not find a CD-ROM device." - } - } - {^[[:alpha:]]:(/|\\)?$} { - set device "\\\\.\\[string range $device 0 1]" - } - {^\\\\\?\\.*#\{[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}\}$} { - # Device name ok - } - {^\\\\\?\\Volume\{[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}\}\\?$} { - # Volume name ok. But make sure we trim off any trailing - # \ since create_file will open the root dir instead of the device - set device [string trimright $device \\] - } - default { - # Just to prevent us from opening some file instead - error "Invalid device name '$device'" - } - } - - # http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q165721& - return [create_file $device -access {generic_read generic_write} \ - -createdisposition open_existing \ - -share {read write}] -} - - -# Map a partition style code to a symbol -proc twapi::_partition_style_sym {partstyle} { - set partstyle [lindex {mbr gpt raw} $partstyle] - if {$partstyle ne ""} { - return $partstyle - } - return "unknown" -} - +# +# Copyright (c) 2008-2014 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + struct _PREVENT_MEDIA_REMOVAL { + BOOLEAN PreventMediaRemoval; + } + record device_element { class_guid device_instance reserved } +} + +interp alias {} close_devinfoset {} devinfoset_close + +proc twapi::rescan_devices {} { + CM_Reenumerate_DevNode_Ex [CM_Locate_DevNode_Ex "" 0] 0 +} + + +# Callback invoked for device changes. +# Does some processing of passed data and then invokes the +# real callback script +proc twapi::_device_notification_handler {id args} { + variable _device_notifiers + set idstr "devnotifier#$id" + if {![info exists _device_notifiers($idstr)]} { + # Notifications that expect a response default to "true" + return 1 + } + set script [lindex $_device_notifiers($idstr) 1] + + # For volume notifications, change drive bitmask to + # list of drives before passing back to script + set event [lindex $args 0] + if {[lindex $args 1] eq "volume" && + ($event eq "deviceremovecomplete" || $event eq "devicearrival")} { + lset args 2 [_drivemask_to_drivelist [lindex $args 2]] + + # Also indicate whether network volume and whether change is a media + # change or physical change + set attrs [list ] + set flags [lindex $args 3] + if {$flags & 1} { + lappend attrs mediachange + } + if {$flags & 2} { + lappend attrs networkvolume + } + lset args 3 $attrs + } + + return [uplevel #0 [linsert $script end $idstr {*}$args]] +} + +proc twapi::start_device_notifier {script args} { + variable _device_notifiers + + set script [lrange $script 0 end]; # Verify syntactically a list + + array set opts [parseargs args { + deviceinterface.arg + handle.arg + } -maxleftover 0] + + # For reference - some common device interface classes + # NOTE: NOT ALL HAVE BEEN VERIFIED! + # Network Card {ad498944-762f-11d0-8dcb-00c04fc3358c} + # Human Interface Device (HID) {4d1e55b2-f16f-11cf-88cb-001111000030} + # GUID_DEVINTERFACE_DISK - {53f56307-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_CDROM - {53f56308-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_PARTITION - {53f5630a-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_TAPE - {53f5630b-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_WRITEONCEDISK - {53f5630c-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_VOLUME - {53f5630d-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_MEDIUMCHANGER - {53f56310-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_FLOPPY - {53f56311-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_CDCHANGER - {53f56312-b6bf-11d0-94f2-00a0c91efb8b} + # GUID_DEVINTERFACE_STORAGEPORT - {2accfe60-c130-11d2-b082-00a0c91efb8b} + # GUID_DEVINTERFACE_KEYBOARD - {884b96c3-56ef-11d1-bc8c-00a0c91405dd} + # GUID_DEVINTERFACE_MOUSE - {378de44c-56ef-11d1-bc8c-00a0c91405dd} + # GUID_DEVINTERFACE_PARALLEL - {97F76EF0-F883-11D0-AF1F-0000F800845C} + # GUID_DEVINTERFACE_COMPORT - {86e0d1e0-8089-11d0-9ce4-08003e301f73} + # GUID_DEVINTERFACE_DISPLAY_ADAPTER - {5b45201d-f2f2-4f3b-85bb-30ff1f953599} + # GUID_DEVINTERFACE_USB_HUB - {f18a0e88-c30c-11d0-8815-00a0c906bed8} + # GUID_DEVINTERFACE_USB_DEVICE - {A5DCBF10-6530-11D2-901F-00C04FB951ED} + # GUID_DEVINTERFACE_USB_HOST_CONTROLLER - {3abf6f2d-71c4-462a-8a92-1e6861e6af27} + + + if {[info exists opts(deviceinterface)] && [info exists opts(handle)]} { + error "Options -deviceinterface and -handle are mutually exclusive." + } + + if {![info exists opts(deviceinterface)]} { + set opts(deviceinterface) "" + } + if {[info exists opts(handle)]} { + set type 6 + } else { + set opts(handle) NULL + switch -exact -- $opts(deviceinterface) { + port { set type 3 ; set opts(deviceinterface) "" } + volume { set type 2 ; set opts(deviceinterface) "" } + default { + # device interface class guid or empty string (for all device interfaces) + set type 5 + } + } + } + + set id [Twapi_RegisterDeviceNotification $type $opts(deviceinterface) $opts(handle)] + set idstr "devnotifier#$id" + + set _device_notifiers($idstr) [list $id $script] + return $idstr +} + +proc twapi::stop_device_notifier {idstr} { + variable _device_notifiers + + if {![info exists _device_notifiers($idstr)]} { + return; + } + + Twapi_UnregisterDeviceNotification [lindex $_device_notifiers($idstr) 0] + unset _device_notifiers($idstr) +} + +proc twapi::devinfoset {args} { + array set opts [parseargs args { + {guid.arg ""} + {classtype.arg setup {interface setup}} + {presentonly.bool false 0x2} + {currentprofileonly.bool false 0x8} + {deviceinfoset.arg NULL} + {hwin.int 0} + {system.arg ""} + {pnpenumerator.arg ""} + } -maxleftover 0] + + # DIGCF_ALLCLASSES is bitmask 4 + set flags [expr {$opts(guid) eq "" ? 0x4 : 0}] + if {$opts(classtype) eq "interface"} { + if {$opts(pnpenumerator) ne ""} { + error "The -pnpenumerator option cannot be used when -classtype interface is specified." + } + # DIGCF_DEVICEINTERFACE + set flags [expr {$flags | 0x10}] + } + + # DIGCF_PRESENT + set flags [expr {$flags | $opts(presentonly)}] + + # DIGCF_PRESENT + set flags [expr {$flags | $opts(currentprofileonly)}] + + return [SetupDiGetClassDevsEx \ + $opts(guid) \ + $opts(pnpenumerator) \ + $opts(hwin) \ + $flags \ + $opts(deviceinfoset) \ + $opts(system)] +} + + +# Given a device information set, returns the device elements within it +proc twapi::devinfoset_elements {hdevinfo} { + set result [list ] + set i 0 + trap { + while {true} { + lappend result [SetupDiEnumDeviceInfo $hdevinfo $i] + incr i + } + } onerror {TWAPI_WIN32 0x103} { + # Fine, Just means no more items + } onerror {TWAPI_WIN32 0x80070103} { + # Fine, Just means no more items (HRESULT version of above code) + } + + return $result +} + +# Given a device information set, returns the device elements within it +proc twapi::devinfoset_instance_ids {hdevinfo} { + set result [list ] + set i 0 + trap { + while {true} { + lappend result [device_element_instance_id $hdevinfo [SetupDiEnumDeviceInfo $hdevinfo $i]] + incr i + } + } onerror {TWAPI_WIN32 0x103} { + # Fine, Just means no more items + } onerror {TWAPI_WIN32 0x80070103} { + # Fine, Just means no more items (HRESULT version of above code) + } + + return $result +} + +# Returns a device instance element from a devinfoset +proc twapi::devinfoset_element {hdevinfo instance_id} { + return [SetupDiOpenDeviceInfo $hdevinfo $instance_id 0 0] +} + +# Get the registry property for a devinfoset element +proc twapi::devinfoset_element_registry_property {hdevinfo develem prop} { + Twapi_SetupDiGetDeviceRegistryProperty $hdevinfo $develem [_device_registry_sym_to_code $prop] +} + +# Given a device information set, returns a list of specified registry +# properties for all elements of the set +# args is list of properties to retrieve +proc twapi::devinfoset_registry_properties {hdevinfo args} { + set result [list ] + trap { + # Keep looping until there is an error saying no more items + set i 0 + while {true} { + + # First element is the DEVINFO_DATA element + set devinfo_data [SetupDiEnumDeviceInfo $hdevinfo $i] + set item [list -deviceelement $devinfo_data ] + + # Get all specified property values + foreach prop $args { + set intprop [_device_registry_sym_to_code $prop] + trap { + lappend item $prop \ + [list success \ + [Twapi_SetupDiGetDeviceRegistryProperty \ + $hdevinfo $devinfo_data $intprop]] + } onerror {} { + lappend item $prop [list fail [list [trapresult] $::errorCode]] + } + } + lappend result $item + + incr i + } + } onerror {TWAPI_WIN32 0x103} { + # Fine, Just means no more items + } onerror {TWAPI_WIN32 0x80070103} { + # Fine, Just means no more items (HRESULT version of above code) + } + + return $result +} + + +# Given a device information set, returns specified device interface +# properties +# TBD - document ? +proc twapi::devinfoset_interface_details {hdevinfo guid args} { + set result [list ] + + array set opts [parseargs args { + {matchdeviceelement.arg {}} + interfaceclass + flags + devicepath + deviceelement + ignoreerrors + } -maxleftover 0] + + trap { + # Keep looping until there is an error saying no more items + set i 0 + while {true} { + set interface_data [SetupDiEnumDeviceInterfaces $hdevinfo \ + $opts(matchdeviceelement) $guid $i] + set item [list ] + if {$opts(interfaceclass)} { + lappend item -interfaceclass [lindex $interface_data 0] + } + if {$opts(flags)} { + set flags [lindex $interface_data 1] + set symflags [_make_symbolic_bitmask $flags {active 1 default 2 removed 4} false] + lappend item -flags [linsert $symflags 0 $flags] + } + + if {$opts(devicepath) || $opts(deviceelement)} { + # Need to get device interface detail. + trap { + foreach {devicepath deviceelement} \ + [SetupDiGetDeviceInterfaceDetail \ + $hdevinfo \ + $interface_data \ + $opts(matchdeviceelement)] \ + break + + if {$opts(deviceelement)} { + lappend item -deviceelement $deviceelement + } + if {$opts(devicepath)} { + lappend item -devicepath $devicepath + } + } onerror {} { + if {! $opts(ignoreerrors)} { + rethrow + } + } + } + lappend result $item + + incr i + } + } onerror {TWAPI_WIN32 0x103} { + # Fine, Just means no more items + } onerror {TWAPI_WIN32 0x80070103} { + # Fine, Just means no more items (HRESULT version of above code) + } + + return $result +} + + +# Return the guids associated with a device class set name. Note +# the latter is not unique so multiple guids may be associated. +proc twapi::device_setup_class_name_to_guids {name args} { + array set opts [parseargs args { + system.arg + } -maxleftover 0 -nulldefault] + + return [twapi::SetupDiClassGuidsFromNameEx $name $opts(system)] +} + +# Utility functions + +proc twapi::_init_device_registry_code_maps {} { + variable _device_registry_syms + variable _device_registry_codes + + # Note this list is ordered based on the corresponding integer codes + set _device_registry_code_syms { + devicedesc hardwareid compatibleids unused0 service unused1 + unused2 class classguid driver configflags mfg friendlyname + location_information physical_device_object_name capabilities + ui_number upperfilters lowerfilters + bustypeguid legacybustype busnumber enumerator_name security + security_sds devtype exclusive characteristics address + ui_number_desc_format device_power_data + removal_policy removal_policy_hw_default removal_policy_override + install_state location_paths base_containerid + } + + set i 0 + foreach sym $_device_registry_code_syms { + set _device_registry_codes($sym) $i + incr i + } +} + +# Map a device registry property to a symbol +proc twapi::_device_registry_code_to_sym {code} { + _init_device_registry_code_maps + + # Once we have initialized, redefine ourselves so we do not do so + # every time. Note define at global ::twapi scope! + proc ::twapi::_device_registry_code_to_sym {code} { + variable _device_registry_code_syms + if {$code >= [llength $_device_registry_code_syms]} { + return $code + } else { + return [lindex $_device_registry_code_syms $code] + } + } + # Call the redefined proc + return [_device_registry_code_to_sym $code] +} + +# Map a device registry property symbol to a numeric code +proc twapi::_device_registry_sym_to_code {sym} { + _init_device_registry_code_maps + + # Once we have initialized, redefine ourselves so we do not do so + # every time. Note define at global ::twapi scope! + proc ::twapi::_device_registry_sym_to_code {sym} { + variable _device_registry_codes + # Return the value. If non-existent, an error will be raised + if {[info exists _device_registry_codes($sym)]} { + return $_device_registry_codes($sym) + } elseif {[string is integer -strict $sym]} { + return $sym + } else { + error "Unknown or unsupported device registry property symbol '$sym'" + } + } + # Call the redefined proc + return [_device_registry_sym_to_code $sym] +} + +# Do a device ioctl, returning result as a binary +# TBD - document that caller has to handle errors 122 (ERROR_INSUFFICIENT_BUFFER) and (ERROR_MORE_DATA) +proc twapi::device_ioctl {h code args} { + array set opts [parseargs args { + {input.arg {}} + {outputcount.int 0} + } -maxleftover 0] + + return [DeviceIoControl $h $code $opts(input) $opts(outputcount)] +} + + +# Return a list of physical disks. Note CD-ROMs and floppies not included +proc twapi::find_physical_disks {} { + # Disk interface class guid + set guid {{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}} + set hdevinfo [devinfoset \ + -guid $guid \ + -presentonly true \ + -classtype interface] + trap { + return [kl_flatten [devinfoset_interface_details $hdevinfo $guid -devicepath] -devicepath] + } finally { + devinfoset_close $hdevinfo + } +} + +# Return information about a physical disk +proc twapi::get_physical_disk_info {disk args} { + set result [list ] + + array set opts [parseargs args { + geometry + layout + all + } -maxleftover 0] + + if {$opts(all) || $opts(geometry) || $opts(layout)} { + set h [create_file $disk -createdisposition open_existing] + } + + trap { + if {$opts(all) || $opts(geometry)} { + # IOCTL_DISK_GET_DRIVE_GEOMETRY - 0x70000 + if {[binary scan [device_ioctl $h 0x70000 -outputcount 24] "wiiii" geom(-cylinders) geom(-mediatype) geom(-trackspercylinder) geom(-sectorspertrack) geom(-bytespersector)] != 5} { + error "DeviceIoControl 0x70000 on disk '$disk' returned insufficient data." + } + lappend result -geometry [array get geom] + } + + if {$opts(all) || $opts(layout)} { + # XP and later - IOCTL_DISK_GET_DRIVE_LAYOUT_EX + set data [device_ioctl $h 0x70050 -outputcount 624] + if {[binary scan $data "i i" partstyle layout(-partitioncount)] != 2} { + error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." + } + set layout(-partitionstyle) [_partition_style_sym $partstyle] + switch -exact -- $layout(-partitionstyle) { + mbr { + if {[binary scan $data "@8 i" layout(-signature)] != 1} { + error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." + } + } + gpt { + set pi(-diskid) [_binary_to_guid $data 32] + if {[binary scan $data "@8 w w i" layout(-startingusableoffset) layout(-usablelength) layout(-maxpartitioncount)] != 3} { + error "DeviceIoControl 0x70050 on disk '$disk' returned insufficient data." + } + } + raw - + unknown { + # No fields to add + } + } + + set layout(-partitions) [list ] + for {set i 0} {$i < $layout(-partitioncount)} {incr i} { + # Decode each partition in turn. Sizeof of PARTITION_INFORMATION_EX is 144 + lappend layout(-partitions) [_decode_PARTITION_INFORMATION_EX_binary $data [expr {48 + (144*$i)}]] + } + lappend result -layout [array get layout] + } + + } finally { + if {[info exists h]} { + CloseHandle $h + } + } + + return $result +} + +# Given a Tcl binary and offset, decode the PARTITION_INFORMATION_EX record +proc twapi::_decode_PARTITION_INFORMATION_EX_binary {bin off} { + if {[binary scan $bin "@$off i x4 w w i c" \ + pi(-partitionstyle) \ + pi(-startingoffset) \ + pi(-partitionlength) \ + pi(-partitionnumber) \ + pi(-rewritepartition)] != 5} { + error "Truncated partition structure." + } + + set pi(-partitionstyle) [_partition_style_sym $pi(-partitionstyle)] + + # MBR/GPT are at offset 32 in the structure + switch -exact -- $pi(-partitionstyle) { + mbr { + if {[binary scan $bin "@$off x32 c c c x i" pi(-partitiontype) pi(-bootindicator) pi(-recognizedpartition) pi(-hiddensectors)] != 4} { + error "Truncated partition structure." + } + # Show partition type in hex, not negative number + set pi(-partitiontype) [format 0x%2.2x [expr {0xff & $pi(-partitiontype)}]] + } + gpt { + set pi(-partitiontype) [_binary_to_guid $bin [expr {$off+32}]] + set pi(-partitionif) [_binary_to_guid $bin [expr {$off+48}]] + if {[binary scan $bin "@$off x64 w" pi(-attributes)] != 1} { + error "Truncated partition structure." + } + set pi(-name) [_ucs16_binary_to_string [string range $bin [expr {$off+72}] end]] + } + raw - + unknown { + # No fields to add + } + + } + + return [array get pi] +} + +# IOCTL_STORAGE_EJECT_MEDIA +interp alias {} twapi::eject {} twapi::eject_media +proc twapi::eject_media device { + # http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q165721& + set h [_open_disk_device $device] + trap { + device_ioctl $h 0x90018; # FSCTL_LOCK_VOLUME + device_ioctl $h 0x90020; # FSCTL_DISMOUNT_VOLUME + # IOCTL_STORAGE_MEDIA_REMOVAL (0) + device_ioctl $h 0x2d4804 -input [_PREVENT_MEDIA_REMOVAL 0] + device_ioctl $h 0x2d4808; # IOCTL_STORAGE_EJECT_MEDIA + } finally { + close_handle $h + } +} + +# IOCTL_DISK_LOAD_MEDIA +# TBD - should we use IOCTL_DISK_LOAD_MEDIA2 instead (0x2d080c) see +# SDK, faster if read / write access not necessary. We are closing +# the handle right away anyway but would that stop other apps from +# acessing the file system on the CD ? Need to try (note device +# has to be opened with FILE_READ_ATTRIBUTES only in that case) + +interp alias {} twapi::load_media {} twapi::_issue_disk_ioctl 0x2d480c + +# FSCTL_LOCK_VOLUME +# TBD - interp alias {} twapi::lock_volume {} twapi::_issue_disk_ioctl 0x90018 +# FSCTL_LOCK_VOLUME +# TBD - interp alias {} twapi::unlock_volume {} twapi::_issue_disk_ioctl 0x9001c + +proc twapi::_lock_media {lock device} { + # IOCTL_STORAGE_MEDIA_REMOVAL + _issue_disk_ioctl 0x2d4804 $device -input [_PREVENT_MEDIA_REMOVAL $lock] +} +interp alias {} twapi::lock_media {} twapi::_lock_media 1 +interp alias {} twapi::unlock_media {} twapi::_lock_media 0 + +proc twapi::_issue_disk_ioctl {ioctl device args} { + set h [_open_disk_device $device] + trap { + device_ioctl $h $ioctl {*}$args + } finally { + close_handle $h + } +} + +twapi::proc* twapi::_open_disk_device {device} { + package require twapi_storage +} { + # device must be "cdrom", X:, X:\\, X:/, a volume or a physical disk as + # returned from find_physical_disks + switch -regexp -nocase -- $device { + {^cdrom$} { + foreach drive [find_logical_drives] { + if {![catch {get_drive_type $drive} drive_type]} { + if {$drive_type eq "cdrom"} { + set device "\\\\.\\$drive" + break + } + } + } + if {$device eq "cdrom"} { + error "Could not find a CD-ROM device." + } + } + {^[[:alpha:]]:(/|\\)?$} { + set device "\\\\.\\[string range $device 0 1]" + } + {^\\\\\?\\.*#\{[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}\}$} { + # Device name ok + } + {^\\\\\?\\Volume\{[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}\}\\?$} { + # Volume name ok. But make sure we trim off any trailing + # \ since create_file will open the root dir instead of the device + set device [string trimright $device \\] + } + default { + # Just to prevent us from opening some file instead + error "Invalid device name '$device'" + } + } + + # http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q165721& + return [create_file $device -access {generic_read generic_write} \ + -createdisposition open_existing \ + -share {read write}] +} + + +# Map a partition style code to a symbol +proc twapi::_partition_style_sym {partstyle} { + set partstyle [lindex {mbr gpt raw} $partstyle] + if {$partstyle ne ""} { + return $partstyle + } + return "unknown" +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/etw.tcl b/src/vendorlib_tcl8/twapi-5.0b1/etw.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/etw.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/etw.tcl index df8d60a0..aee82e98 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/etw.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/etw.tcl @@ -1,1390 +1,1390 @@ -# -# Copyright (c) 2012-2014 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - # GUID's and event types for ETW. - variable _etw_mof - array set _etw_mof { - provider_name "TwapiETWProvider" - provider_guid "{B358E9D9-4D82-4A82-A129-BAC098C54746}" - eventclass_name "TwapiETWEventClass" - eventclass_guid "{D5B52E95-8447-40C1-B316-539894449B36}" - } - - # So we don't pollute namespace with temp vars - apply [list defs { - foreach {key val} $defs { - proc etw_twapi_$key {} "return $val" - } - } [namespace current]] [array get _etw_mof] - - # Cache of event definitions for parsing MOF events. Nested dictionary - # with the following structure (uppercase keys are variables, - # lower case are constant/tokens, "->" is nested dict, "-" is scalar): - # EVENTCLASSGUID -> - # classname - name of the class - # definitions -> - # VERSION -> - # EVENTTYPE -> - # eventtype - same as EVENTTYPE - # eventtypename - name / description for the event type - # fieldtypes - ordered list of field types for that event - # fields -> - # FIELDINDEX -> - # type - the field type in string format - # fieldtype - the corresponding field type numeric value - # extension - the MoF extension qualifier for the field - # - # The cache assumes that MOF event definitions are globally identical - # (ie. same on local and remote systems) - variable _etw_event_defs - set _etw_event_defs [dict create] - - # Keeps track of open trace handles for reading - variable _etw_trace_consumers - array set _etw_trace_consumers {} - - # Keep track of trace controller handles. Note we do not always - # need a handle for controller actions. We can also control based - # on name, for example if some other process has started the trace - variable _etw_trace_controllers - array set _etw_trace_controllers {} - - # - # These record definitions match the lists constructed in the ETW C code - # Note these are purposely formatted on single line so the record fieldnames - # print better. - - # Buffer header (EVENT_TRACE_LOGFILE) - record etw_event_trace_logfile {logfile logger_name current_time buffers_read trace_logfile_header buffer_size filled kernel_trace} - - # TRACE_LOGFILE_HEADER - record etw_trace_logfile_header {buffer_size version_major version_minor version_submajor version_subminor provider_version processor_count end_time timer_resolution max_file_size logfile_mode buffers_written pointer_size events_lost cpu_mhz time_zone boot_time perf_frequency start_time reserved_flags buffers_lost } - - # TDH based event definitions - - record tdh_event { header buffer_context extended_data data } - - record tdh_event_header { flags event_property tid pid timestamp - kernel_time user_time processor_time activity_id descriptor provider_guid} - record tdh_event_buffer_context { processor logger_id } - record tdh_event_data {provider_guid event_guid decoder provider_name level_name channel_name keyword_names task_name opcode_name message localized_provider_name activity_id related_activity_id properties flags} - - record tdh_event_data_descriptor {id version channel level opcode task keywords} - - # Definitions for EVENT_TRACE_LOGFILE - record tdh_buffer { logfile logger current_time buffers_read header buffer_size filled kernel_trace } - - record tdh_logfile_header { size major_version minor_version sub_version subminor_version provider_version processor_count end_time resolution max_file_size logfile_mode buffers_written pointer_size events_lost cpu_mhz timezone boot_time perf_frequency start_time reserved_flags buffers_lost } - - # MOF based event definitions - record mof_event {header instance_id parent_instance_id parent_guid data} - record mof_event_header {type level version tid pid timestamp guid kernel_time user_time processor_time} - - # Standard app visible event definitions. These are made - # compatible with the evt_* routines - record etw_event {-eventid -version -channel -level -opcode -task -keywordmask -timecreated -tid -pid -providerguid -usertime -kerneltime -providername -eventguid -channelname -levelname -opcodename -taskname -keywords -properties -message -sid} - - # Record for EVENT_TRACE_PROPERTIES - # TBD - document - record etw_trace_properties {logfile trace_name trace_guid buffer_size min_buffers max_buffers max_file_size logfile_mode flush_timer enable_flags clock_resolution age_limit buffer_count free_buffers events_lost buffers_written log_buffers_lost real_time_buffers_lost logger_tid} -} - - -proc twapi::etw_get_traces {args} { - parseargs args {detail} -setvars -maxleftover 0 - set sessions {} - foreach sess [QueryAllTraces] { - set name [etw_trace_properties trace_name $sess] - if {$detail} { - lappend sessions [etw_trace_properties $sess] - } else { - lappend sessions $name - } - } - return $sessions -} - -if {[twapi::min_os_version 6]} { - proc twapi::etw_get_provider_guid {name} { - return [lindex [Twapi_TdhEnumerateProviders $name] 0] - } - proc twapi::etw_get_providers {args} { - parseargs args { - detail - {types.arg {mof xml}} - } -setvars -maxleftover 0 - set providers {} - foreach rec [Twapi_TdhEnumerateProviders] { - lassign $rec guid type name - set type [dict* {0 xml 1 mof} $type] - if {$type in $types} { - if {$detail} { - lappend providers [list guid $guid type $type name $name] - } else { - lappend providers $name - } - } - } - return $providers - } -} else { - twapi::proc* twapi::etw_get_provider_guid {lookup_name} { - package require twapi_wmi - } { - set wmi [wmi_root -root wmi] - set oclasses {} - set providers {} - # TBD - check if ExecQuery would be faster - trap { - # All providers are direct subclasses of the EventTrace class - set oclasses [wmi_collect_classes $wmi -ancestor EventTrace -shallow] - foreach ocls $oclasses { - set quals [$ocls Qualifiers_] - trap { - set name [$quals -with {{Item Description}} -invoke Value 2 {}] - if {[string equal -nocase $name $lookup_name]} { - return [$quals -with {{Item Guid}} -invoke Value 2 {}] - } - } finally { - $quals -destroy - } - } - } finally { - foreach ocls $oclasses {$ocls -destroy} - $wmi -destroy - } - return "" - } - - twapi::proc* twapi::etw_get_providers {args} { - package require twapi_wmi - } { - parseargs args { detail {types.arg {mof xml}} } -setvars -maxleftover 0 - if {"mof" ni $types} { - return {}; # Older systems do not have xml based providers - } - set wmi [wmi_root -root wmi] - set oclasses {} - set providers {} - # TBD - check if ExecQuery would be faster - trap { - # All providers are direct subclasses of the EventTrace class - set oclasses [wmi_collect_classes $wmi -ancestor EventTrace -shallow] - foreach ocls $oclasses { - set quals [$ocls Qualifiers_] - trap { - set name [$quals -with {{Item Description}} -invoke Value 2 {}] - set guid [$quals -with {{Item Guid}} -invoke Value 2 {}] - if {$detail} { - lappend providers [list guid $guid type mof name $name] - } else { - lappend providers $name - } - } finally { - $quals -destroy - } - } - } finally { - foreach ocls $oclasses {$ocls -destroy} - $wmi -destroy - } - return $providers - } -} - -twapi::proc* twapi::etw_install_twapi_mof {} { - package require twapi_wmi -} { - variable _etw_mof - - # MOF definition for our ETW trace event. This is loaded into - # the system WMI registry so event readers can decode our events - # - # Note all strings are NullTerminated and not Counted so embedded nulls - # will not be handled correctly. The problem with using Counted strings - # is that the MSDN docs are inconsistent as to whether the count - # is number of *bytes* or number of *characters* and the existing tools - # are similarly confused. We avoid this by choosing null terminated - # strings despite the embedded nulls drawback. - # TBD - revisit this and see if counted can always be treated as - # bytes and not characters. - - # We do not want the pure binary builds think #pragma is a comment - # and remove the line! Bug 170 - #createtmfile-disable-compaction - set mof_template { - #pragma namespace("\\\\.\\root\\wmi") - - // Keep Description same as provider_name as that is how - // TDH library identifies it. Else there will be a mismatch - // between TdhEnumerateProviders and how we internally assume is - // the provider name - [dynamic: ToInstance, Description("@provider_name"), - Guid("@provider_guid")] - class @provider_name : EventTrace - { - }; - - [dynamic: ToInstance, Description("TWAPI ETW event class"): Amended, - Guid("@eventclass_guid")] - class @eventclass_name : @provider_name - { - }; - - // NOTE: The EventTypeName is REQUIRED else the MS LogParser app - // crashes (even though it should not) - - [dynamic: ToInstance, Description("TWAPI log message"): Amended, - EventType(1), EventTypeName("Message")] - class @eventclass_name_Message : @eventclass_name - { - [WmiDataId(1), Description("Log message"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Message; - }; - - [dynamic: ToInstance, Description("TWAPI variable trace"): Amended, - EventType(2), EventTypeName("VariableTrace")] - class @eventclass_name_VariableTrace : @eventclass_name - { - [WmiDataId(1), Description("Operation"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Operation; - [WmiDataId(2), Description("Variable name"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Name; - [WmiDataId(3), Description("Array index"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Index; - [WmiDataId(4), Description("Value"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Value; - [WmiDataId(5), Description("Context"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Context; - }; - - [dynamic: ToInstance, Description("TWAPI execution trace"): Amended, - EventType(3), EventTypeName("ExecutionTrace")] - class @eventclass_name_ExecutionTrace : @eventclass_name - { - [WmiDataId(1), Description("Operation"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Operation; - [WmiDataId(2), Description("Executed command"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Command; - [WmiDataId(3), Description("Status code"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Code; - [WmiDataId(4), Description("Result"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Result; - [WmiDataId(5), Description("Context"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Context; - }; - - [dynamic: ToInstance, Description("TWAPI command trace"): Amended, - EventType(4), EventTypeName("CommandTrace")] - class @eventclass_name_CommandTrace : @eventclass_name - { - [WmiDataId(1), Description("Operation"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Operation; - [WmiDataId(2), Description("Old command name"): Amended, read, StringTermination("NullTerminated"), Format("w")] string OldName; - [WmiDataId(3), Description("New command name"): Amended, read, StringTermination("NullTerminated"), Format("w")] string NewName; - [WmiDataId(4), Description("Context"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Context; - }; - } - - #createtmfile-enable-compaction - - set mof [string map \ - [list @provider_name $_etw_mof(provider_name) \ - @provider_guid $_etw_mof(provider_guid) \ - @eventclass_name $_etw_mof(eventclass_name) \ - @eventclass_guid $_etw_mof(eventclass_guid) \ - ] $mof_template] - - set mofc [twapi::IMofCompilerProxy new] - twapi::trap { - $mofc CompileBuffer $mof - } finally { - $mofc Release - } -} - -proc twapi::etw_uninstall_twapi_mof {} { - variable _etw_mof - - set wmi [twapi::_wmi wmi] - trap { - set omof [$wmi Get $_etw_mof(provider_name)] - $omof Delete_ - } finally { - if {[info exists omof]} { - $omof destroy - } - $wmi destroy - } -} - -proc twapi::etw_twapi_provider_register {} { - variable _etw_mof - return [twapi::RegisterTraceGuids $_etw_mof(provider_guid) $_etw_mof(eventclass_guid)] -} - -proc twapi::etw_log_message {htrace message {level 4}} { - set level [_etw_level_to_int $level] - if {[etw_provider_enable_level] >= $level} { - # Must match Message event type in MoF definition - # 1 -> event type for Message - TraceEvent $htrace 1 $level [encoding convertto unicode "$message\0"] - } -} - -proc twapi::etw_variable_tracker {htrace name1 name2 op} { - switch -exact -- $op { - array - - unset { set var "" } - default { - if {$name2 eq ""} { - upvar 1 $name1 var - } else { - upvar 1 $name1($name2) var - } - } - } - - if {[info level] > 1} { - set context [info level -1] - } else { - set context "" - } - - # Must match VariableTrace event type in MoF definition - TraceEvent $htrace 2 0 \ - [encoding convertto unicode "$op\0$name1\0$name2\0$var\0"] \ - [_etw_encode_limited_unicode $context] -} - - -proc twapi::etw_execution_tracker {htrace command args} { - set op [lindex $args end] - - switch -exact -- $op { - enter - - enterstep { - set code "" - set result "" - } - leave - - leavestep { - lassign $args code result - } - } - - if {[info level] > 1} { - set context [info level -1] - } else { - set context "" - } - - # Must match Execution event type in MoF definition - TraceEvent $htrace 3 0 \ - [encoding convertto unicode "$op\0"] \ - [_etw_encode_limited_unicode $command] \ - [encoding convertto unicode "$code\0"] \ - [_etw_encode_limited_unicode $result] \ - [_etw_encode_limited_unicode $context] -} - - -proc twapi::etw_command_tracker {htrace oldname newname op} { - if {[info level] > 1} { - set context [info level -1] - } else { - set context "" - } - # Must match CommandTrace event type in MoF definition - TraceEvent $htrace 4 0 \ - [encoding convertto unicode "$op\0$oldname\0$newname\0"] \ - [_etw_encode_limited_unicode $context] -} - -proc twapi::etw_parse_mof_event_class {ocls} { - # Returns a dict - # First level key - event type (integer) - # See description of _etw_event_defs for rest of the structure - - set result [dict create] - - # Iterate over the subclasses, collecting the event metadata - # Create a forward only enumerator for efficiency - # wbemFlagUseAmendedQualifiers|wbemFlagReturnImmediately|wbemFlagForwardOnly - # wbemQueryFlagsShallow - # -> 0x20031 - $ocls -with {{SubClasses_ 0x20031}} -iterate -cleanup osub { - # The subclass must have the eventtype property - # We fetch as a raw value so we can tell the - # original type - if {![catch { - $osub -with { - Qualifiers_ - {Item EventType} - } -invoke Value 2 {} -raw 1 - } event_types]} { - - # event_types is a raw value with a type descriptor as elem 0 - if {[variant_type $event_types] & 0x2000} { - # It is VT_ARRAY so value is already a list - set event_types [variant_value $event_types 0 0 0] - } else { - set event_types [list [variant_value $event_types 0 0 0]] - } - - set event_type_names {} - catch { - set event_type_names [$osub -with { - Qualifiers_ - {Item EventTypeName} - } -invoke Value 2 {} -raw 1] - # event_type_names is a raw value with a type descriptor as elem 0 - # It is IMPORTANT to check this else we cannot distinguish - # between a array (list) and a string with spaces - if {[variant_type $event_type_names] & 0x2000} { - # It is VT_ARRAY so value is already a list - set event_type_names [variant_value $event_type_names 0 0 0] - } else { - # Scalar value. Make into a list - set event_type_names [list [variant_value $event_type_names 0 0 0]] - } - } - - # The subclass has a EventType property. Pick up the - # field definitions. - set fields [dict create] - $osub -with Properties_ -iterate -cleanup oprop { - set quals [$oprop Qualifiers_] - # Event fields will have a WmiDataId qualifier - if {![catch {$quals -with {{Item WmiDataId}} Value} wmidataid]} { - # Yep this is a field, figure out its type - set type [_etw_decipher_mof_event_field_type $oprop $quals] - dict set type -fieldname [$oprop -get Name] - dict set fields $wmidataid $type - } - $quals destroy - } - - # Process the records to put the fields in order based on - # their wmidataid. If any info is missing or inconsistent - # we will mark the whole event type class has undecodable. - # Ids begin from 1. - set fieldtypes {} - for {set id 1} {$id <= [dict size $fields]} {incr id} { - if {![dict exists $fields $id]} { - # Discard all type info - missing type info - debuglog "Missing id $id for event type(s) $event_types for EventTrace Mof Class [$ocls -with {{SystemProperties_} {Item __CLASS}} Value]" - set fieldtypes {} - break; - } - lappend fieldtypes [dict get $fields $id -fieldname] [dict get $fields $id -fieldtype] - } - - foreach event_type $event_types event_type_name $event_type_names { - dict set result -definitions $event_type [dict create -eventtype $event_type -eventtypename $event_type_name -fields $fields -fieldtypes $fieldtypes] - } - } - } - - if {[dict size $result] == 0} { - return {} - } else { - dict set result -classname [$ocls -with {SystemProperties_ {Item __CLASS}} Value] - return $result - } -} - -# Deciphers an event field type - -proc twapi::_etw_decipher_mof_event_field_type {oprop oquals} { - # Maps event field type strings to enums to pass to the C code - # 0 should be unmapped. Note some are duplicates because they - # are the same format. Some are legacy formats not explicitly documented - # in MSDN but found in the sample code. - # Reference - Event Tracing MOF Qualifiers http://msdn.microsoft.com/en-us/library/windows/desktop/aa363800(v=vs.85).aspx - set etw_fieldtypes { - string 1 - stringnullterminated 1 - wstring 2 - wstringnullterminated 2 - stringcounted 3 - stringreversecounted 4 - wstringcounted 5 - wstringreversecounted 6 - boolean 7 - sint8 8 - uint8 9 - csint8 10 - cuint8 11 - sint16 12 - uint16 13 - uint32 14 - sint32 15 - sint64 16 - uint64 17 - xsint16 18 - xuint16 19 - xsint32 20 - xuint32 21 - xsint64 22 - xuint64 23 - real32 24 - real64 25 - object 26 - char16 27 - uint8guid 28 - objectguid 29 - objectipaddrv4 30 - uint32ipaddr 30 - objectipaddr 30 - objectipaddrv6 31 - objectvariant 32 - objectsid 33 - uint64wmitime 34 - objectwmitime 35 - uint16port 38 - objectport 39 - datetime 40 - stringnotcounted 41 - wstringnotcounted 42 - pointer 43 - sizet 43 - } - - # On any errors, we will set type to unknown or unsupported - set type unknown - set quals(extension) ""; # Hint for formatting for display - - if {![catch { - $oquals -with {{Item Pointer}} Value - }]} { - # Actual value does not matter - # If the Pointer qualifier exists, ignore everything else - set type pointer - } elseif {![catch { - $oquals -with {{Item PointerType}} Value - }]} { - # Actual value does not matter - # Some apps mistakenly use PointerType instead of Pointer - set type pointer - } else { - catch { - set type [string tolower [$oquals -with {{Item CIMTYPE}} Value]] - - # The following qualifiers may or may not exist - # TBD - not all may be required to be retrieved - # NOTE: MSDN says some qualifiers are case sensitive! - foreach qual {BitMap BitValues Extension Format Pointer StringTermination ValueMap Values ValueType XMLFragment} { - # catch in case it does not exist - set lqual [string tolower $qual] - set quals($lqual) "" - catch { - set quals($lqual) [$oquals -with [list [list Item $qual]] Value] - } - } - set type [string tolower "$quals(format)${type}$quals(stringtermination)"] - set quals(extension) [string tolower $quals(extension)] - # Not all extensions affect how the event field is extracted - # e.g. the noprint value - if {$quals(extension) in {ipaddr ipaddrv4 ipaddrv6 port variant wmitime guid sid}} { - append type $quals(extension) - } elseif {$quals(extension) eq "sizet"} { - set type sizet - } - } - } - - # Cannot handle arrays yet - TBD - if {[$oprop -get IsArray]} { - set type "arrayof$type" - } - - if {![dict exists $etw_fieldtypes $type]} { - set fieldtype 0 - } else { - set fieldtype [dict get $etw_fieldtypes $type] - } - - return [dict create -type $type -fieldtype $fieldtype -extension $quals(extension)] -} - -proc twapi::etw_find_mof_event_classes {oswbemservices args} { - # Return all classes where a GUID or name matches - - # To avoid iterating the tree multiple times, separate out the guids - # and the names and use separator comparators - - set guids {} - set names {} - - foreach arg $args { - if {[Twapi_IsValidGUID $arg]} { - # GUID's can be multiple format, canonicalize for lsearch - lappend guids [canonicalize_guid $arg] - } else { - lappend names $arg - } - } - - # Note there can be multiple versions sharing a single guid so - # we cannot use the wmi_collect_classes "-first" option to stop the - # search when one is found. - - set name_matcher [lambda* {names val} { - ::tcl::mathop::>= [lsearch -exact -nocase $names $val] 0 - } :: $names] - set guid_matcher [lambda* {guids val} { - ::tcl::mathop::>= [lsearch -exact -nocase $guids $val] 0 - } :: $guids] - - set named_classes {} - if {[llength $names]} { - foreach name $names { - catch {lappend named_classes [$oswbemservices Get $name]} - } - } - - if {[llength $guids]} { - set guid_classes [wmi_collect_classes $oswbemservices -ancestor EventTrace -matchqualifiers [list Guid $guid_matcher]] - } else { - set guid_classes {} - } - - return [concat $guid_classes $named_classes] -} - -proc twapi::etw_get_all_mof_event_classes {oswbemservices} { - return [twapi::wmi_collect_classes $oswbemservices -ancestor EventTrace -matchqualifiers [list Guid ::twapi::true]] -} - -proc twapi::etw_load_mof_event_class_obj {oswbemservices ocls} { - variable _etw_event_defs - set quals [$ocls Qualifiers_] - trap { - set guid [$quals -with {{Item Guid}} Value] - set vers "" - catch {set vers [$quals -with {{Item EventVersion}} Value]} - set def [etw_parse_mof_event_class $ocls] - # Class may be a provider, not a event class in which case - # def will be empty - if {[dict size $def]} { - dict set _etw_event_defs [canonicalize_guid $guid] $vers $def - } - } finally { - $quals destroy - } -} - -proc twapi::etw_load_mof_event_classes {oswbemservices args} { - if {[llength $args] == 0} { - set oclasses [etw_get_all_mof_event_classes $oswbemservices] - } else { - set oclasses [etw_find_mof_event_classes $oswbemservices {*}$args] - } - - foreach ocls $oclasses { - trap { - etw_load_mof_event_class_obj $oswbemservices $ocls - } finally { - $ocls destroy - } - } -} - -proc twapi::etw_open_file {path} { -# TBD - PROCESS_TRACE_MODE_RAW_TIMESTAMP - variable _etw_trace_consumers - - set path [file normalize $path] - - set htrace [OpenTrace $path 0] - set _etw_trace_consumers($htrace) $path - return $htrace -} - -proc twapi::etw_open_session {sessionname} { -# TBD - PROCESS_TRACE_MODE_RAW_TIMESTAMP - variable _etw_trace_consumers - - set htrace [OpenTrace $sessionname 1] - set _etw_trace_consumers($htrace) $sessionname - return $htrace -} - -proc twapi::etw_close_session {htrace} { - variable _etw_trace_consumers - - if {! [info exists _etw_trace_consumers($htrace)]} { - badargs! "Cannot find trace session with handle $htrace" - } - - CloseTrace $htrace - unset _etw_trace_consumers($htrace) - return -} - - -proc twapi::etw_process_events {args} { - array set opts [parseargs args { - callback.arg - start.arg - end.arg - } -nulldefault] - - if {[llength $args] == 0} { - error "At least one trace handle must be specified." - } - - return [ProcessTrace $args $opts(callback) $opts(start) $opts(end)] -} - -proc twapi::etw_open_formatter {} { - variable _etw_formatters - - if {[etw_force_mof] || ![twapi::min_os_version 6 0]} { - uplevel #0 package require twapi_wmi - # Need WMI MOF definitions - set id mof[TwapiId] - dict set _etw_formatters $id OSWBemServices [wmi_root -root wmi] - } else { - # Just a dummy if using a TDH based api - set id tdh[TwapiId] - # Nothing to set as yet but for consistency with MOF implementation - dict set _etw_formatters $id {} - } - return $id -} - -proc twapi::etw_close_formatter {formatter} { - variable _etw_formatters - if {[dict exists $_etw_formatters $formatter OSWBemServices]} { - [dict get $_etw_formatters $formatter OSWBemServices] -destroy - } - - dict unset _etw_formatters $formatter - if {[dict size $_etw_formatters] == 0} { - variable _etw_event_defs - # No more formatters - # Clear out event defs cache which can be quite large - # Really only needed for mof but doesn't matter - set _etw_event_defs {} - } - - return -} - -proc twapi::etw_format_events {formatter args} { - variable _etw_formatters - - if {![dict exists $_etw_formatters $formatter]} { - # We could actually just init ourselves but we want to force - # consistency and caller to release wmi COM object - badargs! "Invalid ETW formatter id \"$formatter\"" - } - - set events {} - if {[dict exists $_etw_formatters $formatter OSWBemServices]} { - set oswbemservices [dict get $_etw_formatters $formatter OSWBemServices] - foreach {bufd rawevents} $args { - lappend events [_etw_format_mof_events $oswbemservices $bufd $rawevents] - } - } else { - foreach {bufd rawevents} $args { - lappend events [_etw_format_tdh_events $bufd $rawevents] - } - } - - # Return as a recordarray - return [list [etw_event] [lconcat {*}$events]] -} - -proc twapi::_etw_format_tdh_events {bufdesc events} { - - set bufhdr [etw_event_trace_logfile trace_logfile_header $bufdesc] - set timer_resolution [etw_trace_logfile_header timer_resolution $bufhdr] - set private_session [expr {0x800 & [etw_trace_logfile_header logfile_mode $bufhdr]}] - set pointer_size [etw_trace_logfile_header pointer_size $bufhdr] - - set formatted_events {} - foreach event $events { - array set fields [tdh_event $event] - set formatted_event [tdh_event_header descriptor $fields(header)] - # Do not select provider_guid from header as for TDH it needs to come - # from the provider_guid in the data portion. - lappend formatted_event {*}[tdh_event_header select $fields(header) {timestamp tid pid}] - lappend formatted_event {*}[tdh_event_data select $fields(data) provider_guid] - if {$private_session} { - lappend formatted_event [expr {[tdh_event_header processor_time $fields(header)] * $timer_resolution}] 0 - } else { - lappend formatted_event [expr {[tdh_event_header user_time $fields(header)] * $timer_resolution}] [expr {[tdh_event_header kernel_time $fields(header)] * $timer_resolution}] - } - lappend formatted_event {*}[tdh_event_data select $fields(data) {provider_name event_guid channel_name level_name opcode_name task_name keyword_names properties message}] [dict* $fields(extended_data) sid ""] - - lappend formatted_events $formatted_event - } - return $formatted_events -} - -proc twapi::_etw_format_mof_events {oswbemservices bufdesc events} { - variable _etw_event_defs - - # TBD - it may be faster to special case NT kernel events as per - # the structures defined in http://msdn.microsoft.com/en-us/library/windows/desktop/aa364083(v=vs.85).aspx - # However, the MSDN warns that structures should not be created from - # MOF classes as alignment restrictions might be different - array set missing {} - foreach event $events { - set guid [mof_event_header guid [mof_event header $event]] - if {! [dict exists $_etw_event_defs $guid]} { - set missing($guid) "" - } - } - - if {[array size missing]} { - etw_load_mof_event_classes $oswbemservices {*}[array names missing] - } - - set bufhdr [etw_event_trace_logfile trace_logfile_header $bufdesc] - set timer_resolution [etw_trace_logfile_header timer_resolution $bufhdr] - set private_session [expr {0x800 & [etw_trace_logfile_header logfile_mode $bufhdr]}] - set pointer_size [etw_trace_logfile_header pointer_size $bufhdr] - - # TBD - what should provider_guid be for each event? - set provider_guid "" - - set formatted_events {} - foreach event $events { - array set hdr [mof_event_header [mof_event header $event]] - - # Formatted event must match field sequence in etw_event record - set formatted_event [list 0 $hdr(version) 0 $hdr(level) $hdr(type) 0 0 \ - $hdr(timestamp) $hdr(tid) $hdr(pid) $provider_guid] - - if {$private_session} { - lappend formatted_event [expr {$hdr(processor_time) * $timer_resolution}] 0 - } else { - lappend formatted_event [expr {$hdr(user_time) * $timer_resolution}] [expr {$hdr(kernel_time) * $timer_resolution}] - } - - if {[dict exists $_etw_event_defs $hdr(guid) $hdr(version) -definitions $hdr(type)]} { - set eventclass [dict get $_etw_event_defs $hdr(guid) $hdr(version) -classname] - set mof [dict get $_etw_event_defs $hdr(guid) $hdr(version) -definitions $hdr(type)] - set eventtypename [dict get $mof -eventtypename] - set properties [Twapi_ParseEventMofData \ - [mof_event data $event] \ - [dict get $mof -fieldtypes] \ - $pointer_size] - } elseif {[dict exists $_etw_event_defs $hdr(guid) "" -definitions $hdr(type)]} { - # If exact version not present, use one without - # a version - set eventclass [dict get $_etw_event_defs $hdr(guid) "" -classname] - set mof [dict get $_etw_event_defs $hdr(guid) "" -definitions $hdr(type)] - set eventtypename [dict get $mof -eventtypename] - set properties [Twapi_ParseEventMofData \ - [mof_event data $event] \ - [dict get $mof -fieldtypes] \ - $pointer_size] - } else { - # No definition. Create an entry so we know we already tried - # looking this up and don't keep retrying later - dict set _etw_event_defs $hdr(guid) {} - - # Nothing we can add to the event. Pass on with defaults - set eventtypename $hdr(type) - # Try to get at least the class name - if {[dict exists $_etw_event_defs $hdr(guid) $hdr(version) -classname]} { - set eventclass [dict get $_etw_event_defs $hdr(guid) $hdr(version) -classname] - } elseif {[dict exists $_etw_event_defs $hdr(guid) "" -classname]} { - set eventclass [dict get $_etw_event_defs $hdr(guid) "" -classname] - } else { - set eventclass "" - } - set properties [list _mofdata [mof_event data $event]] - } - - # eventclass -> provider_name - # TBD - should we get the Provider qualifier from Mof as provider_name? (Does it even exist?) - # mofformatteddata -> properties - # level name is not localized. Oh well, too bad - set level_name [dict* {0 {Log Always} 1 Critical 2 Error 3 Warning 4 Informational 5 Debug} $hdr(level)] - lappend formatted_event $eventclass $hdr(guid) "" $level_name $eventtypename "" "" $properties "" "" - - lappend formatted_events $formatted_event - } - - return $formatted_events -} - -proc twapi::etw_format_event_message {message properties} { - if {$message ne ""} { - set params {} - foreach {propname propval} $properties { - # Properties are always a list, even when scalars because - # there is no way of distinguishing between a scalar and - # an array of size 1 in the return values from TDH - lappend params [join $propval {, }] - } - catch {set message [format_message -fmtstring $message -params $params]} - } - return $message -} - - -proc twapi::etw_dump_to_file {args} { - array set opts [parseargs args { - {output.arg stdout} - {limit.int -1} - {format.arg csv {csv list}} - {separator.arg ,} - {fields.arg {-timecreated -levelname -providername -pid -taskname -opcodename -message}} - {filter.arg {}} - }] - - if {$opts(format) eq "csv"} { - package require csv - } - if {$opts(output) in [chan names]} { - # Writing to a channel - set outfd $opts(output) - set do_close 0 - } else { - if {[file exists $opts(output)]} { - error "File $opts(output) already exists." - } - set outfd [open $opts(output) a] - set do_close 1 - } - - set formatter [etw_open_formatter] - trap { - set varname ::twapi::_etw_dump_ctr[TwapiId] - set $varname 0; # Yes, set $varname, not set varname - set htraces {} - foreach arg $args { - if {[file exists $arg]} { - lappend htraces [etw_open_file $arg] - } else { - lappend htraces [etw_open_session $arg] - } - } - - if {$opts(format) eq "csv"} { - puts $outfd [csv::join $opts(fields) $opts(separator)] - } - if {[llength $htraces] == 0} { - return - } - # This is written using a callback to basically test the callback path - set callback [list apply { - {options outfd counter_varname max formatter bufd events} - { - array set opts $options - set events [etw_format_events $formatter $bufd $events] - foreach event [recordarray getlist $events -format dict -filter $opts(filter)] { - if {$max >= 0 && [set $counter_varname] >= $max} { - return -code break - } - array set fields $event - if {"-message" in $opts(fields)} { - if {$fields(-message) ne ""} { - set fields(-message) [etw_format_event_message $fields(-message) $fields(-properties)] - } else { - set fields(-message) "Properties: $fields(-properties)" - } - } - if {"-properties" in $opts(fields)} { - set fmtdata $fields(-properties) - if {[dict exists $fmtdata mofdata]} { - # Only show 32 bytes - binary scan [string range [dict get $fmtdata mofdata] 0 31] H* hex - dict set fmtdata mofdata [regsub -all (..) $hex {\1 }] - } - set fields(-properties) $fmtdata - } - set fmtlist {} - foreach field $opts(fields) { - lappend fmtlist $fields($field) - } - if {$opts(format) eq "csv"} { - puts $outfd [csv::join $fmtlist $opts(separator)] - } else { - puts $outfd $fmtlist - } - incr $counter_varname - } - } - } [array get opts] $outfd $varname $opts(limit) $formatter] - - # Process the events using the callback - etw_process_events -callback $callback {*}$htraces - - } finally { - unset -nocomplain $varname - foreach htrace $htraces { - etw_close_session $htrace - } - if {$do_close} { - close $outfd - } else { - flush $outfd - } - etw_close_formatter $formatter - } -} - -proc twapi::etw_dump_to_list {args} { - set htraces {} - set formatter [etw_open_formatter] - trap { - foreach arg $args { - if {[file exists $arg]} { - lappend htraces [etw_open_file $arg] - } else { - lappend htraces [etw_open_session $arg] - } - } - return [recordarray getlist [etw_format_events $formatter {*}[etw_process_events {*}$htraces]]] - } finally { - foreach htrace $htraces { - etw_close_session $htrace - } - etw_close_formatter $formatter - } -} - -proc twapi::etw_dump {args} { - set htraces {} - set formatter [etw_open_formatter] - trap { - foreach arg $args { - if {[file exists $arg]} { - lappend htraces [etw_open_file $arg] - } else { - lappend htraces [etw_open_session $arg] - } - } - return [recordarray get [etw_format_events $formatter {*}[etw_process_events {*}$htraces]]] - } finally { - foreach htrace $htraces { - etw_close_session $htrace - } - etw_close_formatter $formatter - } -} - - -proc twapi::etw_start_trace {session_name args} { - variable _etw_trace_controllers - - # Specialized for kernel debugging - {bufferingmode {} 0x400} - # Not supported until Win7 {noperprocessorbuffering {} 0x10000000} - # Not clear what conditions it can be used {usekbytesforsize {} 0x2000} - array set opts [parseargs args { - traceguid.arg - logfile.arg - buffersize.int - minbuffers.int - maxbuffers.int - maxfilesize.int - flushtimer.int - enableflags.int - {filemode.arg circular {sequential append rotate circular}} - {clockresolution.sym system {qpc 1 system 2 cpucycle 3}} - {private.bool 0 0x800} - {realtime.bool 0 0x100} - {secure.bool 0 0x80} - {privateinproc.bool 0 0x20800} - {sequence.sym none {none 0 local 0x8000 global 0x4000}} - {paged.bool 0 0x01000000} - {preallocate.bool 0 0x20} - } -maxleftover 0] - - if {!$opts(realtime) && (![info exists opts(logfile)] || $opts(logfile) eq "")} { - badargs! "Log file name must be specified if real time mode is not in effect" - } - - if {[string equal -nocase $session_name "NT Kernel Logger"] && - $opts(filemode) eq "rotate"} { - error "Option -filemode cannot have value \"rotate\" for NT Kernel Logger" - } - - set logfilemode 0 - switch -exact $opts(filemode) { - sequential { - if {[info exists opts(maxfilesize)]} { - # 1 -> EVENT_TRACE_FILE_MODE_SEQUENTIAL - set logfilemode [expr {$logfilemode | 1}] - } else { - # 0 -> EVENT_TRACE_FILE_MODE_NONE - # set logfilemode [expr {$logfilemode | 0}] - } - } - circular { - # 2 -> EVENT_TRACE_FILE_MODE_CIRCULAR - set logfilemode [expr {$logfilemode | 2}] - if {![info exists opts(maxfilesize)]} { - set opts(maxfilesize) 1; # 1MB default - } - } - rotate { - if {$opts(private) || $opts(privateinproc)} { - if {![min_os_version 6 2]} { - badargs! "Option -filemode must not be \"rotate\" for private traces" - } - } - - # 8 -> EVENT_TRACE_FILE_MODE_NEWFILE - set logfilemode [expr {$logfilemode | 8}] - if {![info exists opts(maxfilesize)]} { - set opts(maxfilesize) 1; # 1MB default - } - } - append { - if {$opts(private) || $opts(privateinproc) || $opts(realtime)} { - badargs! "Option -filemode must not be \"append\" for private or realtime traces" - } - # 4 -> EVENT_TRACE_FILE_MODE_APPEND - # Not clear what to do about maxfilesize. Keep as is for now - set logfilemode [expr {$logfilemode | 4}] - } - } - - if {![info exists opts(maxfilesize)]} { - set opts(maxfilesize) 0 - } - - if {$opts(realtime) && ($opts(private) || $opts(privateinproc))} { - badargs! "Option -realtime is incompatible with options -private and -privateinproc" - } - - foreach opt {traceguid logfile buffersize minbuffers maxbuffers flushtimer enableflags maxfilesize} { - if {[info exists opts($opt)]} { - lappend params -$opt $opts($opt) - } - } - - set logfilemode [expr {$logfilemode | $opts(sequence)}] - - set logfilemode [tcl::mathop::| $logfilemode $opts(realtime) $opts(private) $opts(privateinproc) $opts(secure) $opts(paged) $opts(preallocate)] - - lappend params -logfilemode $logfilemode - - if {$opts(filemode) eq "append" && $opts(clockresolution) != 2} { - error "Option -clockresolution must be set to 'system' if -filemode is append" - } - - if {($opts(filemode) eq "rotate" || $opts(preallocate)) && - $opts(maxfilesize) == 0} { - error "Option -maxfilesize must also be specified with -preallocate or -filemodenewfile." - } - - lappend params -clockresolution $opts(clockresolution) - - trap { - set h [StartTrace $session_name $params] - set _etw_trace_controllers($h) $session_name - return $h - } onerror {TWAPI_WIN32 5} { - return -options [trapoptions] "Access denied. This may be because the process does not have permission to create the specified logfile or because it is not running under an account permitted to control ETW traces." - } -} - -proc twapi::etw_start_kernel_trace {events args} { - - set enableflags 0 - - # Note sysconfig is a dummy event. It is always logged. - set eventmap { - process 0x00000001 - thread 0x00000002 - imageload 0x00000004 - diskio 0x00000100 - diskfileio 0x00000200 - pagefault 0x00001000 - hardfault 0x00002000 - tcpip 0x00010000 - registry 0x00020000 - dbgprint 0x00040000 - sysconfig 0x00000000 - } - - if {"diskfileio" in $events} { - lappend events diskio; # Required by diskfileio - } - - if {[min_os_version 6]} { - lappend eventmap {*}{ - processcounter 0x00000008 - contextswitch 0x00000010 - dpc 0x00000020 - interrupt 0x00000040 - systemcall 0x00000080 - diskioinit 0x00000400 - alpc 0x00100000 - splitio 0x00200000 - driver 0x00800000 - profile 0x01000000 - fileio 0x02000000 - fileioinit 0x04000000 - } - - if {"diskio" in $events} { - lappend events diskioinit - } - } - - if {[min_os_version 6 1]} { - lappend eventmap {*}{ - dispatcher 0x00000800 - virtualalloc 0x00004000 - } - } - - if {[min_os_version 6 2]} { - lappend eventmap {*}{ - vamap 0x00008000 - } - if {"sysconfig" ni $events} { - # EVENT_TRACE_FLAG_NO_SYSCONFIG - set enableflags [expr {$enableflags | 0x10000000}] - } - } - - foreach event $events { - set enableflags [expr {$enableflags | [dict! $eventmap $event]}] - } - - # Name "NT Kernel Logger" is hardcoded in Windows - # GUID is 9e814aad-3204-11d2-9a82-006008a86939 but does not need to be - # specified. Note kernel logger cannot use paged memory so - # -paged 0 is required - return [etw_start_trace "NT Kernel Logger" -enableflags $enableflags {*}$args -paged 0] -} - -proc twapi::etw_enable_provider {htrace guid enableflags level} { - set guid [_etw_provider_guid $guid] - return [EnableTrace 1 $enableflags [_etw_level_to_int $level] $guid $htrace] -} - -proc twapi::etw_disable_provider {htrace guid} { - set guid [_etw_provider_guid $guid] - return [EnableTrace 0 -1 5 $guid $htrace] -} - -proc twapi::etw_control_trace {action session args} { - variable _etw_trace_controllers - - if {[info exists _etw_trace_controllers($session)]} { - set sessionhandle $session - } else { - set sessionhandle 0 - set sessionname $session - } - - set action [dict get { - query 0 - stop 1 - update 2 - flush 3 - } $action] - - array set opts [parseargs args { - traceguid.arg - logfile.arg - maxbuffers.int - flushtimer.int - enableflags.int - realtime.bool - } -maxleftover 0] - - set params {} - - if {[info exists opts(realtime)]} { - if {$opts(realtime)} { - lappend params -logfilemode 0x100; # EVENT_TRACE_REAL_TIME_MODE - } else { - lappend params -logfilemode 0 - } - } - - if {[info exists opts(traceguid)]} { - append params -traceguid $opts(traceguid) - } - - if {[info exists sessionname]} { - lappend params -sessionname $sessionname - } - - if {$action == 2} { - # update - foreach opt {logfile flushtimer enableflags maxbuffers} { - if {[info exists opts($opt)]} { - lappend params -$opt $opts($opt) - } - } - } - - return [etw_trace_properties [ControlTrace $action $sessionhandle $params]] -} - -interp alias {} twapi::etw_update_trace {} twapi::etw_control_trace update - -proc twapi::etw_stop_trace {trace} { - variable _etw_trace_controllers - set stats [etw_control_trace stop $trace] - unset -nocomplain _etw_trace_controllers($trace) - return $stats -} - -proc twapi::etw_flush_trace {trace} { - return [etw_control_trace flush $trace] -} - -proc twapi::etw_query_trace {trace} { - set d [etw_control_trace query $trace] - set cres [lindex {{} qpc system cpucycle} [dict get $d clock_resolution]] - if {$cres ne ""} { - dict set d clock_resolution $cres - } - - #TBD - check whether -maxfilesize needs to be massaged - - return $d -} - - - -# -# Helper functions -# - - -# Return binary unicode with truncation if necessary -proc twapi::_etw_encode_limited_unicode {s {max 80}} { - if {[string length $s] > $max} { - set s "[string range $s 0 $max-3]..." - } - return [encoding convertto unicode "$s\0"] -} - -# Used for development/debug to see what all types are in use -proc twapi::_etw_get_types {} { - dict for {g gval} $::twapi::_etw_event_defs { - dict for {ver verval} $gval { - dict for {eventtype eval} [dict get $verval -definitions] { - dict for {id idval} [dict get $eval -fields] { - dict set types [dict get $idval -type] [dict get $verval -classname] $eventtype $id - } - } - } - } - return $types -} - -proc twapi::_etw_level_to_int {level} { - return [dict* {verbose 5 information 4 info 4 informational 4 warning 3 error 2 fatal 1 critical 1} [string tolower $level]] -} - -# Map provider guid/name to guid -proc twapi::_etw_provider_guid {lookup} { - if {[Twapi_IsValidGUID $lookup]} { - return $lookup - } - set guid [etw_get_provider_guid $lookup] - if {$guid eq ""} { - badargs! "Provider \"$lookup\" not found." - } - return $guid -} +# +# Copyright (c) 2012-2014 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + # GUID's and event types for ETW. + variable _etw_mof + array set _etw_mof { + provider_name "TwapiETWProvider" + provider_guid "{B358E9D9-4D82-4A82-A129-BAC098C54746}" + eventclass_name "TwapiETWEventClass" + eventclass_guid "{D5B52E95-8447-40C1-B316-539894449B36}" + } + + # So we don't pollute namespace with temp vars + apply [list defs { + foreach {key val} $defs { + proc etw_twapi_$key {} "return $val" + } + } [namespace current]] [array get _etw_mof] + + # Cache of event definitions for parsing MOF events. Nested dictionary + # with the following structure (uppercase keys are variables, + # lower case are constant/tokens, "->" is nested dict, "-" is scalar): + # EVENTCLASSGUID -> + # classname - name of the class + # definitions -> + # VERSION -> + # EVENTTYPE -> + # eventtype - same as EVENTTYPE + # eventtypename - name / description for the event type + # fieldtypes - ordered list of field types for that event + # fields -> + # FIELDINDEX -> + # type - the field type in string format + # fieldtype - the corresponding field type numeric value + # extension - the MoF extension qualifier for the field + # + # The cache assumes that MOF event definitions are globally identical + # (ie. same on local and remote systems) + variable _etw_event_defs + set _etw_event_defs [dict create] + + # Keeps track of open trace handles for reading + variable _etw_trace_consumers + array set _etw_trace_consumers {} + + # Keep track of trace controller handles. Note we do not always + # need a handle for controller actions. We can also control based + # on name, for example if some other process has started the trace + variable _etw_trace_controllers + array set _etw_trace_controllers {} + + # + # These record definitions match the lists constructed in the ETW C code + # Note these are purposely formatted on single line so the record fieldnames + # print better. + + # Buffer header (EVENT_TRACE_LOGFILE) + record etw_event_trace_logfile {logfile logger_name current_time buffers_read trace_logfile_header buffer_size filled kernel_trace} + + # TRACE_LOGFILE_HEADER + record etw_trace_logfile_header {buffer_size version_major version_minor version_submajor version_subminor provider_version processor_count end_time timer_resolution max_file_size logfile_mode buffers_written pointer_size events_lost cpu_mhz time_zone boot_time perf_frequency start_time reserved_flags buffers_lost } + + # TDH based event definitions + + record tdh_event { header buffer_context extended_data data } + + record tdh_event_header { flags event_property tid pid timestamp + kernel_time user_time processor_time activity_id descriptor provider_guid} + record tdh_event_buffer_context { processor logger_id } + record tdh_event_data {provider_guid event_guid decoder provider_name level_name channel_name keyword_names task_name opcode_name message localized_provider_name activity_id related_activity_id properties flags} + + record tdh_event_data_descriptor {id version channel level opcode task keywords} + + # Definitions for EVENT_TRACE_LOGFILE + record tdh_buffer { logfile logger current_time buffers_read header buffer_size filled kernel_trace } + + record tdh_logfile_header { size major_version minor_version sub_version subminor_version provider_version processor_count end_time resolution max_file_size logfile_mode buffers_written pointer_size events_lost cpu_mhz timezone boot_time perf_frequency start_time reserved_flags buffers_lost } + + # MOF based event definitions + record mof_event {header instance_id parent_instance_id parent_guid data} + record mof_event_header {type level version tid pid timestamp guid kernel_time user_time processor_time} + + # Standard app visible event definitions. These are made + # compatible with the evt_* routines + record etw_event {-eventid -version -channel -level -opcode -task -keywordmask -timecreated -tid -pid -providerguid -usertime -kerneltime -providername -eventguid -channelname -levelname -opcodename -taskname -keywords -properties -message -sid} + + # Record for EVENT_TRACE_PROPERTIES + # TBD - document + record etw_trace_properties {logfile trace_name trace_guid buffer_size min_buffers max_buffers max_file_size logfile_mode flush_timer enable_flags clock_resolution age_limit buffer_count free_buffers events_lost buffers_written log_buffers_lost real_time_buffers_lost logger_tid} +} + + +proc twapi::etw_get_traces {args} { + parseargs args {detail} -setvars -maxleftover 0 + set sessions {} + foreach sess [QueryAllTraces] { + set name [etw_trace_properties trace_name $sess] + if {$detail} { + lappend sessions [etw_trace_properties $sess] + } else { + lappend sessions $name + } + } + return $sessions +} + +if {[twapi::min_os_version 6]} { + proc twapi::etw_get_provider_guid {name} { + return [lindex [Twapi_TdhEnumerateProviders $name] 0] + } + proc twapi::etw_get_providers {args} { + parseargs args { + detail + {types.arg {mof xml}} + } -setvars -maxleftover 0 + set providers {} + foreach rec [Twapi_TdhEnumerateProviders] { + lassign $rec guid type name + set type [dict* {0 xml 1 mof} $type] + if {$type in $types} { + if {$detail} { + lappend providers [list guid $guid type $type name $name] + } else { + lappend providers $name + } + } + } + return $providers + } +} else { + twapi::proc* twapi::etw_get_provider_guid {lookup_name} { + package require twapi_wmi + } { + set wmi [wmi_root -root wmi] + set oclasses {} + set providers {} + # TBD - check if ExecQuery would be faster + trap { + # All providers are direct subclasses of the EventTrace class + set oclasses [wmi_collect_classes $wmi -ancestor EventTrace -shallow] + foreach ocls $oclasses { + set quals [$ocls Qualifiers_] + trap { + set name [$quals -with {{Item Description}} -invoke Value 2 {}] + if {[string equal -nocase $name $lookup_name]} { + return [$quals -with {{Item Guid}} -invoke Value 2 {}] + } + } finally { + $quals -destroy + } + } + } finally { + foreach ocls $oclasses {$ocls -destroy} + $wmi -destroy + } + return "" + } + + twapi::proc* twapi::etw_get_providers {args} { + package require twapi_wmi + } { + parseargs args { detail {types.arg {mof xml}} } -setvars -maxleftover 0 + if {"mof" ni $types} { + return {}; # Older systems do not have xml based providers + } + set wmi [wmi_root -root wmi] + set oclasses {} + set providers {} + # TBD - check if ExecQuery would be faster + trap { + # All providers are direct subclasses of the EventTrace class + set oclasses [wmi_collect_classes $wmi -ancestor EventTrace -shallow] + foreach ocls $oclasses { + set quals [$ocls Qualifiers_] + trap { + set name [$quals -with {{Item Description}} -invoke Value 2 {}] + set guid [$quals -with {{Item Guid}} -invoke Value 2 {}] + if {$detail} { + lappend providers [list guid $guid type mof name $name] + } else { + lappend providers $name + } + } finally { + $quals -destroy + } + } + } finally { + foreach ocls $oclasses {$ocls -destroy} + $wmi -destroy + } + return $providers + } +} + +twapi::proc* twapi::etw_install_twapi_mof {} { + package require twapi_wmi +} { + variable _etw_mof + + # MOF definition for our ETW trace event. This is loaded into + # the system WMI registry so event readers can decode our events + # + # Note all strings are NullTerminated and not Counted so embedded nulls + # will not be handled correctly. The problem with using Counted strings + # is that the MSDN docs are inconsistent as to whether the count + # is number of *bytes* or number of *characters* and the existing tools + # are similarly confused. We avoid this by choosing null terminated + # strings despite the embedded nulls drawback. + # TBD - revisit this and see if counted can always be treated as + # bytes and not characters. + + # We do not want the pure binary builds think #pragma is a comment + # and remove the line! Bug 170 + #createtmfile-disable-compaction + set mof_template { + #pragma namespace("\\\\.\\root\\wmi") + + // Keep Description same as provider_name as that is how + // TDH library identifies it. Else there will be a mismatch + // between TdhEnumerateProviders and how we internally assume is + // the provider name + [dynamic: ToInstance, Description("@provider_name"), + Guid("@provider_guid")] + class @provider_name : EventTrace + { + }; + + [dynamic: ToInstance, Description("TWAPI ETW event class"): Amended, + Guid("@eventclass_guid")] + class @eventclass_name : @provider_name + { + }; + + // NOTE: The EventTypeName is REQUIRED else the MS LogParser app + // crashes (even though it should not) + + [dynamic: ToInstance, Description("TWAPI log message"): Amended, + EventType(1), EventTypeName("Message")] + class @eventclass_name_Message : @eventclass_name + { + [WmiDataId(1), Description("Log message"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Message; + }; + + [dynamic: ToInstance, Description("TWAPI variable trace"): Amended, + EventType(2), EventTypeName("VariableTrace")] + class @eventclass_name_VariableTrace : @eventclass_name + { + [WmiDataId(1), Description("Operation"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Operation; + [WmiDataId(2), Description("Variable name"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Name; + [WmiDataId(3), Description("Array index"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Index; + [WmiDataId(4), Description("Value"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Value; + [WmiDataId(5), Description("Context"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Context; + }; + + [dynamic: ToInstance, Description("TWAPI execution trace"): Amended, + EventType(3), EventTypeName("ExecutionTrace")] + class @eventclass_name_ExecutionTrace : @eventclass_name + { + [WmiDataId(1), Description("Operation"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Operation; + [WmiDataId(2), Description("Executed command"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Command; + [WmiDataId(3), Description("Status code"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Code; + [WmiDataId(4), Description("Result"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Result; + [WmiDataId(5), Description("Context"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Context; + }; + + [dynamic: ToInstance, Description("TWAPI command trace"): Amended, + EventType(4), EventTypeName("CommandTrace")] + class @eventclass_name_CommandTrace : @eventclass_name + { + [WmiDataId(1), Description("Operation"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Operation; + [WmiDataId(2), Description("Old command name"): Amended, read, StringTermination("NullTerminated"), Format("w")] string OldName; + [WmiDataId(3), Description("New command name"): Amended, read, StringTermination("NullTerminated"), Format("w")] string NewName; + [WmiDataId(4), Description("Context"): Amended, read, StringTermination("NullTerminated"), Format("w")] string Context; + }; + } + + #createtmfile-enable-compaction + + set mof [string map \ + [list @provider_name $_etw_mof(provider_name) \ + @provider_guid $_etw_mof(provider_guid) \ + @eventclass_name $_etw_mof(eventclass_name) \ + @eventclass_guid $_etw_mof(eventclass_guid) \ + ] $mof_template] + + set mofc [twapi::IMofCompilerProxy new] + twapi::trap { + $mofc CompileBuffer $mof + } finally { + $mofc Release + } +} + +proc twapi::etw_uninstall_twapi_mof {} { + variable _etw_mof + + set wmi [twapi::_wmi wmi] + trap { + set omof [$wmi Get $_etw_mof(provider_name)] + $omof Delete_ + } finally { + if {[info exists omof]} { + $omof destroy + } + $wmi destroy + } +} + +proc twapi::etw_twapi_provider_register {} { + variable _etw_mof + return [twapi::RegisterTraceGuids $_etw_mof(provider_guid) $_etw_mof(eventclass_guid)] +} + +proc twapi::etw_log_message {htrace message {level 4}} { + set level [_etw_level_to_int $level] + if {[etw_provider_enable_level] >= $level} { + # Must match Message event type in MoF definition + # 1 -> event type for Message + TraceEvent $htrace 1 $level [encoding convertto unicode "$message\0"] + } +} + +proc twapi::etw_variable_tracker {htrace name1 name2 op} { + switch -exact -- $op { + array - + unset { set var "" } + default { + if {$name2 eq ""} { + upvar 1 $name1 var + } else { + upvar 1 $name1($name2) var + } + } + } + + if {[info level] > 1} { + set context [info level -1] + } else { + set context "" + } + + # Must match VariableTrace event type in MoF definition + TraceEvent $htrace 2 0 \ + [encoding convertto unicode "$op\0$name1\0$name2\0$var\0"] \ + [_etw_encode_limited_unicode $context] +} + + +proc twapi::etw_execution_tracker {htrace command args} { + set op [lindex $args end] + + switch -exact -- $op { + enter - + enterstep { + set code "" + set result "" + } + leave - + leavestep { + lassign $args code result + } + } + + if {[info level] > 1} { + set context [info level -1] + } else { + set context "" + } + + # Must match Execution event type in MoF definition + TraceEvent $htrace 3 0 \ + [encoding convertto unicode "$op\0"] \ + [_etw_encode_limited_unicode $command] \ + [encoding convertto unicode "$code\0"] \ + [_etw_encode_limited_unicode $result] \ + [_etw_encode_limited_unicode $context] +} + + +proc twapi::etw_command_tracker {htrace oldname newname op} { + if {[info level] > 1} { + set context [info level -1] + } else { + set context "" + } + # Must match CommandTrace event type in MoF definition + TraceEvent $htrace 4 0 \ + [encoding convertto unicode "$op\0$oldname\0$newname\0"] \ + [_etw_encode_limited_unicode $context] +} + +proc twapi::etw_parse_mof_event_class {ocls} { + # Returns a dict + # First level key - event type (integer) + # See description of _etw_event_defs for rest of the structure + + set result [dict create] + + # Iterate over the subclasses, collecting the event metadata + # Create a forward only enumerator for efficiency + # wbemFlagUseAmendedQualifiers|wbemFlagReturnImmediately|wbemFlagForwardOnly + # wbemQueryFlagsShallow + # -> 0x20031 + $ocls -with {{SubClasses_ 0x20031}} -iterate -cleanup osub { + # The subclass must have the eventtype property + # We fetch as a raw value so we can tell the + # original type + if {![catch { + $osub -with { + Qualifiers_ + {Item EventType} + } -invoke Value 2 {} -raw 1 + } event_types]} { + + # event_types is a raw value with a type descriptor as elem 0 + if {[variant_type $event_types] & 0x2000} { + # It is VT_ARRAY so value is already a list + set event_types [variant_value $event_types 0 0 0] + } else { + set event_types [list [variant_value $event_types 0 0 0]] + } + + set event_type_names {} + catch { + set event_type_names [$osub -with { + Qualifiers_ + {Item EventTypeName} + } -invoke Value 2 {} -raw 1] + # event_type_names is a raw value with a type descriptor as elem 0 + # It is IMPORTANT to check this else we cannot distinguish + # between a array (list) and a string with spaces + if {[variant_type $event_type_names] & 0x2000} { + # It is VT_ARRAY so value is already a list + set event_type_names [variant_value $event_type_names 0 0 0] + } else { + # Scalar value. Make into a list + set event_type_names [list [variant_value $event_type_names 0 0 0]] + } + } + + # The subclass has a EventType property. Pick up the + # field definitions. + set fields [dict create] + $osub -with Properties_ -iterate -cleanup oprop { + set quals [$oprop Qualifiers_] + # Event fields will have a WmiDataId qualifier + if {![catch {$quals -with {{Item WmiDataId}} Value} wmidataid]} { + # Yep this is a field, figure out its type + set type [_etw_decipher_mof_event_field_type $oprop $quals] + dict set type -fieldname [$oprop -get Name] + dict set fields $wmidataid $type + } + $quals destroy + } + + # Process the records to put the fields in order based on + # their wmidataid. If any info is missing or inconsistent + # we will mark the whole event type class has undecodable. + # Ids begin from 1. + set fieldtypes {} + for {set id 1} {$id <= [dict size $fields]} {incr id} { + if {![dict exists $fields $id]} { + # Discard all type info - missing type info + debuglog "Missing id $id for event type(s) $event_types for EventTrace Mof Class [$ocls -with {{SystemProperties_} {Item __CLASS}} Value]" + set fieldtypes {} + break; + } + lappend fieldtypes [dict get $fields $id -fieldname] [dict get $fields $id -fieldtype] + } + + foreach event_type $event_types event_type_name $event_type_names { + dict set result -definitions $event_type [dict create -eventtype $event_type -eventtypename $event_type_name -fields $fields -fieldtypes $fieldtypes] + } + } + } + + if {[dict size $result] == 0} { + return {} + } else { + dict set result -classname [$ocls -with {SystemProperties_ {Item __CLASS}} Value] + return $result + } +} + +# Deciphers an event field type + +proc twapi::_etw_decipher_mof_event_field_type {oprop oquals} { + # Maps event field type strings to enums to pass to the C code + # 0 should be unmapped. Note some are duplicates because they + # are the same format. Some are legacy formats not explicitly documented + # in MSDN but found in the sample code. + # Reference - Event Tracing MOF Qualifiers http://msdn.microsoft.com/en-us/library/windows/desktop/aa363800(v=vs.85).aspx + set etw_fieldtypes { + string 1 + stringnullterminated 1 + wstring 2 + wstringnullterminated 2 + stringcounted 3 + stringreversecounted 4 + wstringcounted 5 + wstringreversecounted 6 + boolean 7 + sint8 8 + uint8 9 + csint8 10 + cuint8 11 + sint16 12 + uint16 13 + uint32 14 + sint32 15 + sint64 16 + uint64 17 + xsint16 18 + xuint16 19 + xsint32 20 + xuint32 21 + xsint64 22 + xuint64 23 + real32 24 + real64 25 + object 26 + char16 27 + uint8guid 28 + objectguid 29 + objectipaddrv4 30 + uint32ipaddr 30 + objectipaddr 30 + objectipaddrv6 31 + objectvariant 32 + objectsid 33 + uint64wmitime 34 + objectwmitime 35 + uint16port 38 + objectport 39 + datetime 40 + stringnotcounted 41 + wstringnotcounted 42 + pointer 43 + sizet 43 + } + + # On any errors, we will set type to unknown or unsupported + set type unknown + set quals(extension) ""; # Hint for formatting for display + + if {![catch { + $oquals -with {{Item Pointer}} Value + }]} { + # Actual value does not matter + # If the Pointer qualifier exists, ignore everything else + set type pointer + } elseif {![catch { + $oquals -with {{Item PointerType}} Value + }]} { + # Actual value does not matter + # Some apps mistakenly use PointerType instead of Pointer + set type pointer + } else { + catch { + set type [string tolower [$oquals -with {{Item CIMTYPE}} Value]] + + # The following qualifiers may or may not exist + # TBD - not all may be required to be retrieved + # NOTE: MSDN says some qualifiers are case sensitive! + foreach qual {BitMap BitValues Extension Format Pointer StringTermination ValueMap Values ValueType XMLFragment} { + # catch in case it does not exist + set lqual [string tolower $qual] + set quals($lqual) "" + catch { + set quals($lqual) [$oquals -with [list [list Item $qual]] Value] + } + } + set type [string tolower "$quals(format)${type}$quals(stringtermination)"] + set quals(extension) [string tolower $quals(extension)] + # Not all extensions affect how the event field is extracted + # e.g. the noprint value + if {$quals(extension) in {ipaddr ipaddrv4 ipaddrv6 port variant wmitime guid sid}} { + append type $quals(extension) + } elseif {$quals(extension) eq "sizet"} { + set type sizet + } + } + } + + # Cannot handle arrays yet - TBD + if {[$oprop -get IsArray]} { + set type "arrayof$type" + } + + if {![dict exists $etw_fieldtypes $type]} { + set fieldtype 0 + } else { + set fieldtype [dict get $etw_fieldtypes $type] + } + + return [dict create -type $type -fieldtype $fieldtype -extension $quals(extension)] +} + +proc twapi::etw_find_mof_event_classes {oswbemservices args} { + # Return all classes where a GUID or name matches + + # To avoid iterating the tree multiple times, separate out the guids + # and the names and use separator comparators + + set guids {} + set names {} + + foreach arg $args { + if {[Twapi_IsValidGUID $arg]} { + # GUID's can be multiple format, canonicalize for lsearch + lappend guids [canonicalize_guid $arg] + } else { + lappend names $arg + } + } + + # Note there can be multiple versions sharing a single guid so + # we cannot use the wmi_collect_classes "-first" option to stop the + # search when one is found. + + set name_matcher [lambda* {names val} { + ::tcl::mathop::>= [lsearch -exact -nocase $names $val] 0 + } :: $names] + set guid_matcher [lambda* {guids val} { + ::tcl::mathop::>= [lsearch -exact -nocase $guids $val] 0 + } :: $guids] + + set named_classes {} + if {[llength $names]} { + foreach name $names { + catch {lappend named_classes [$oswbemservices Get $name]} + } + } + + if {[llength $guids]} { + set guid_classes [wmi_collect_classes $oswbemservices -ancestor EventTrace -matchqualifiers [list Guid $guid_matcher]] + } else { + set guid_classes {} + } + + return [concat $guid_classes $named_classes] +} + +proc twapi::etw_get_all_mof_event_classes {oswbemservices} { + return [twapi::wmi_collect_classes $oswbemservices -ancestor EventTrace -matchqualifiers [list Guid ::twapi::true]] +} + +proc twapi::etw_load_mof_event_class_obj {oswbemservices ocls} { + variable _etw_event_defs + set quals [$ocls Qualifiers_] + trap { + set guid [$quals -with {{Item Guid}} Value] + set vers "" + catch {set vers [$quals -with {{Item EventVersion}} Value]} + set def [etw_parse_mof_event_class $ocls] + # Class may be a provider, not a event class in which case + # def will be empty + if {[dict size $def]} { + dict set _etw_event_defs [canonicalize_guid $guid] $vers $def + } + } finally { + $quals destroy + } +} + +proc twapi::etw_load_mof_event_classes {oswbemservices args} { + if {[llength $args] == 0} { + set oclasses [etw_get_all_mof_event_classes $oswbemservices] + } else { + set oclasses [etw_find_mof_event_classes $oswbemservices {*}$args] + } + + foreach ocls $oclasses { + trap { + etw_load_mof_event_class_obj $oswbemservices $ocls + } finally { + $ocls destroy + } + } +} + +proc twapi::etw_open_file {path} { +# TBD - PROCESS_TRACE_MODE_RAW_TIMESTAMP + variable _etw_trace_consumers + + set path [file normalize $path] + + set htrace [OpenTrace $path 0] + set _etw_trace_consumers($htrace) $path + return $htrace +} + +proc twapi::etw_open_session {sessionname} { +# TBD - PROCESS_TRACE_MODE_RAW_TIMESTAMP + variable _etw_trace_consumers + + set htrace [OpenTrace $sessionname 1] + set _etw_trace_consumers($htrace) $sessionname + return $htrace +} + +proc twapi::etw_close_session {htrace} { + variable _etw_trace_consumers + + if {! [info exists _etw_trace_consumers($htrace)]} { + badargs! "Cannot find trace session with handle $htrace" + } + + CloseTrace $htrace + unset _etw_trace_consumers($htrace) + return +} + + +proc twapi::etw_process_events {args} { + array set opts [parseargs args { + callback.arg + start.arg + end.arg + } -nulldefault] + + if {[llength $args] == 0} { + error "At least one trace handle must be specified." + } + + return [ProcessTrace $args $opts(callback) $opts(start) $opts(end)] +} + +proc twapi::etw_open_formatter {} { + variable _etw_formatters + + if {[etw_force_mof] || ![twapi::min_os_version 6 0]} { + uplevel #0 package require twapi_wmi + # Need WMI MOF definitions + set id mof[TwapiId] + dict set _etw_formatters $id OSWBemServices [wmi_root -root wmi] + } else { + # Just a dummy if using a TDH based api + set id tdh[TwapiId] + # Nothing to set as yet but for consistency with MOF implementation + dict set _etw_formatters $id {} + } + return $id +} + +proc twapi::etw_close_formatter {formatter} { + variable _etw_formatters + if {[dict exists $_etw_formatters $formatter OSWBemServices]} { + [dict get $_etw_formatters $formatter OSWBemServices] -destroy + } + + dict unset _etw_formatters $formatter + if {[dict size $_etw_formatters] == 0} { + variable _etw_event_defs + # No more formatters + # Clear out event defs cache which can be quite large + # Really only needed for mof but doesn't matter + set _etw_event_defs {} + } + + return +} + +proc twapi::etw_format_events {formatter args} { + variable _etw_formatters + + if {![dict exists $_etw_formatters $formatter]} { + # We could actually just init ourselves but we want to force + # consistency and caller to release wmi COM object + badargs! "Invalid ETW formatter id \"$formatter\"" + } + + set events {} + if {[dict exists $_etw_formatters $formatter OSWBemServices]} { + set oswbemservices [dict get $_etw_formatters $formatter OSWBemServices] + foreach {bufd rawevents} $args { + lappend events [_etw_format_mof_events $oswbemservices $bufd $rawevents] + } + } else { + foreach {bufd rawevents} $args { + lappend events [_etw_format_tdh_events $bufd $rawevents] + } + } + + # Return as a recordarray + return [list [etw_event] [lconcat {*}$events]] +} + +proc twapi::_etw_format_tdh_events {bufdesc events} { + + set bufhdr [etw_event_trace_logfile trace_logfile_header $bufdesc] + set timer_resolution [etw_trace_logfile_header timer_resolution $bufhdr] + set private_session [expr {0x800 & [etw_trace_logfile_header logfile_mode $bufhdr]}] + set pointer_size [etw_trace_logfile_header pointer_size $bufhdr] + + set formatted_events {} + foreach event $events { + array set fields [tdh_event $event] + set formatted_event [tdh_event_header descriptor $fields(header)] + # Do not select provider_guid from header as for TDH it needs to come + # from the provider_guid in the data portion. + lappend formatted_event {*}[tdh_event_header select $fields(header) {timestamp tid pid}] + lappend formatted_event {*}[tdh_event_data select $fields(data) provider_guid] + if {$private_session} { + lappend formatted_event [expr {[tdh_event_header processor_time $fields(header)] * $timer_resolution}] 0 + } else { + lappend formatted_event [expr {[tdh_event_header user_time $fields(header)] * $timer_resolution}] [expr {[tdh_event_header kernel_time $fields(header)] * $timer_resolution}] + } + lappend formatted_event {*}[tdh_event_data select $fields(data) {provider_name event_guid channel_name level_name opcode_name task_name keyword_names properties message}] [dict* $fields(extended_data) sid ""] + + lappend formatted_events $formatted_event + } + return $formatted_events +} + +proc twapi::_etw_format_mof_events {oswbemservices bufdesc events} { + variable _etw_event_defs + + # TBD - it may be faster to special case NT kernel events as per + # the structures defined in http://msdn.microsoft.com/en-us/library/windows/desktop/aa364083(v=vs.85).aspx + # However, the MSDN warns that structures should not be created from + # MOF classes as alignment restrictions might be different + array set missing {} + foreach event $events { + set guid [mof_event_header guid [mof_event header $event]] + if {! [dict exists $_etw_event_defs $guid]} { + set missing($guid) "" + } + } + + if {[array size missing]} { + etw_load_mof_event_classes $oswbemservices {*}[array names missing] + } + + set bufhdr [etw_event_trace_logfile trace_logfile_header $bufdesc] + set timer_resolution [etw_trace_logfile_header timer_resolution $bufhdr] + set private_session [expr {0x800 & [etw_trace_logfile_header logfile_mode $bufhdr]}] + set pointer_size [etw_trace_logfile_header pointer_size $bufhdr] + + # TBD - what should provider_guid be for each event? + set provider_guid "" + + set formatted_events {} + foreach event $events { + array set hdr [mof_event_header [mof_event header $event]] + + # Formatted event must match field sequence in etw_event record + set formatted_event [list 0 $hdr(version) 0 $hdr(level) $hdr(type) 0 0 \ + $hdr(timestamp) $hdr(tid) $hdr(pid) $provider_guid] + + if {$private_session} { + lappend formatted_event [expr {$hdr(processor_time) * $timer_resolution}] 0 + } else { + lappend formatted_event [expr {$hdr(user_time) * $timer_resolution}] [expr {$hdr(kernel_time) * $timer_resolution}] + } + + if {[dict exists $_etw_event_defs $hdr(guid) $hdr(version) -definitions $hdr(type)]} { + set eventclass [dict get $_etw_event_defs $hdr(guid) $hdr(version) -classname] + set mof [dict get $_etw_event_defs $hdr(guid) $hdr(version) -definitions $hdr(type)] + set eventtypename [dict get $mof -eventtypename] + set properties [Twapi_ParseEventMofData \ + [mof_event data $event] \ + [dict get $mof -fieldtypes] \ + $pointer_size] + } elseif {[dict exists $_etw_event_defs $hdr(guid) "" -definitions $hdr(type)]} { + # If exact version not present, use one without + # a version + set eventclass [dict get $_etw_event_defs $hdr(guid) "" -classname] + set mof [dict get $_etw_event_defs $hdr(guid) "" -definitions $hdr(type)] + set eventtypename [dict get $mof -eventtypename] + set properties [Twapi_ParseEventMofData \ + [mof_event data $event] \ + [dict get $mof -fieldtypes] \ + $pointer_size] + } else { + # No definition. Create an entry so we know we already tried + # looking this up and don't keep retrying later + dict set _etw_event_defs $hdr(guid) {} + + # Nothing we can add to the event. Pass on with defaults + set eventtypename $hdr(type) + # Try to get at least the class name + if {[dict exists $_etw_event_defs $hdr(guid) $hdr(version) -classname]} { + set eventclass [dict get $_etw_event_defs $hdr(guid) $hdr(version) -classname] + } elseif {[dict exists $_etw_event_defs $hdr(guid) "" -classname]} { + set eventclass [dict get $_etw_event_defs $hdr(guid) "" -classname] + } else { + set eventclass "" + } + set properties [list _mofdata [mof_event data $event]] + } + + # eventclass -> provider_name + # TBD - should we get the Provider qualifier from Mof as provider_name? (Does it even exist?) + # mofformatteddata -> properties + # level name is not localized. Oh well, too bad + set level_name [dict* {0 {Log Always} 1 Critical 2 Error 3 Warning 4 Informational 5 Debug} $hdr(level)] + lappend formatted_event $eventclass $hdr(guid) "" $level_name $eventtypename "" "" $properties "" "" + + lappend formatted_events $formatted_event + } + + return $formatted_events +} + +proc twapi::etw_format_event_message {message properties} { + if {$message ne ""} { + set params {} + foreach {propname propval} $properties { + # Properties are always a list, even when scalars because + # there is no way of distinguishing between a scalar and + # an array of size 1 in the return values from TDH + lappend params [join $propval {, }] + } + catch {set message [format_message -fmtstring $message -params $params]} + } + return $message +} + + +proc twapi::etw_dump_to_file {args} { + array set opts [parseargs args { + {output.arg stdout} + {limit.int -1} + {format.arg csv {csv list}} + {separator.arg ,} + {fields.arg {-timecreated -levelname -providername -pid -taskname -opcodename -message}} + {filter.arg {}} + }] + + if {$opts(format) eq "csv"} { + package require csv + } + if {$opts(output) in [chan names]} { + # Writing to a channel + set outfd $opts(output) + set do_close 0 + } else { + if {[file exists $opts(output)]} { + error "File $opts(output) already exists." + } + set outfd [open $opts(output) a] + set do_close 1 + } + + set formatter [etw_open_formatter] + trap { + set varname ::twapi::_etw_dump_ctr[TwapiId] + set $varname 0; # Yes, set $varname, not set varname + set htraces {} + foreach arg $args { + if {[file exists $arg]} { + lappend htraces [etw_open_file $arg] + } else { + lappend htraces [etw_open_session $arg] + } + } + + if {$opts(format) eq "csv"} { + puts $outfd [csv::join $opts(fields) $opts(separator)] + } + if {[llength $htraces] == 0} { + return + } + # This is written using a callback to basically test the callback path + set callback [list apply { + {options outfd counter_varname max formatter bufd events} + { + array set opts $options + set events [etw_format_events $formatter $bufd $events] + foreach event [recordarray getlist $events -format dict -filter $opts(filter)] { + if {$max >= 0 && [set $counter_varname] >= $max} { + return -code break + } + array set fields $event + if {"-message" in $opts(fields)} { + if {$fields(-message) ne ""} { + set fields(-message) [etw_format_event_message $fields(-message) $fields(-properties)] + } else { + set fields(-message) "Properties: $fields(-properties)" + } + } + if {"-properties" in $opts(fields)} { + set fmtdata $fields(-properties) + if {[dict exists $fmtdata mofdata]} { + # Only show 32 bytes + binary scan [string range [dict get $fmtdata mofdata] 0 31] H* hex + dict set fmtdata mofdata [regsub -all (..) $hex {\1 }] + } + set fields(-properties) $fmtdata + } + set fmtlist {} + foreach field $opts(fields) { + lappend fmtlist $fields($field) + } + if {$opts(format) eq "csv"} { + puts $outfd [csv::join $fmtlist $opts(separator)] + } else { + puts $outfd $fmtlist + } + incr $counter_varname + } + } + } [array get opts] $outfd $varname $opts(limit) $formatter] + + # Process the events using the callback + etw_process_events -callback $callback {*}$htraces + + } finally { + unset -nocomplain $varname + foreach htrace $htraces { + etw_close_session $htrace + } + if {$do_close} { + close $outfd + } else { + flush $outfd + } + etw_close_formatter $formatter + } +} + +proc twapi::etw_dump_to_list {args} { + set htraces {} + set formatter [etw_open_formatter] + trap { + foreach arg $args { + if {[file exists $arg]} { + lappend htraces [etw_open_file $arg] + } else { + lappend htraces [etw_open_session $arg] + } + } + return [recordarray getlist [etw_format_events $formatter {*}[etw_process_events {*}$htraces]]] + } finally { + foreach htrace $htraces { + etw_close_session $htrace + } + etw_close_formatter $formatter + } +} + +proc twapi::etw_dump {args} { + set htraces {} + set formatter [etw_open_formatter] + trap { + foreach arg $args { + if {[file exists $arg]} { + lappend htraces [etw_open_file $arg] + } else { + lappend htraces [etw_open_session $arg] + } + } + return [recordarray get [etw_format_events $formatter {*}[etw_process_events {*}$htraces]]] + } finally { + foreach htrace $htraces { + etw_close_session $htrace + } + etw_close_formatter $formatter + } +} + + +proc twapi::etw_start_trace {session_name args} { + variable _etw_trace_controllers + + # Specialized for kernel debugging - {bufferingmode {} 0x400} + # Not supported until Win7 {noperprocessorbuffering {} 0x10000000} + # Not clear what conditions it can be used {usekbytesforsize {} 0x2000} + array set opts [parseargs args { + traceguid.arg + logfile.arg + buffersize.int + minbuffers.int + maxbuffers.int + maxfilesize.int + flushtimer.int + enableflags.int + {filemode.arg circular {sequential append rotate circular}} + {clockresolution.sym system {qpc 1 system 2 cpucycle 3}} + {private.bool 0 0x800} + {realtime.bool 0 0x100} + {secure.bool 0 0x80} + {privateinproc.bool 0 0x20800} + {sequence.sym none {none 0 local 0x8000 global 0x4000}} + {paged.bool 0 0x01000000} + {preallocate.bool 0 0x20} + } -maxleftover 0] + + if {!$opts(realtime) && (![info exists opts(logfile)] || $opts(logfile) eq "")} { + badargs! "Log file name must be specified if real time mode is not in effect" + } + + if {[string equal -nocase $session_name "NT Kernel Logger"] && + $opts(filemode) eq "rotate"} { + error "Option -filemode cannot have value \"rotate\" for NT Kernel Logger" + } + + set logfilemode 0 + switch -exact $opts(filemode) { + sequential { + if {[info exists opts(maxfilesize)]} { + # 1 -> EVENT_TRACE_FILE_MODE_SEQUENTIAL + set logfilemode [expr {$logfilemode | 1}] + } else { + # 0 -> EVENT_TRACE_FILE_MODE_NONE + # set logfilemode [expr {$logfilemode | 0}] + } + } + circular { + # 2 -> EVENT_TRACE_FILE_MODE_CIRCULAR + set logfilemode [expr {$logfilemode | 2}] + if {![info exists opts(maxfilesize)]} { + set opts(maxfilesize) 1; # 1MB default + } + } + rotate { + if {$opts(private) || $opts(privateinproc)} { + if {![min_os_version 6 2]} { + badargs! "Option -filemode must not be \"rotate\" for private traces" + } + } + + # 8 -> EVENT_TRACE_FILE_MODE_NEWFILE + set logfilemode [expr {$logfilemode | 8}] + if {![info exists opts(maxfilesize)]} { + set opts(maxfilesize) 1; # 1MB default + } + } + append { + if {$opts(private) || $opts(privateinproc) || $opts(realtime)} { + badargs! "Option -filemode must not be \"append\" for private or realtime traces" + } + # 4 -> EVENT_TRACE_FILE_MODE_APPEND + # Not clear what to do about maxfilesize. Keep as is for now + set logfilemode [expr {$logfilemode | 4}] + } + } + + if {![info exists opts(maxfilesize)]} { + set opts(maxfilesize) 0 + } + + if {$opts(realtime) && ($opts(private) || $opts(privateinproc))} { + badargs! "Option -realtime is incompatible with options -private and -privateinproc" + } + + foreach opt {traceguid logfile buffersize minbuffers maxbuffers flushtimer enableflags maxfilesize} { + if {[info exists opts($opt)]} { + lappend params -$opt $opts($opt) + } + } + + set logfilemode [expr {$logfilemode | $opts(sequence)}] + + set logfilemode [tcl::mathop::| $logfilemode $opts(realtime) $opts(private) $opts(privateinproc) $opts(secure) $opts(paged) $opts(preallocate)] + + lappend params -logfilemode $logfilemode + + if {$opts(filemode) eq "append" && $opts(clockresolution) != 2} { + error "Option -clockresolution must be set to 'system' if -filemode is append" + } + + if {($opts(filemode) eq "rotate" || $opts(preallocate)) && + $opts(maxfilesize) == 0} { + error "Option -maxfilesize must also be specified with -preallocate or -filemodenewfile." + } + + lappend params -clockresolution $opts(clockresolution) + + trap { + set h [StartTrace $session_name $params] + set _etw_trace_controllers($h) $session_name + return $h + } onerror {TWAPI_WIN32 5} { + return -options [trapoptions] "Access denied. This may be because the process does not have permission to create the specified logfile or because it is not running under an account permitted to control ETW traces." + } +} + +proc twapi::etw_start_kernel_trace {events args} { + + set enableflags 0 + + # Note sysconfig is a dummy event. It is always logged. + set eventmap { + process 0x00000001 + thread 0x00000002 + imageload 0x00000004 + diskio 0x00000100 + diskfileio 0x00000200 + pagefault 0x00001000 + hardfault 0x00002000 + tcpip 0x00010000 + registry 0x00020000 + dbgprint 0x00040000 + sysconfig 0x00000000 + } + + if {"diskfileio" in $events} { + lappend events diskio; # Required by diskfileio + } + + if {[min_os_version 6]} { + lappend eventmap {*}{ + processcounter 0x00000008 + contextswitch 0x00000010 + dpc 0x00000020 + interrupt 0x00000040 + systemcall 0x00000080 + diskioinit 0x00000400 + alpc 0x00100000 + splitio 0x00200000 + driver 0x00800000 + profile 0x01000000 + fileio 0x02000000 + fileioinit 0x04000000 + } + + if {"diskio" in $events} { + lappend events diskioinit + } + } + + if {[min_os_version 6 1]} { + lappend eventmap {*}{ + dispatcher 0x00000800 + virtualalloc 0x00004000 + } + } + + if {[min_os_version 6 2]} { + lappend eventmap {*}{ + vamap 0x00008000 + } + if {"sysconfig" ni $events} { + # EVENT_TRACE_FLAG_NO_SYSCONFIG + set enableflags [expr {$enableflags | 0x10000000}] + } + } + + foreach event $events { + set enableflags [expr {$enableflags | [dict! $eventmap $event]}] + } + + # Name "NT Kernel Logger" is hardcoded in Windows + # GUID is 9e814aad-3204-11d2-9a82-006008a86939 but does not need to be + # specified. Note kernel logger cannot use paged memory so + # -paged 0 is required + return [etw_start_trace "NT Kernel Logger" -enableflags $enableflags {*}$args -paged 0] +} + +proc twapi::etw_enable_provider {htrace guid enableflags level} { + set guid [_etw_provider_guid $guid] + return [EnableTrace 1 $enableflags [_etw_level_to_int $level] $guid $htrace] +} + +proc twapi::etw_disable_provider {htrace guid} { + set guid [_etw_provider_guid $guid] + return [EnableTrace 0 -1 5 $guid $htrace] +} + +proc twapi::etw_control_trace {action session args} { + variable _etw_trace_controllers + + if {[info exists _etw_trace_controllers($session)]} { + set sessionhandle $session + } else { + set sessionhandle 0 + set sessionname $session + } + + set action [dict get { + query 0 + stop 1 + update 2 + flush 3 + } $action] + + array set opts [parseargs args { + traceguid.arg + logfile.arg + maxbuffers.int + flushtimer.int + enableflags.int + realtime.bool + } -maxleftover 0] + + set params {} + + if {[info exists opts(realtime)]} { + if {$opts(realtime)} { + lappend params -logfilemode 0x100; # EVENT_TRACE_REAL_TIME_MODE + } else { + lappend params -logfilemode 0 + } + } + + if {[info exists opts(traceguid)]} { + append params -traceguid $opts(traceguid) + } + + if {[info exists sessionname]} { + lappend params -sessionname $sessionname + } + + if {$action == 2} { + # update + foreach opt {logfile flushtimer enableflags maxbuffers} { + if {[info exists opts($opt)]} { + lappend params -$opt $opts($opt) + } + } + } + + return [etw_trace_properties [ControlTrace $action $sessionhandle $params]] +} + +interp alias {} twapi::etw_update_trace {} twapi::etw_control_trace update + +proc twapi::etw_stop_trace {trace} { + variable _etw_trace_controllers + set stats [etw_control_trace stop $trace] + unset -nocomplain _etw_trace_controllers($trace) + return $stats +} + +proc twapi::etw_flush_trace {trace} { + return [etw_control_trace flush $trace] +} + +proc twapi::etw_query_trace {trace} { + set d [etw_control_trace query $trace] + set cres [lindex {{} qpc system cpucycle} [dict get $d clock_resolution]] + if {$cres ne ""} { + dict set d clock_resolution $cres + } + + #TBD - check whether -maxfilesize needs to be massaged + + return $d +} + + + +# +# Helper functions +# + + +# Return binary unicode with truncation if necessary +proc twapi::_etw_encode_limited_unicode {s {max 80}} { + if {[string length $s] > $max} { + set s "[string range $s 0 $max-3]..." + } + return [encoding convertto unicode "$s\0"] +} + +# Used for development/debug to see what all types are in use +proc twapi::_etw_get_types {} { + dict for {g gval} $::twapi::_etw_event_defs { + dict for {ver verval} $gval { + dict for {eventtype eval} [dict get $verval -definitions] { + dict for {id idval} [dict get $eval -fields] { + dict set types [dict get $idval -type] [dict get $verval -classname] $eventtype $id + } + } + } + } + return $types +} + +proc twapi::_etw_level_to_int {level} { + return [dict* {verbose 5 information 4 info 4 informational 4 warning 3 error 2 fatal 1 critical 1} [string tolower $level]] +} + +# Map provider guid/name to guid +proc twapi::_etw_provider_guid {lookup} { + if {[Twapi_IsValidGUID $lookup]} { + return $lookup + } + set guid [etw_get_provider_guid $lookup] + if {$guid eq ""} { + badargs! "Provider \"$lookup\" not found." + } + return $guid +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/eventlog.tcl b/src/vendorlib_tcl8/twapi-5.0b1/eventlog.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/eventlog.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/eventlog.tcl index 205784fd..2f77f8be 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/eventlog.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/eventlog.tcl @@ -1,391 +1,391 @@ -# -# Copyright (c) 2004-2012, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -package require registry - -namespace eval twapi { - # We maintain caches so we do not do lookups all the time - # TBD - have a means of clearing this out - variable _eventlog_message_cache - set _eventlog_message_cache {} -} - - -# Read the event log -proc twapi::eventlog_read {hevl args} { - _eventlog_valid_handle $hevl read raise - - array set opts [parseargs args { - seek.int - {direction.arg forward} - }] - - if {[info exists opts(seek)]} { - set flags 2; # Seek - set offset $opts(seek) - } else { - set flags 1; # Sequential read - set offset 0 - } - - switch -glob -- $opts(direction) { - "" - - forw* { - setbits flags 4 - } - back* { - setbits flags 8 - } - default { - error "Invalid value '$opts(direction)' for -direction option" - } - } - - set results [list ] - - trap { - set recs [ReadEventLog $hevl $flags $offset] - } onerror {TWAPI_WIN32 38} { - # EOF - no more - set recs [list ] - } - foreach event $recs { - dict set event -type [string map {0 success 1 error 2 warning 4 information 8 auditsuccess 16 auditfailure} [dict get $event -level]] - lappend results $event - } - - return $results -} - - -# Get the oldest event log record index. $hevl must be read handle -proc twapi::eventlog_oldest {hevl} { - _eventlog_valid_handle $hevl read raise - return [GetOldestEventLogRecord $hevl] -} - -# Get the event log record count. $hevl must be read handle -proc twapi::eventlog_count {hevl} { - _eventlog_valid_handle $hevl read raise - return [GetNumberOfEventLogRecords $hevl] -} - -# Check if the event log is full. $hevl may be either read or write handle -# (only win2k plus) -proc twapi::eventlog_is_full {hevl} { - # Does not matter if $hevl is read or write, but verify it is a handle - _eventlog_valid_handle $hevl read - return [Twapi_IsEventLogFull $hevl] -} - -# Backup the event log -proc twapi::eventlog_backup {hevl file} { - _eventlog_valid_handle $hevl read raise - BackupEventLog $hevl $file -} - -# Clear the event log -proc twapi::eventlog_clear {hevl args} { - _eventlog_valid_handle $hevl read raise - array set opts [parseargs args {backup.arg} -nulldefault] - ClearEventLog $hevl $opts(backup) -} - - -# Formats the given event log record message -# -proc twapi::eventlog_format_message {rec args} { - variable _eventlog_message_cache - - array set opts [parseargs args { - width.int - langid.int - } -nulldefault] - - set source [dict get $rec -source] - set eventid [dict get $rec -eventid] - - if {[dict exists $_eventlog_message_cache $source fmtstring $opts(langid) $eventid]} { - set fmtstring [dict get $_eventlog_message_cache $source fmtstring $opts(langid) $eventid] - dict incr _eventlog_message_cache __fmtstring_hits - } else { - dict incr _eventlog_message_cache __fmtstring_misses - - # Find the registry key if we do not have it already - if {[dict exists $_eventlog_message_cache $source regkey]} { - dict incr _eventlog_message_cache __regkey_hits - set regkey [dict get $_eventlog_message_cache $source regkey] - } else { - set regkey [_find_eventlog_regkey $source] - dict set _eventlog_message_cache $source regkey $regkey - dict incr _eventlog_message_cache __regkey_misses - } - - # Get the message file, if there is one - if {! [catch {registry get $regkey "EventMessageFile"} path]} { - # Try each file listed in turn - foreach dll [split $path \;] { - set dll [expand_environment_strings $dll] - if {! [catch { - set fmtstring [format_message -module $dll -messageid $eventid -width $opts(width) -langid $opts(langid)] - } msg]} { - dict set _eventlog_message_cache $source fmtstring $opts(langid) $eventid $fmtstring - break - } - } - } - } - - if {! [info exists fmtstring]} { - dict incr _eventlog_message_cache __notfound - - set fmt "The message file or event definition for event id [dict get $rec -eventid] from source [dict get $rec -source] was not found. The following information was part of the event: " - set flds [list ] - for {set i 1} {$i <= [llength [dict get $rec -params]]} {incr i} { - lappend flds %$i - } - append fmt [join $flds ", "] - return [format_message -fmtstring $fmt \ - -params [dict get $rec -params] -width $opts(width)] - } - - set msg [format_message -fmtstring $fmtstring -params [dict get $rec -params]] - - # We'd found a message from the message file and replaced the string - # parameters. Now fill in the parameter file values if any. Note these are - # separate from the string parameters passed in through rec(-params) - - # First check if the formatted string itself still has placeholders - # Place holder for the parameters file are supposed to start - # with two % chars. Unfortunately, not all apps, even Microsoft's own - # DCOM obey this. So check for both % and %% - set placeholder_indices [regexp -indices -all -inline {%?%\d+} $msg] - if {[llength $placeholder_indices] == 0} { - # No placeholders. - return $msg - } - - # Loop through to replace placeholders. - set msg2 ""; # Holds result after param replacement - set prev_end 0 - foreach placeholder $placeholder_indices { - lassign $placeholder start end - # Append the stuff between previous placeholder and this one - append msg2 [string range $msg $prev_end [expr {$start-1}]] - set repl [string range $msg $start $end]; # Default if not found - set paramid [string trimleft $repl %]; # Skip "%" - if {[dict exists $_eventlog_message_cache $source paramstring $opts(langid) $paramid]} { - dict incr _eventlog_message_cache __paramstring_hits - set repl [format_message -fmtstring [dict get $_eventlog_message_cache $source paramstring $opts(langid) $paramid] -params [dict get $rec -params]] - } else { - dict incr _eventlog_message_cache __paramstring_misses - # Not in cache, need to look up - if {![info exists paramfiles]} { - # Construct list of parameter string files - - # TBD - cache registry key results? - # Find the registry key if we do not have it already - if {![info exists regkey]} { - if {[dict exists $_eventlog_message_cache $source regkey]} { - dict incr _eventlog_message_cache __regkey_hits - set regkey [dict get $_eventlog_message_cache $source regkey] - } else { - dict incr _eventlog_message_cache __regkey_misses - set regkey [_find_eventlog_regkey $source] - dict set _eventlog_message_cache $source regkey $regkey - } - } - set paramfiles {} - if {! [catch {registry get $regkey "ParameterMessageFile"} path]} { - # Loop through every placeholder, look for the entry in the - # parameters file and replace it if found - foreach paramfile [split $path \;] { - lappend paramfiles [expand_environment_strings $paramfile] - } - } - } - # Try each file listed in turn - foreach paramfile $paramfiles { - if {! [catch { - set paramstring [string trimright [format_message -module $paramfile -messageid $paramid -langid $opts(langid)] \r\n] - } ]} { - # Found the replacement - dict set _eventlog_message_cache $source paramstring $opts(langid) $paramid $paramstring - set repl [format_message -fmtstring $paramstring -params [dict get $rec -params]] - break - } - } - } - append msg2 $repl - set prev_end [incr end] - } - - # Tack on tail after last placeholder - append msg2 [string range $msg $prev_end end] - return $msg2 -} - -# Format the category -proc twapi::eventlog_format_category {rec args} { - - array set opts [parseargs args { - width.int - langid.int - } -nulldefault] - - set category [dict get $rec -category] - if {$category == 0} { - return "" - } - - variable _eventlog_message_cache - - set source [dict get $rec -source] - - # Get the category string from cache, if there is one - if {[dict exists $_eventlog_message_cache $source category $opts(langid) $category]} { - dict incr _eventlog_message_cache __category_hits - set fmtstring [dict get $_eventlog_message_cache $source category $opts(langid) $category] - } else { - dict incr _eventlog_message_cache __category_misses - - # Find the registry key if we do not have it already - if {[dict exists $_eventlog_message_cache $source regkey]} { - dict incr _eventlog_message_cache __regkey_hits - set regkey [dict get $_eventlog_message_cache $source regkey] - } else { - set regkey [_find_eventlog_regkey $source] - dict set _eventlog_message_cache $source regkey $regkey - dict incr _eventlog_message_cache __regkey_misses - } - - if {! [catch {registry get $regkey "CategoryMessageFile"} path]} { - # Try each file listed in turn - foreach dll [split $path \;] { - set dll [expand_environment_strings $dll] - if {! [catch { - set fmtstring [format_message -module $dll -messageid $category -width $opts(width) -langid $opts(langid)] - } msg]} { - dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring - break - } - } - } - } - - if {![info exists fmtstring]} { - set fmtstring "Category $category" - dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring - } - - return [format_message -fmtstring $fmtstring -params [dict get $rec -params]] -} - -proc twapi::eventlog_monitor_start {hevl script} { - variable _eventlog_notification_scripts - - set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] - if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { - CloseHandle $hevent - error $msg $::errorInfo $::errorCode - } - - wait_on_handle $hevent -async twapi::_eventlog_notification_handler - set _eventlog_notification_scripts($hevent) $script - - # We do not want the application mistakenly closing the event - # while being waited on by the thread pool. That would be a big NO-NO - # so change the handle type so it cannot be passed to close_handle. - return [list evl $hevent] -} - -# Stop any notifications. Note these will stop even if the event log -# handle is closed but leave the event dangling. -proc twapi::eventlog_monitor_stop {hevent} { - variable _eventlog_notification_scripts - set hevent [lindex $hevent 1] - if {[info exists _eventlog_notification_scripts($hevent)]} { - unset _eventlog_notification_scripts($hevent) - cancel_wait_on_handle $hevent - CloseHandle $hevent - } -} - -proc twapi::_eventlog_notification_handler {hevent event} { - variable _eventlog_notification_scripts - if {[info exists _eventlog_notification_scripts($hevent)] && - $event eq "signalled"} { - uplevel #0 $_eventlog_notification_scripts($hevent) [list [list evl $hevent]] - } -} - -# TBD - document -proc twapi::eventlog_subscribe {source} { - set hevl [eventlog_open -source $source] - set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] - if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { - set erinfo $::errorInfo - set ercode $::errorCode - CloseHandle $hevent - error $hsubscribe $erinfo $ercode - } - - return [list $hevl $hevent] -} - -# Utility procs - -# Find the registry key corresponding the given event log source -proc twapi::_find_eventlog_regkey {source} { - set topkey {HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog} - - # Set a default list of children to work around an issue in - # the Tcl [registry keys] command where a ERROR_MORE_DATA is returned - # instead of a retry with a larger buffer. - set keys {Application Security System} - catch {set keys [registry keys $topkey]} - # Get all keys under this key and look for a source under that - foreach key $keys { - # See above Tcl issue - set srckeys {} - catch {set srckeys [registry keys "${topkey}\\$key"]} - foreach srckey $srckeys { - if {[string equal -nocase $srckey $source]} { - return "${topkey}\\${key}\\$srckey" - } - } - } - - # Default to Application - TBD - return "${topkey}\\Application" -} - -proc twapi::_eventlog_dump {source chan} { - set hevl [eventlog_open -source $source] - while {[llength [set events [eventlog_read $hevl]]]} { - # print out each record - foreach eventrec $events { - array set event $eventrec - set timestamp [clock format $event(-timewritten) -format "%x %X"] - set source $event(-source) - set category [twapi::eventlog_format_category $eventrec -width -1] - set message [twapi::eventlog_format_message $eventrec -width -1] - puts $chan "$timestamp $source $category $message" - } - } - eventlog_close $hevl -} - - - - -# If we are not being sourced from a executable resource, need to -# source the remaining support files. In the former case, they are -# automatically combined into one so the sourcing is not needed. -if {![info exists twapi::twapi_eventlog_rc_sourced]} { - source [file join [file dirname [info script]] evt.tcl] - source [file join [file dirname [info script]] winlog.tcl] -} +# +# Copyright (c) 2004-2012, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +package require registry + +namespace eval twapi { + # We maintain caches so we do not do lookups all the time + # TBD - have a means of clearing this out + variable _eventlog_message_cache + set _eventlog_message_cache {} +} + + +# Read the event log +proc twapi::eventlog_read {hevl args} { + _eventlog_valid_handle $hevl read raise + + array set opts [parseargs args { + seek.int + {direction.arg forward} + }] + + if {[info exists opts(seek)]} { + set flags 2; # Seek + set offset $opts(seek) + } else { + set flags 1; # Sequential read + set offset 0 + } + + switch -glob -- $opts(direction) { + "" - + forw* { + setbits flags 4 + } + back* { + setbits flags 8 + } + default { + error "Invalid value '$opts(direction)' for -direction option" + } + } + + set results [list ] + + trap { + set recs [ReadEventLog $hevl $flags $offset] + } onerror {TWAPI_WIN32 38} { + # EOF - no more + set recs [list ] + } + foreach event $recs { + dict set event -type [string map {0 success 1 error 2 warning 4 information 8 auditsuccess 16 auditfailure} [dict get $event -level]] + lappend results $event + } + + return $results +} + + +# Get the oldest event log record index. $hevl must be read handle +proc twapi::eventlog_oldest {hevl} { + _eventlog_valid_handle $hevl read raise + return [GetOldestEventLogRecord $hevl] +} + +# Get the event log record count. $hevl must be read handle +proc twapi::eventlog_count {hevl} { + _eventlog_valid_handle $hevl read raise + return [GetNumberOfEventLogRecords $hevl] +} + +# Check if the event log is full. $hevl may be either read or write handle +# (only win2k plus) +proc twapi::eventlog_is_full {hevl} { + # Does not matter if $hevl is read or write, but verify it is a handle + _eventlog_valid_handle $hevl read + return [Twapi_IsEventLogFull $hevl] +} + +# Backup the event log +proc twapi::eventlog_backup {hevl file} { + _eventlog_valid_handle $hevl read raise + BackupEventLog $hevl $file +} + +# Clear the event log +proc twapi::eventlog_clear {hevl args} { + _eventlog_valid_handle $hevl read raise + array set opts [parseargs args {backup.arg} -nulldefault] + ClearEventLog $hevl $opts(backup) +} + + +# Formats the given event log record message +# +proc twapi::eventlog_format_message {rec args} { + variable _eventlog_message_cache + + array set opts [parseargs args { + width.int + langid.int + } -nulldefault] + + set source [dict get $rec -source] + set eventid [dict get $rec -eventid] + + if {[dict exists $_eventlog_message_cache $source fmtstring $opts(langid) $eventid]} { + set fmtstring [dict get $_eventlog_message_cache $source fmtstring $opts(langid) $eventid] + dict incr _eventlog_message_cache __fmtstring_hits + } else { + dict incr _eventlog_message_cache __fmtstring_misses + + # Find the registry key if we do not have it already + if {[dict exists $_eventlog_message_cache $source regkey]} { + dict incr _eventlog_message_cache __regkey_hits + set regkey [dict get $_eventlog_message_cache $source regkey] + } else { + set regkey [_find_eventlog_regkey $source] + dict set _eventlog_message_cache $source regkey $regkey + dict incr _eventlog_message_cache __regkey_misses + } + + # Get the message file, if there is one + if {! [catch {registry get $regkey "EventMessageFile"} path]} { + # Try each file listed in turn + foreach dll [split $path \;] { + set dll [expand_environment_strings $dll] + if {! [catch { + set fmtstring [format_message -module $dll -messageid $eventid -width $opts(width) -langid $opts(langid)] + } msg]} { + dict set _eventlog_message_cache $source fmtstring $opts(langid) $eventid $fmtstring + break + } + } + } + } + + if {! [info exists fmtstring]} { + dict incr _eventlog_message_cache __notfound + + set fmt "The message file or event definition for event id [dict get $rec -eventid] from source [dict get $rec -source] was not found. The following information was part of the event: " + set flds [list ] + for {set i 1} {$i <= [llength [dict get $rec -params]]} {incr i} { + lappend flds %$i + } + append fmt [join $flds ", "] + return [format_message -fmtstring $fmt \ + -params [dict get $rec -params] -width $opts(width)] + } + + set msg [format_message -fmtstring $fmtstring -params [dict get $rec -params]] + + # We'd found a message from the message file and replaced the string + # parameters. Now fill in the parameter file values if any. Note these are + # separate from the string parameters passed in through rec(-params) + + # First check if the formatted string itself still has placeholders + # Place holder for the parameters file are supposed to start + # with two % chars. Unfortunately, not all apps, even Microsoft's own + # DCOM obey this. So check for both % and %% + set placeholder_indices [regexp -indices -all -inline {%?%\d+} $msg] + if {[llength $placeholder_indices] == 0} { + # No placeholders. + return $msg + } + + # Loop through to replace placeholders. + set msg2 ""; # Holds result after param replacement + set prev_end 0 + foreach placeholder $placeholder_indices { + lassign $placeholder start end + # Append the stuff between previous placeholder and this one + append msg2 [string range $msg $prev_end [expr {$start-1}]] + set repl [string range $msg $start $end]; # Default if not found + set paramid [string trimleft $repl %]; # Skip "%" + if {[dict exists $_eventlog_message_cache $source paramstring $opts(langid) $paramid]} { + dict incr _eventlog_message_cache __paramstring_hits + set repl [format_message -fmtstring [dict get $_eventlog_message_cache $source paramstring $opts(langid) $paramid] -params [dict get $rec -params]] + } else { + dict incr _eventlog_message_cache __paramstring_misses + # Not in cache, need to look up + if {![info exists paramfiles]} { + # Construct list of parameter string files + + # TBD - cache registry key results? + # Find the registry key if we do not have it already + if {![info exists regkey]} { + if {[dict exists $_eventlog_message_cache $source regkey]} { + dict incr _eventlog_message_cache __regkey_hits + set regkey [dict get $_eventlog_message_cache $source regkey] + } else { + dict incr _eventlog_message_cache __regkey_misses + set regkey [_find_eventlog_regkey $source] + dict set _eventlog_message_cache $source regkey $regkey + } + } + set paramfiles {} + if {! [catch {registry get $regkey "ParameterMessageFile"} path]} { + # Loop through every placeholder, look for the entry in the + # parameters file and replace it if found + foreach paramfile [split $path \;] { + lappend paramfiles [expand_environment_strings $paramfile] + } + } + } + # Try each file listed in turn + foreach paramfile $paramfiles { + if {! [catch { + set paramstring [string trimright [format_message -module $paramfile -messageid $paramid -langid $opts(langid)] \r\n] + } ]} { + # Found the replacement + dict set _eventlog_message_cache $source paramstring $opts(langid) $paramid $paramstring + set repl [format_message -fmtstring $paramstring -params [dict get $rec -params]] + break + } + } + } + append msg2 $repl + set prev_end [incr end] + } + + # Tack on tail after last placeholder + append msg2 [string range $msg $prev_end end] + return $msg2 +} + +# Format the category +proc twapi::eventlog_format_category {rec args} { + + array set opts [parseargs args { + width.int + langid.int + } -nulldefault] + + set category [dict get $rec -category] + if {$category == 0} { + return "" + } + + variable _eventlog_message_cache + + set source [dict get $rec -source] + + # Get the category string from cache, if there is one + if {[dict exists $_eventlog_message_cache $source category $opts(langid) $category]} { + dict incr _eventlog_message_cache __category_hits + set fmtstring [dict get $_eventlog_message_cache $source category $opts(langid) $category] + } else { + dict incr _eventlog_message_cache __category_misses + + # Find the registry key if we do not have it already + if {[dict exists $_eventlog_message_cache $source regkey]} { + dict incr _eventlog_message_cache __regkey_hits + set regkey [dict get $_eventlog_message_cache $source regkey] + } else { + set regkey [_find_eventlog_regkey $source] + dict set _eventlog_message_cache $source regkey $regkey + dict incr _eventlog_message_cache __regkey_misses + } + + if {! [catch {registry get $regkey "CategoryMessageFile"} path]} { + # Try each file listed in turn + foreach dll [split $path \;] { + set dll [expand_environment_strings $dll] + if {! [catch { + set fmtstring [format_message -module $dll -messageid $category -width $opts(width) -langid $opts(langid)] + } msg]} { + dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring + break + } + } + } + } + + if {![info exists fmtstring]} { + set fmtstring "Category $category" + dict set _eventlog_message_cache $source category $opts(langid) $category $fmtstring + } + + return [format_message -fmtstring $fmtstring -params [dict get $rec -params]] +} + +proc twapi::eventlog_monitor_start {hevl script} { + variable _eventlog_notification_scripts + + set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] + if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { + CloseHandle $hevent + error $msg $::errorInfo $::errorCode + } + + wait_on_handle $hevent -async twapi::_eventlog_notification_handler + set _eventlog_notification_scripts($hevent) $script + + # We do not want the application mistakenly closing the event + # while being waited on by the thread pool. That would be a big NO-NO + # so change the handle type so it cannot be passed to close_handle. + return [list evl $hevent] +} + +# Stop any notifications. Note these will stop even if the event log +# handle is closed but leave the event dangling. +proc twapi::eventlog_monitor_stop {hevent} { + variable _eventlog_notification_scripts + set hevent [lindex $hevent 1] + if {[info exists _eventlog_notification_scripts($hevent)]} { + unset _eventlog_notification_scripts($hevent) + cancel_wait_on_handle $hevent + CloseHandle $hevent + } +} + +proc twapi::_eventlog_notification_handler {hevent event} { + variable _eventlog_notification_scripts + if {[info exists _eventlog_notification_scripts($hevent)] && + $event eq "signalled"} { + uplevel #0 $_eventlog_notification_scripts($hevent) [list [list evl $hevent]] + } +} + +# TBD - document +proc twapi::eventlog_subscribe {source} { + set hevl [eventlog_open -source $source] + set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] + if {[catch {NotifyChangeEventLog $hevl $hevent} msg]} { + set erinfo $::errorInfo + set ercode $::errorCode + CloseHandle $hevent + error $hsubscribe $erinfo $ercode + } + + return [list $hevl $hevent] +} + +# Utility procs + +# Find the registry key corresponding the given event log source +proc twapi::_find_eventlog_regkey {source} { + set topkey {HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog} + + # Set a default list of children to work around an issue in + # the Tcl [registry keys] command where a ERROR_MORE_DATA is returned + # instead of a retry with a larger buffer. + set keys {Application Security System} + catch {set keys [registry keys $topkey]} + # Get all keys under this key and look for a source under that + foreach key $keys { + # See above Tcl issue + set srckeys {} + catch {set srckeys [registry keys "${topkey}\\$key"]} + foreach srckey $srckeys { + if {[string equal -nocase $srckey $source]} { + return "${topkey}\\${key}\\$srckey" + } + } + } + + # Default to Application - TBD + return "${topkey}\\Application" +} + +proc twapi::_eventlog_dump {source chan} { + set hevl [eventlog_open -source $source] + while {[llength [set events [eventlog_read $hevl]]]} { + # print out each record + foreach eventrec $events { + array set event $eventrec + set timestamp [clock format $event(-timewritten) -format "%x %X"] + set source $event(-source) + set category [twapi::eventlog_format_category $eventrec -width -1] + set message [twapi::eventlog_format_message $eventrec -width -1] + puts $chan "$timestamp $source $category $message" + } + } + eventlog_close $hevl +} + + + + +# If we are not being sourced from a executable resource, need to +# source the remaining support files. In the former case, they are +# automatically combined into one so the sourcing is not needed. +if {![info exists twapi::twapi_eventlog_rc_sourced]} { + source [file join [file dirname [info script]] evt.tcl] + source [file join [file dirname [info script]] winlog.tcl] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/evt.tcl b/src/vendorlib_tcl8/twapi-5.0b1/evt.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/evt.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/evt.tcl index 61d19bc1..c9f5e86d 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/evt.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/evt.tcl @@ -1,718 +1,718 @@ -# -# Copyright (c) 2012-2014, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Event log handling for Vista and later - -namespace eval twapi { - variable _evt; # See _evt_init - - # System event fields in order returned by _evt_decode_event_system_fields - twapi::record evt_system_fields { - -providername -providerguid -eventid -qualifiers -level -task - -opcode -keywordmask -timecreated -eventrecordid -activityid - -relatedactivityid -pid -tid -channel - -computer -sid -version - } - - proc _evt_init {} { - variable _evt - - # Various structures that we maintain / cache for efficiency as they - # are commonly used are kept in the _evt array with the following keys: - - # system_render_context_handle - is the handle to a rendering - # context for the system portion of an event - set _evt(system_render_context_handle) [evt_render_context_system] - - # user_render_context_handle - is the handle to a rendering - # context for the user data portion of an event - set _evt(user_render_context_handle) [evt_render_context_user] - - # render_buffer - is NULL or holds a pointer to the buffer used to - # retrieve values so does not have to be reallocated every time. - set _evt(render_buffer) NULL - - # publisher_handles - caches publisher names to their meta information. - # This is a dictionary indexed with nested keys - - # publisher, session, lcid. TBD - need a mechanism to clear ? - set _evt(publisher_handles) [dict create] - - # -levelname - dict of publisher name / level number to level names - set _evt(-levelname) {} - - # -taskname - dict of publisher name / task number to task name - set _evt(-taskname) {} - - # -opcodename - dict of publisher name / opcode number to opcode name - set _evt(-opcodename) {} - - # No-op the proc once init is done - proc _evt_init {} {} - } -} - -# TBD - document -proc twapi::evt_local_session {} { - return NULL -} - -# TBD - document -proc twapi::evt_local_session? {hsess} { - return [pointer_null? $hsess] -} - -# TBD - document -proc twapi::evt_open_session {server args} { - array set opts [parseargs args { - user.arg - domain.arg - password.arg - {authtype.arg 0} - } -nulldefault -maxleftover 0] - - if {![string is integer -strict $opts(authtype)]} { - set opts(authtype) [dict get {default 0 negotiate 1 kerberos 2 ntlm 3} [string tolower $opts(authtype)]] - } - - return [EvtOpenSession 1 [list $server $opts(user) $opts(domain) $opts(password) $opts(authtype)] 0 0] -} - -# TBD - document -proc twapi::evt_close_session {hsess} { - if {![evt_local_session? $hsess]} { - evt_close $hsess - } -} - -proc twapi::evt_channels {{hevtsess NULL}} { - # TBD - document hevtsess - set chnames {} - set hevt [EvtOpenChannelEnum $hevtsess 0] - trap { - while {[set chname [EvtNextChannelPath $hevt]] ne ""} { - lappend chnames $chname - } - } finally { - evt_close $hevt - } - - return $chnames -} - -proc twapi::evt_clear_log {chanpath args} { - # TBD - document -session - array set opts [parseargs args { - {session.arg NULL} - {backup.arg ""} - } -maxleftover 0] - - return [EvtClearLog $opts(session) $chanpath [_evt_normalize_path $opts(backup)] 0] -} - -# TBD - document -proc twapi::evt_archive_exported_log {logpath args} { - array set opts [parseargs args { - {session.arg NULL} - {lcid.int 0} - } -maxleftover 0] - - return [EvtArchiveExportedLog $opts(session) [_evt_normalize_path $logpath] $opts(lcid) 0] -} - -proc twapi::evt_export_log {outfile args} { - # TBD - document -session - array set opts [parseargs args { - {session.arg NULL} - file.arg - channel.arg - {query.arg *} - {ignorequeryerrors 0 0x1000} - } -maxleftover 0] - - if {([info exists opts(file)] && [info exists opts(channel)]) || - ! ([info exists opts(file)] || [info exists opts(channel)])} { - error "Exactly one of -file or -channel must be specified." - } - - if {[info exists opts(file)]} { - set path [_evt_normalize_path $opts(file)] - incr opts(ignorequeryerrors) 2 - } else { - set path $opts(channel) - incr opts(ignorequeryerrors) 1 - } - - return [EvtExportLog $opts(session) $path $opts(query) [_evt_normalize_path $outfile] $opts(ignorequeryerrors)] -} - -# TBD - document -proc twapi::evt_create_bookmark {{mark ""}} { - return [EvtCreateBookmark $mark] -} - -# TBD - document -proc twapi::evt_render_context_xpaths {xpaths} { - return [EvtCreateRenderContext $xpaths 0] -} - -# TBD - document -proc twapi::evt_render_context_system {} { - return [EvtCreateRenderContext {} 1] -} - -# TBD - document -proc twapi::evt_render_context_user {} { - return [EvtCreateRenderContext {} 2] -} - -# TBD - document -proc twapi::evt_open_channel_config {chanpath args} { - array set opts [parseargs args { - {session.arg NULL} - } -maxleftover 0] - - return [EvtOpenChannelConfig $opts(session) $chanpath 0] -} - -# TBD - document -proc twapi::evt_get_channel_config {hevt args} { - set result {} - foreach opt $args { - lappend result $opt \ - [EvtGetChannelConfigProperty $hevt \ - [_evt_map_channel_config_property $hevt $propid]] - } - return $result -} - -# TBD - document -proc twapi::evt_set_channel_config {hevt propid val} { - return [EvtSetChannelConfigProperty $hevt [_evt_map_channel_config_property $propid 0 $val]] -} - - -# TBD - document -proc twapi::_evt_map_channel_config_property {propid} { - if {[string is integer -strict $propid]} { - return $propid - } - - # Note: values are from winevt.h, Win7 SDK has typos for last few - return [dict get { - -enabled 0 - -isolation 1 - -type 2 - -owningpublisher 3 - -classiceventlog 4 - -access 5 - -loggingretention 6 - -loggingautobackup 7 - -loggingmaxsize 8 - -logginglogfilepath 9 - -publishinglevel 10 - -publishingkeywords 11 - -publishingcontrolguid 12 - -publishingbuffersize 13 - -publishingminbuffers 14 - -publishingmaxbuffers 15 - -publishinglatency 16 - -publishingclocktype 17 - -publishingsidtype 18 - -publisherlist 19 - -publishingfilemax 20 - } $propid] -} - -# TBD - document -proc twapi::evt_event_info {hevt args} { - set result {} - foreach opt $args { - lappend result $opt [EvtGetEventInfo $hevt \ - [dict get {-queryids 0 -path 1} $opt]] - } - return $result -} - - -# TBD - document -proc twapi::evt_event_metadata_property {hevt args} { - set result {} - foreach opt $args { - lappend result $opt \ - [EvtGetEventMetadataProperty $hevt \ - [dict get { - -id 0 -version 1 -channel 2 -level 3 - -opcode 4 -task 5 -keyword 6 -messageid 7 -template 8 - } $opt]] - } - return $result -} - - -# TBD - document -proc twapi::evt_open_log_info {args} { - array set opts [parseargs args { - {session.arg NULL} - file.arg - channel.arg - } -maxleftover 0] - - if {([info exists opts(file)] && [info exists opts(channel)]) || - ! ([info exists opts(file)] || [info exists opts(channel)])} { - error "Exactly one of -file or -channel must be specified." - } - - if {[info exists opts(file)]} { - set path [_evt_normalize_path $opts(file)] - set flags 0x2 - } else { - set path $opts(channel) - set flags 0x1 - } - - return [EvtOpenLog $opts(session) $path $flags] -} - -# TBD - document -proc twapi::evt_log_info {hevt args} { - set result {} - foreach opt $args { - lappend result $opt [EvtGetLogInfo $hevt [dict get { - -creationtime 0 -lastaccesstime 1 -lastwritetime 2 - -filesize 3 -attributes 4 -numberoflogrecords 5 - -oldestrecordnumber 6 -full 7 - } $opt]] - } - return $result -} - -# TBD - document -proc twapi::evt_publisher_metadata_property {hpub args} { - set result {} - foreach opt $args { - set val [EvtGetPublisherMetadataProperty $hpub [dict get { - -publisherguid 0 -resourcefilepath 1 -parameterfilepath 2 - -messagefilepath 3 -helplink 4 -publishermessageid 5 - -channelreferences 6 -levels 12 -tasks 16 - -opcodes 21 -keywords 25 - } $opt] 0] - if {$opt ni {-channelreferences -levels -tasks -opcodes -keywords}} { - lappend result $opt $val - continue - } - set n [EvtGetObjectArraySize $val] - set val2 {} - for {set i 0} {$i < $n} {incr i} { - set rec {} - foreach {opt2 iopt} [dict get { - -channelreferences { -channelreferencepath 7 - -channelreferenceindex 8 -channelreferenceid 9 - -channelreferenceflags 10 -channelreferencemessageid 11} - -levels { -levelname 13 -levelvalue 14 -levelmessageid 15 } - -tasks { -taskname 17 -taskeventguid 18 -taskvalue 19 - -taskmessageid 20} - -opcodes {-opcodename 22 -opcodevalue 23 -opcodemessageid 24} - -keywords {-keywordname 26 -keywordvalue 27 - -keywordmessageid 28} - } $opt] { - lappend rec $opt2 [EvtGetObjectArrayProperty $val $iopt $i] - } - lappend val2 $rec - } - - evt_close $val - lappend result $opt $val2 - } - return $result -} - -# TBD - document -proc twapi::evt_query_info {hq args} { - set result {} - foreach opt $args { - lappend result $opt [EvtGetQueryInfo $hq [dict get { - -names 1 statuses 2 - } $opt]] - } - return $result -} - -# TBD - document -proc twapi::evt_object_array_size {hevt} { - return [EvtGetObjectArraySize $hevt] -} - -# TBD - document -proc twapi::evt_object_array_property {hevt index args} { - set result {} - - foreach opt $args { - lappend result $opt \ - [EvtGetObjectArrayProperty $hevt [dict get { - -channelreferencepath 7 - -channelreferenceindex 8 -channelreferenceid 9 - -channelreferenceflags 10 -channelreferencemessageid 11 - -levelname 13 -levelvalue 14 -levelmessageid 15 - -taskname 17 -taskeventguid 18 -taskvalue 19 - -taskmessageid 20 -opcodename 22 - -opcodevalue 23 -opcodemessageid 24 - -keywordname 26 -keywordvalue 27 -keywordmessageid 28 - }] $index] - } - return $result -} - -proc twapi::evt_publishers {{hsess NULL}} { - set pubs {} - set hevt [EvtOpenPublisherEnum $hsess 0] - trap { - while {[set pub [EvtNextPublisherId $hevt]] ne ""} { - lappend pubs $pub - } - } finally { - evt_close $hevt - } - - return $pubs -} - -# TBD - document -proc twapi::evt_open_publisher_metadata {pub args} { - array set opts [parseargs args { - {session.arg NULL} - logfile.arg - lcid.int - } -nulldefault -maxleftover 0] - - return [EvtOpenPublisherMetadata $opts(session) $pub $opts(logfile) $opts(lcid) 0] -} - -# TBD - document -proc twapi::evt_publisher_events_metadata {hpub args} { - set henum [EvtOpenEventMetadataEnum $hpub] - - # It is faster to build a list and then have Tcl shimmer to a dict when - # required - set meta {} - trap { - while {[set hmeta [EvtNextEventMetadata $henum 0]] ne ""} { - lappend meta [evt_event_metadata_property $hmeta {*}$args] - evt_close $hmeta - } - } finally { - evt_close $henum - } - - return $meta -} - -proc twapi::evt_query {args} { - array set opts [parseargs args { - {session.arg NULL} - file.arg - channel.arg - {query.arg *} - {ignorequeryerrors 0 0x1000} - {direction.sym forward {forward 0x100 reverse 0x200 backward 0x200}} - } -maxleftover 0] - - if {([info exists opts(file)] && [info exists opts(channel)]) || - ! ([info exists opts(file)] || [info exists opts(channel)])} { - error "Exactly one of -file or -channel must be specified." - } - - set flags $opts(ignorequeryerrors) - incr flags $opts(direction) - - if {[info exists opts(file)]} { - set path [_evt_normalize_path $opts(file)] - incr flags 0x2 - } else { - set path $opts(channel) - incr flags 0x1 - } - - return [EvtQuery $opts(session) $path $opts(query) $flags] -} - -proc twapi::evt_next {hresultset args} { - array set opts [parseargs args { - {timeout.int -1} - {count.int 1} - {status.arg} - } -maxleftover 0] - - if {[info exists opts(status)]} { - upvar 1 $opts(status) status - return [EvtNext $hresultset $opts(count) $opts(timeout) 0 status] - } else { - return [EvtNext $hresultset $opts(count) $opts(timeout) 0] - } -} - -twapi::proc* twapi::_evt_decode_event_system_fields {hevt} { - _evt_init -} { - variable _evt - set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(system_render_context_handle) $hevt $_evt(render_buffer)] - set rec [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] - return [evt_system_fields set $rec \ - -providername [atomize [evt_system_fields -providername $rec]] \ - -providerguid [atomize [evt_system_fields -providerguid $rec]] \ - -channel [atomize [evt_system_fields -channel $rec]] \ - -computer [atomize [evt_system_fields -computer $rec]]] -} - -# TBD - document. Returns a list of user data values -twapi::proc* twapi::evt_decode_event_userdata {hevt} { - _evt_init -} { - variable _evt - set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(user_render_context_handle) $hevt $_evt(render_buffer)] - return [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] -} - -twapi::proc* twapi::evt_decode_events {hevts args} { - _evt_init -} { - variable _evt - - array set opts [parseargs args { - {values.arg NULL} - {session.arg NULL} - {logfile.arg ""} - {lcid.int 0} - ignorestring.arg - message - levelname - taskname - opcodename - keywords - xml - } -ignoreunknown -hyphenated] - - # SAME ORDER AS _evt_decode_event_system_fields - set decoded_fields [evt_system_fields] - set decoded_events {} - - # ORDER MUST BE SAME AS order in which values are appended below - foreach opt {-levelname -taskname -opcodename -keywords -xml -message} { - if {$opts($opt)} { - lappend decoded_fields $opt - } - } - - foreach hevt $hevts { - set decoded [_evt_decode_event_system_fields $hevt] - # Get publisher from hevt - set publisher [evt_system_fields -providername $decoded] - - if {! [dict exists $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)]} { - if {[catch { - dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) [EvtOpenPublisherMetadata $opts(-session) $publisher $opts(-logfile) $opts(-lcid) 0] - }]} { - # TBD - debug log - dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) NULL - } - } - set hpub [dict get $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)] - - # See if cached values are present for -levelname -taskname - # and -opcodename. TBD - can -keywords be added to this ? - foreach {intopt opt callflag} {-level -levelname 2 -task -taskname 3 -opcode -opcodename 4} { - if {$opts($opt)} { - set ival [evt_system_fields $intopt $decoded] - if {[dict exists $_evt($opt) $publisher $ival]} { - lappend decoded [dict get $_evt($opt) $publisher $ival] - } else { - # Not cached. Look it up. Value of 0 -> null so - # just use ignorestring if specified. - if {$ival == 0 && [info exists opts(-ignorestring)]} { - set optval $opts(-ignorestring) - } else { - if {[info exists opts(-ignorestring)]} { - if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { - dict set _evt($opt) $publisher $ival $optval - } else { - # Note result not cached if not found since - # ignorestring may be different on every call - set optval $opts(-ignorestring) - } - } else { - # -ignorestring not specified so - # will raise error if not found - set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] - dict set _evt($opt) $publisher $ival [atomize $optval] - } - } - lappend decoded $optval - } - } - } - - # Non-cached fields - # ORDER MUST BE SAME AS decoded_fields ABOVE - foreach {opt callflag} { - -keywords 5 - -xml 9 - } { - if {$opts($opt)} { - if {[info exists opts(-ignorestring)]} { - if {! [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { - set optval $opts(-ignorestring) - } - } else { - set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] - } - lappend decoded $optval - } - } - - # We treat -message differently because on failure we want - # to extract the user data. -ignorestring is not used for this - # unless user data extraction also fails - if {$opts(-message)} { - if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) 1 message]} { - lappend decoded $message - } else { - # TBD - make sure we have a test for this case. - # TBD - log - if {[catch { - lappend decoded "Message for event could not be found. Event contained user data: [join [evt_decode_event_userdata $hevt] ,]" - } message]} { - if {[info exists opts(-ignorestring)]} { - lappend decoded $opts(-ignorestring) - } else { - error $message - } - } - } - } - - lappend decoded_events $decoded - } - - return [list $decoded_fields $decoded_events] -} - -proc twapi::evt_decode_event {hevt args} { - return [recordarray index [evt_decode_events [list $hevt] {*}$args] 0 -format dict] -} - -# TBD - document -proc twapi::evt_format_publisher_message {hpub msgid args} { - - array set opts [parseargs args { - {values.arg NULL} - } -maxleftover 0] - - return [EvtFormatMessage $hpub NULL $msgid $opts(values) 8] -} - -# TBD - document -# Where is this used? -proc twapi::evt_free_EVT_VARIANT_ARRAY {p} { - evt_free $p -} - -# TBD - document -# Where is this used? -proc twapi::evt_free_EVT_RENDER_VALUES {p} { - evt_free $p -} - -# TBD - document -proc twapi::evt_seek {hresults pos args} { - array set opts [parseargs args { - {origin.arg first {first last current}} - bookmark.arg - {strict 0 0x10000} - } -maxleftover 0] - - if {[info exists opts(bookmark)]} { - set flags 4 - } else { - set flags [lsearch -exact {first last current} $opts(origin)] - incr flags; # 1 -> first, 2 -> last, 3 -> current - set opts(bookmark) NULL - } - - incr flags $opts(strict) - - EvtSeek $hresults $pos $opts(bookmark) 0 $flags -} - -proc twapi::evt_subscribe {path args} { - # TBD - document -session and -bookmark and -strict - array set opts [parseargs args { - {session.arg NULL} - {query.arg *} - bookmark.arg - includeexisting - {ignorequeryerrors 0 0x1000} - {strict 0 0x10000} - } -maxleftover 0] - - set flags [expr {$opts(ignorequeryerrors) | $opts(strict)}] - if {[info exists opts(bookmark)]} { - set flags [expr {$flags | 3}] - set bookmark $opts(origin) - } else { - set bookmark NULL - if {$opts(includeexisting)} { - set flags [expr {$flags | 2}] - } else { - set flags [expr {$flags | 1}] - } - } - - set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] - if {[catch { - EvtSubscribe $opts(session) $hevent $path $opts(query) $bookmark $flags - } hsubscribe]} { - set erinfo $::errorInfo - set ercode $::errorCode - CloseHandle $hevent - error $hsubscribe $erinfo $ercode - } - - return [list $hsubscribe $hevent] -} - -proc twapi::_evt_normalize_path {path} { - # Do not want to rely on [file normalize] returning "" for "" - if {$path eq ""} { - return "" - } else { - return [file nativename [file normalize $path]] - } -} - -proc twapi::_evt_dump {args} { - array set opts [parseargs args { - {outfd.arg stdout} - count.int - } -ignoreunknown] - - set hq [evt_query {*}$args] - trap { - while {[llength [set hevts [evt_next $hq]]]} { - trap { - foreach ev [recordarray getlist [evt_decode_events $hevts -message -ignorestring None.] -format dict] { - if {[info exists opts(count)] && - [incr opts(count) -1] < 0} { - return - } - puts $opts(outfd) "[dict get $ev -timecreated] [dict get $ev -eventrecordid] [dict get $ev -providername]: [dict get $ev -eventrecordid] [dict get $ev -message]" - } - } finally { - evt_close {*}$hevts - } - } - } finally { - evt_close $hq - } -} +# +# Copyright (c) 2012-2014, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Event log handling for Vista and later + +namespace eval twapi { + variable _evt; # See _evt_init + + # System event fields in order returned by _evt_decode_event_system_fields + twapi::record evt_system_fields { + -providername -providerguid -eventid -qualifiers -level -task + -opcode -keywordmask -timecreated -eventrecordid -activityid + -relatedactivityid -pid -tid -channel + -computer -sid -version + } + + proc _evt_init {} { + variable _evt + + # Various structures that we maintain / cache for efficiency as they + # are commonly used are kept in the _evt array with the following keys: + + # system_render_context_handle - is the handle to a rendering + # context for the system portion of an event + set _evt(system_render_context_handle) [evt_render_context_system] + + # user_render_context_handle - is the handle to a rendering + # context for the user data portion of an event + set _evt(user_render_context_handle) [evt_render_context_user] + + # render_buffer - is NULL or holds a pointer to the buffer used to + # retrieve values so does not have to be reallocated every time. + set _evt(render_buffer) NULL + + # publisher_handles - caches publisher names to their meta information. + # This is a dictionary indexed with nested keys - + # publisher, session, lcid. TBD - need a mechanism to clear ? + set _evt(publisher_handles) [dict create] + + # -levelname - dict of publisher name / level number to level names + set _evt(-levelname) {} + + # -taskname - dict of publisher name / task number to task name + set _evt(-taskname) {} + + # -opcodename - dict of publisher name / opcode number to opcode name + set _evt(-opcodename) {} + + # No-op the proc once init is done + proc _evt_init {} {} + } +} + +# TBD - document +proc twapi::evt_local_session {} { + return NULL +} + +# TBD - document +proc twapi::evt_local_session? {hsess} { + return [pointer_null? $hsess] +} + +# TBD - document +proc twapi::evt_open_session {server args} { + array set opts [parseargs args { + user.arg + domain.arg + password.arg + {authtype.arg 0} + } -nulldefault -maxleftover 0] + + if {![string is integer -strict $opts(authtype)]} { + set opts(authtype) [dict get {default 0 negotiate 1 kerberos 2 ntlm 3} [string tolower $opts(authtype)]] + } + + return [EvtOpenSession 1 [list $server $opts(user) $opts(domain) $opts(password) $opts(authtype)] 0 0] +} + +# TBD - document +proc twapi::evt_close_session {hsess} { + if {![evt_local_session? $hsess]} { + evt_close $hsess + } +} + +proc twapi::evt_channels {{hevtsess NULL}} { + # TBD - document hevtsess + set chnames {} + set hevt [EvtOpenChannelEnum $hevtsess 0] + trap { + while {[set chname [EvtNextChannelPath $hevt]] ne ""} { + lappend chnames $chname + } + } finally { + evt_close $hevt + } + + return $chnames +} + +proc twapi::evt_clear_log {chanpath args} { + # TBD - document -session + array set opts [parseargs args { + {session.arg NULL} + {backup.arg ""} + } -maxleftover 0] + + return [EvtClearLog $opts(session) $chanpath [_evt_normalize_path $opts(backup)] 0] +} + +# TBD - document +proc twapi::evt_archive_exported_log {logpath args} { + array set opts [parseargs args { + {session.arg NULL} + {lcid.int 0} + } -maxleftover 0] + + return [EvtArchiveExportedLog $opts(session) [_evt_normalize_path $logpath] $opts(lcid) 0] +} + +proc twapi::evt_export_log {outfile args} { + # TBD - document -session + array set opts [parseargs args { + {session.arg NULL} + file.arg + channel.arg + {query.arg *} + {ignorequeryerrors 0 0x1000} + } -maxleftover 0] + + if {([info exists opts(file)] && [info exists opts(channel)]) || + ! ([info exists opts(file)] || [info exists opts(channel)])} { + error "Exactly one of -file or -channel must be specified." + } + + if {[info exists opts(file)]} { + set path [_evt_normalize_path $opts(file)] + incr opts(ignorequeryerrors) 2 + } else { + set path $opts(channel) + incr opts(ignorequeryerrors) 1 + } + + return [EvtExportLog $opts(session) $path $opts(query) [_evt_normalize_path $outfile] $opts(ignorequeryerrors)] +} + +# TBD - document +proc twapi::evt_create_bookmark {{mark ""}} { + return [EvtCreateBookmark $mark] +} + +# TBD - document +proc twapi::evt_render_context_xpaths {xpaths} { + return [EvtCreateRenderContext $xpaths 0] +} + +# TBD - document +proc twapi::evt_render_context_system {} { + return [EvtCreateRenderContext {} 1] +} + +# TBD - document +proc twapi::evt_render_context_user {} { + return [EvtCreateRenderContext {} 2] +} + +# TBD - document +proc twapi::evt_open_channel_config {chanpath args} { + array set opts [parseargs args { + {session.arg NULL} + } -maxleftover 0] + + return [EvtOpenChannelConfig $opts(session) $chanpath 0] +} + +# TBD - document +proc twapi::evt_get_channel_config {hevt args} { + set result {} + foreach opt $args { + lappend result $opt \ + [EvtGetChannelConfigProperty $hevt \ + [_evt_map_channel_config_property $hevt $propid]] + } + return $result +} + +# TBD - document +proc twapi::evt_set_channel_config {hevt propid val} { + return [EvtSetChannelConfigProperty $hevt [_evt_map_channel_config_property $propid 0 $val]] +} + + +# TBD - document +proc twapi::_evt_map_channel_config_property {propid} { + if {[string is integer -strict $propid]} { + return $propid + } + + # Note: values are from winevt.h, Win7 SDK has typos for last few + return [dict get { + -enabled 0 + -isolation 1 + -type 2 + -owningpublisher 3 + -classiceventlog 4 + -access 5 + -loggingretention 6 + -loggingautobackup 7 + -loggingmaxsize 8 + -logginglogfilepath 9 + -publishinglevel 10 + -publishingkeywords 11 + -publishingcontrolguid 12 + -publishingbuffersize 13 + -publishingminbuffers 14 + -publishingmaxbuffers 15 + -publishinglatency 16 + -publishingclocktype 17 + -publishingsidtype 18 + -publisherlist 19 + -publishingfilemax 20 + } $propid] +} + +# TBD - document +proc twapi::evt_event_info {hevt args} { + set result {} + foreach opt $args { + lappend result $opt [EvtGetEventInfo $hevt \ + [dict get {-queryids 0 -path 1} $opt]] + } + return $result +} + + +# TBD - document +proc twapi::evt_event_metadata_property {hevt args} { + set result {} + foreach opt $args { + lappend result $opt \ + [EvtGetEventMetadataProperty $hevt \ + [dict get { + -id 0 -version 1 -channel 2 -level 3 + -opcode 4 -task 5 -keyword 6 -messageid 7 -template 8 + } $opt]] + } + return $result +} + + +# TBD - document +proc twapi::evt_open_log_info {args} { + array set opts [parseargs args { + {session.arg NULL} + file.arg + channel.arg + } -maxleftover 0] + + if {([info exists opts(file)] && [info exists opts(channel)]) || + ! ([info exists opts(file)] || [info exists opts(channel)])} { + error "Exactly one of -file or -channel must be specified." + } + + if {[info exists opts(file)]} { + set path [_evt_normalize_path $opts(file)] + set flags 0x2 + } else { + set path $opts(channel) + set flags 0x1 + } + + return [EvtOpenLog $opts(session) $path $flags] +} + +# TBD - document +proc twapi::evt_log_info {hevt args} { + set result {} + foreach opt $args { + lappend result $opt [EvtGetLogInfo $hevt [dict get { + -creationtime 0 -lastaccesstime 1 -lastwritetime 2 + -filesize 3 -attributes 4 -numberoflogrecords 5 + -oldestrecordnumber 6 -full 7 + } $opt]] + } + return $result +} + +# TBD - document +proc twapi::evt_publisher_metadata_property {hpub args} { + set result {} + foreach opt $args { + set val [EvtGetPublisherMetadataProperty $hpub [dict get { + -publisherguid 0 -resourcefilepath 1 -parameterfilepath 2 + -messagefilepath 3 -helplink 4 -publishermessageid 5 + -channelreferences 6 -levels 12 -tasks 16 + -opcodes 21 -keywords 25 + } $opt] 0] + if {$opt ni {-channelreferences -levels -tasks -opcodes -keywords}} { + lappend result $opt $val + continue + } + set n [EvtGetObjectArraySize $val] + set val2 {} + for {set i 0} {$i < $n} {incr i} { + set rec {} + foreach {opt2 iopt} [dict get { + -channelreferences { -channelreferencepath 7 + -channelreferenceindex 8 -channelreferenceid 9 + -channelreferenceflags 10 -channelreferencemessageid 11} + -levels { -levelname 13 -levelvalue 14 -levelmessageid 15 } + -tasks { -taskname 17 -taskeventguid 18 -taskvalue 19 + -taskmessageid 20} + -opcodes {-opcodename 22 -opcodevalue 23 -opcodemessageid 24} + -keywords {-keywordname 26 -keywordvalue 27 + -keywordmessageid 28} + } $opt] { + lappend rec $opt2 [EvtGetObjectArrayProperty $val $iopt $i] + } + lappend val2 $rec + } + + evt_close $val + lappend result $opt $val2 + } + return $result +} + +# TBD - document +proc twapi::evt_query_info {hq args} { + set result {} + foreach opt $args { + lappend result $opt [EvtGetQueryInfo $hq [dict get { + -names 1 statuses 2 + } $opt]] + } + return $result +} + +# TBD - document +proc twapi::evt_object_array_size {hevt} { + return [EvtGetObjectArraySize $hevt] +} + +# TBD - document +proc twapi::evt_object_array_property {hevt index args} { + set result {} + + foreach opt $args { + lappend result $opt \ + [EvtGetObjectArrayProperty $hevt [dict get { + -channelreferencepath 7 + -channelreferenceindex 8 -channelreferenceid 9 + -channelreferenceflags 10 -channelreferencemessageid 11 + -levelname 13 -levelvalue 14 -levelmessageid 15 + -taskname 17 -taskeventguid 18 -taskvalue 19 + -taskmessageid 20 -opcodename 22 + -opcodevalue 23 -opcodemessageid 24 + -keywordname 26 -keywordvalue 27 -keywordmessageid 28 + }] $index] + } + return $result +} + +proc twapi::evt_publishers {{hsess NULL}} { + set pubs {} + set hevt [EvtOpenPublisherEnum $hsess 0] + trap { + while {[set pub [EvtNextPublisherId $hevt]] ne ""} { + lappend pubs $pub + } + } finally { + evt_close $hevt + } + + return $pubs +} + +# TBD - document +proc twapi::evt_open_publisher_metadata {pub args} { + array set opts [parseargs args { + {session.arg NULL} + logfile.arg + lcid.int + } -nulldefault -maxleftover 0] + + return [EvtOpenPublisherMetadata $opts(session) $pub $opts(logfile) $opts(lcid) 0] +} + +# TBD - document +proc twapi::evt_publisher_events_metadata {hpub args} { + set henum [EvtOpenEventMetadataEnum $hpub] + + # It is faster to build a list and then have Tcl shimmer to a dict when + # required + set meta {} + trap { + while {[set hmeta [EvtNextEventMetadata $henum 0]] ne ""} { + lappend meta [evt_event_metadata_property $hmeta {*}$args] + evt_close $hmeta + } + } finally { + evt_close $henum + } + + return $meta +} + +proc twapi::evt_query {args} { + array set opts [parseargs args { + {session.arg NULL} + file.arg + channel.arg + {query.arg *} + {ignorequeryerrors 0 0x1000} + {direction.sym forward {forward 0x100 reverse 0x200 backward 0x200}} + } -maxleftover 0] + + if {([info exists opts(file)] && [info exists opts(channel)]) || + ! ([info exists opts(file)] || [info exists opts(channel)])} { + error "Exactly one of -file or -channel must be specified." + } + + set flags $opts(ignorequeryerrors) + incr flags $opts(direction) + + if {[info exists opts(file)]} { + set path [_evt_normalize_path $opts(file)] + incr flags 0x2 + } else { + set path $opts(channel) + incr flags 0x1 + } + + return [EvtQuery $opts(session) $path $opts(query) $flags] +} + +proc twapi::evt_next {hresultset args} { + array set opts [parseargs args { + {timeout.int -1} + {count.int 1} + {status.arg} + } -maxleftover 0] + + if {[info exists opts(status)]} { + upvar 1 $opts(status) status + return [EvtNext $hresultset $opts(count) $opts(timeout) 0 status] + } else { + return [EvtNext $hresultset $opts(count) $opts(timeout) 0] + } +} + +twapi::proc* twapi::_evt_decode_event_system_fields {hevt} { + _evt_init +} { + variable _evt + set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(system_render_context_handle) $hevt $_evt(render_buffer)] + set rec [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] + return [evt_system_fields set $rec \ + -providername [atomize [evt_system_fields -providername $rec]] \ + -providerguid [atomize [evt_system_fields -providerguid $rec]] \ + -channel [atomize [evt_system_fields -channel $rec]] \ + -computer [atomize [evt_system_fields -computer $rec]]] +} + +# TBD - document. Returns a list of user data values +twapi::proc* twapi::evt_decode_event_userdata {hevt} { + _evt_init +} { + variable _evt + set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(user_render_context_handle) $hevt $_evt(render_buffer)] + return [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] +} + +twapi::proc* twapi::evt_decode_events {hevts args} { + _evt_init +} { + variable _evt + + array set opts [parseargs args { + {values.arg NULL} + {session.arg NULL} + {logfile.arg ""} + {lcid.int 0} + ignorestring.arg + message + levelname + taskname + opcodename + keywords + xml + } -ignoreunknown -hyphenated] + + # SAME ORDER AS _evt_decode_event_system_fields + set decoded_fields [evt_system_fields] + set decoded_events {} + + # ORDER MUST BE SAME AS order in which values are appended below + foreach opt {-levelname -taskname -opcodename -keywords -xml -message} { + if {$opts($opt)} { + lappend decoded_fields $opt + } + } + + foreach hevt $hevts { + set decoded [_evt_decode_event_system_fields $hevt] + # Get publisher from hevt + set publisher [evt_system_fields -providername $decoded] + + if {! [dict exists $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)]} { + if {[catch { + dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) [EvtOpenPublisherMetadata $opts(-session) $publisher $opts(-logfile) $opts(-lcid) 0] + }]} { + # TBD - debug log + dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) NULL + } + } + set hpub [dict get $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)] + + # See if cached values are present for -levelname -taskname + # and -opcodename. TBD - can -keywords be added to this ? + foreach {intopt opt callflag} {-level -levelname 2 -task -taskname 3 -opcode -opcodename 4} { + if {$opts($opt)} { + set ival [evt_system_fields $intopt $decoded] + if {[dict exists $_evt($opt) $publisher $ival]} { + lappend decoded [dict get $_evt($opt) $publisher $ival] + } else { + # Not cached. Look it up. Value of 0 -> null so + # just use ignorestring if specified. + if {$ival == 0 && [info exists opts(-ignorestring)]} { + set optval $opts(-ignorestring) + } else { + if {[info exists opts(-ignorestring)]} { + if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { + dict set _evt($opt) $publisher $ival $optval + } else { + # Note result not cached if not found since + # ignorestring may be different on every call + set optval $opts(-ignorestring) + } + } else { + # -ignorestring not specified so + # will raise error if not found + set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] + dict set _evt($opt) $publisher $ival [atomize $optval] + } + } + lappend decoded $optval + } + } + } + + # Non-cached fields + # ORDER MUST BE SAME AS decoded_fields ABOVE + foreach {opt callflag} { + -keywords 5 + -xml 9 + } { + if {$opts($opt)} { + if {[info exists opts(-ignorestring)]} { + if {! [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { + set optval $opts(-ignorestring) + } + } else { + set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] + } + lappend decoded $optval + } + } + + # We treat -message differently because on failure we want + # to extract the user data. -ignorestring is not used for this + # unless user data extraction also fails + if {$opts(-message)} { + if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) 1 message]} { + lappend decoded $message + } else { + # TBD - make sure we have a test for this case. + # TBD - log + if {[catch { + lappend decoded "Message for event could not be found. Event contained user data: [join [evt_decode_event_userdata $hevt] ,]" + } message]} { + if {[info exists opts(-ignorestring)]} { + lappend decoded $opts(-ignorestring) + } else { + error $message + } + } + } + } + + lappend decoded_events $decoded + } + + return [list $decoded_fields $decoded_events] +} + +proc twapi::evt_decode_event {hevt args} { + return [recordarray index [evt_decode_events [list $hevt] {*}$args] 0 -format dict] +} + +# TBD - document +proc twapi::evt_format_publisher_message {hpub msgid args} { + + array set opts [parseargs args { + {values.arg NULL} + } -maxleftover 0] + + return [EvtFormatMessage $hpub NULL $msgid $opts(values) 8] +} + +# TBD - document +# Where is this used? +proc twapi::evt_free_EVT_VARIANT_ARRAY {p} { + evt_free $p +} + +# TBD - document +# Where is this used? +proc twapi::evt_free_EVT_RENDER_VALUES {p} { + evt_free $p +} + +# TBD - document +proc twapi::evt_seek {hresults pos args} { + array set opts [parseargs args { + {origin.arg first {first last current}} + bookmark.arg + {strict 0 0x10000} + } -maxleftover 0] + + if {[info exists opts(bookmark)]} { + set flags 4 + } else { + set flags [lsearch -exact {first last current} $opts(origin)] + incr flags; # 1 -> first, 2 -> last, 3 -> current + set opts(bookmark) NULL + } + + incr flags $opts(strict) + + EvtSeek $hresults $pos $opts(bookmark) 0 $flags +} + +proc twapi::evt_subscribe {path args} { + # TBD - document -session and -bookmark and -strict + array set opts [parseargs args { + {session.arg NULL} + {query.arg *} + bookmark.arg + includeexisting + {ignorequeryerrors 0 0x1000} + {strict 0 0x10000} + } -maxleftover 0] + + set flags [expr {$opts(ignorequeryerrors) | $opts(strict)}] + if {[info exists opts(bookmark)]} { + set flags [expr {$flags | 3}] + set bookmark $opts(origin) + } else { + set bookmark NULL + if {$opts(includeexisting)} { + set flags [expr {$flags | 2}] + } else { + set flags [expr {$flags | 1}] + } + } + + set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] + if {[catch { + EvtSubscribe $opts(session) $hevent $path $opts(query) $bookmark $flags + } hsubscribe]} { + set erinfo $::errorInfo + set ercode $::errorCode + CloseHandle $hevent + error $hsubscribe $erinfo $ercode + } + + return [list $hsubscribe $hevent] +} + +proc twapi::_evt_normalize_path {path} { + # Do not want to rely on [file normalize] returning "" for "" + if {$path eq ""} { + return "" + } else { + return [file nativename [file normalize $path]] + } +} + +proc twapi::_evt_dump {args} { + array set opts [parseargs args { + {outfd.arg stdout} + count.int + } -ignoreunknown] + + set hq [evt_query {*}$args] + trap { + while {[llength [set hevts [evt_next $hq]]]} { + trap { + foreach ev [recordarray getlist [evt_decode_events $hevts -message -ignorestring None.] -format dict] { + if {[info exists opts(count)] && + [incr opts(count) -1] < 0} { + return + } + puts $opts(outfd) "[dict get $ev -timecreated] [dict get $ev -eventrecordid] [dict get $ev -providername]: [dict get $ev -eventrecordid] [dict get $ev -message]" + } + } finally { + evt_close {*}$hevts + } + } + } finally { + evt_close $hq + } +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/handle.tcl b/src/vendorlib_tcl8/twapi-5.0b1/handle.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/handle.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/handle.tcl index 223608ac..66a3a1d3 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/handle.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/handle.tcl @@ -1,236 +1,236 @@ -# -# Copyright (c) 2010, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - # Array maps handles we are waiting on to the ids of the registered waits - variable _wait_handle_ids - # Array maps id of registered wait to the corresponding callback scripts - variable _wait_handle_scripts - -} - -proc twapi::cast_handle {h type} { - # TBD - should this use pointer_from_address: - # return [pointer_from_address [address_from_pointer $h] $type] - return [list [lindex $h 0] $type] -} - -proc twapi::close_handle {h} { - - # Cancel waits on the handle, if any - cancel_wait_on_handle $h - - # Then close it - CloseHandle $h -} - -# Close multiple handles. In case of errors, collects them but keeps -# closing remaining handles and only raises the error at the end. -proc twapi::close_handles {args} { - # The original definition for this was broken in that it would - # gracefully accept non list parameters as a list of one. In 3.0 - # the handle format has changed so this does not happen - # naturally. We have to try and decipher whether it is a list - # of handles or a single handle. - - foreach arg $args { - if {[pointer? $arg]} { - # Looks like a single handle - if {[catch {close_handle $arg} msg]} { - set erinfo $::errorInfo - set ercode $::errorCode - set ermsg $msg - } - } else { - # Assume a list of handles - foreach h $arg { - if {[catch {close_handle $h} msg]} { - set erinfo $::errorInfo - set ercode $::errorCode - set ermsg $msg - } - } - } - } - - if {[info exists erinfo]} { - error $msg $erinfo $ercode - } -} - -# -# Wait on a handle -proc twapi::wait_on_handle {hwait args} { - variable _wait_handle_ids - variable _wait_handle_scripts - - # When we are invoked from callback, handle is always typed as HANDLE - # so convert it so lookups succeed - set h [cast_handle $hwait HANDLE] - - # 0x00000008 -> # WT_EXECUTEONCEONLY - array set opts [parseargs args { - {wait.int -1} - async.arg - {executeonce.bool false 0x00000008} - }] - - if {![info exists opts(async)]} { - if {[info exists _wait_handle_ids($h)]} { - error "Attempt to synchronously wait on handle that is registered for an asynchronous wait." - } - - set ret [WaitForSingleObject $h $opts(wait)] - if {$ret == 0x80} { - return abandoned - } elseif {$ret == 0} { - return signalled - } elseif {$ret == 0x102} { - return timeout - } else { - error "Unexpected value $ret returned from WaitForSingleObject" - } - } - - # async option specified - - # Do not wait on manual reset events as cpu will spin continuously - # queueing events - if {[pointer? $hwait HANDLE_MANUALRESETEVENT] && - ! $opts(executeonce) - } { - error "A handle to a manual reset event cannot be waited on asynchronously unless -executeonce is specified." - } - - # If handle already registered, cancel previous registration. - if {[info exists _wait_handle_ids($h)]} { - cancel_wait_on_handle $h - } - - - set id [Twapi_RegisterWaitOnHandle $h $opts(wait) $opts(executeonce)] - - # Set now that successfully registered - set _wait_handle_scripts($id) $opts(async) - set _wait_handle_ids($h) $id - - return -} - -# -# Cancel an async wait on a handle -proc twapi::cancel_wait_on_handle {h} { - variable _wait_handle_ids - variable _wait_handle_scripts - - if {[info exists _wait_handle_ids($h)]} { - Twapi_UnregisterWaitOnHandle $_wait_handle_ids($h) - unset _wait_handle_scripts($_wait_handle_ids($h)) - unset _wait_handle_ids($h) - } -} - -# -# Called from C when a handle is signalled or times out -proc twapi::_wait_handler {id h event} { - variable _wait_handle_ids - variable _wait_handle_scripts - - # We ignore the following stale event cases - - # - _wait_handle_ids($h) does not exist : the wait was canceled while - # and event was queued - # - _wait_handle_ids($h) exists but is different from $id - same - # as prior case, except that a new wait has since been initiated - # on the same handle value (which might have be for a different - # resource - - if {[info exists _wait_handle_ids($h)] && - $_wait_handle_ids($h) == $id} { - uplevel #0 [linsert $_wait_handle_scripts($id) end $h $event] - } - - return -} - -# Get the handle for a Tcl channel -proc twapi::get_tcl_channel_handle {chan direction} { - set direction [expr {[string equal $direction "write"] ? 1 : 0}] - return [Tcl_GetChannelHandle $chan $direction] -} - -# Duplicate a OS handle -proc twapi::duplicate_handle {h args} { - variable my_process_handle - - array set opts [parseargs args { - sourcepid.int - targetpid.int - access.arg - inherit - closesource - } -maxleftover 0] - - # Assume source and target processes are us - set source_ph $my_process_handle - set target_ph $my_process_handle - - if {[string is wideinteger $h]} { - set h [pointer_from_address $h HANDLE] - } - - trap { - set me [pid] - # If source pid specified and is not us, get a handle to the process - if {[info exists opts(sourcepid)] && $opts(sourcepid) != $me} { - set source_ph [get_process_handle $opts(sourcepid) -access process_dup_handle] - } - - # Ditto for target process... - if {[info exists opts(targetpid)] && $opts(targetpid) != $me} { - set target_ph [get_process_handle $opts(targetpid) -access process_dup_handle] - } - - # Do we want to close the original handle (DUPLICATE_CLOSE_SOURCE) - set flags [expr {$opts(closesource) ? 0x1: 0}] - - if {[info exists opts(access)]} { - set access [_access_rights_to_mask $opts(access)] - } else { - # If no desired access is indicated, we want the same access as - # the original handle - set access 0 - set flags [expr {$flags | 0x2}]; # DUPLICATE_SAME_ACCESS - } - - - set dup [DuplicateHandle $source_ph $h $target_ph $access $opts(inherit) $flags] - - # IF targetpid specified, return handle else literal - # (even if targetpid is us) - if {[info exists opts(targetpid)]} { - set dup [pointer_to_address $dup] - } - } finally { - if {$source_ph != $my_process_handle} { - CloseHandle $source_ph - } - if {$target_ph != $my_process_handle} { - CloseHandle $source_ph - } - } - - return $dup -} - -proc twapi::set_handle_inheritance {h inherit} { - # 1 -> HANDLE_FLAG_INHERIT - SetHandleInformation $h 0x1 [expr {$inherit ? 1 : 0}] -} - -proc twapi::get_handle_inheritance {h} { - # 1 -> HANDLE_FLAG_INHERIT - return [expr {[GetHandleInformation $h] & 1}] -} +# +# Copyright (c) 2010, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + # Array maps handles we are waiting on to the ids of the registered waits + variable _wait_handle_ids + # Array maps id of registered wait to the corresponding callback scripts + variable _wait_handle_scripts + +} + +proc twapi::cast_handle {h type} { + # TBD - should this use pointer_from_address: + # return [pointer_from_address [address_from_pointer $h] $type] + return [list [lindex $h 0] $type] +} + +proc twapi::close_handle {h} { + + # Cancel waits on the handle, if any + cancel_wait_on_handle $h + + # Then close it + CloseHandle $h +} + +# Close multiple handles. In case of errors, collects them but keeps +# closing remaining handles and only raises the error at the end. +proc twapi::close_handles {args} { + # The original definition for this was broken in that it would + # gracefully accept non list parameters as a list of one. In 3.0 + # the handle format has changed so this does not happen + # naturally. We have to try and decipher whether it is a list + # of handles or a single handle. + + foreach arg $args { + if {[pointer? $arg]} { + # Looks like a single handle + if {[catch {close_handle $arg} msg]} { + set erinfo $::errorInfo + set ercode $::errorCode + set ermsg $msg + } + } else { + # Assume a list of handles + foreach h $arg { + if {[catch {close_handle $h} msg]} { + set erinfo $::errorInfo + set ercode $::errorCode + set ermsg $msg + } + } + } + } + + if {[info exists erinfo]} { + error $msg $erinfo $ercode + } +} + +# +# Wait on a handle +proc twapi::wait_on_handle {hwait args} { + variable _wait_handle_ids + variable _wait_handle_scripts + + # When we are invoked from callback, handle is always typed as HANDLE + # so convert it so lookups succeed + set h [cast_handle $hwait HANDLE] + + # 0x00000008 -> # WT_EXECUTEONCEONLY + array set opts [parseargs args { + {wait.int -1} + async.arg + {executeonce.bool false 0x00000008} + }] + + if {![info exists opts(async)]} { + if {[info exists _wait_handle_ids($h)]} { + error "Attempt to synchronously wait on handle that is registered for an asynchronous wait." + } + + set ret [WaitForSingleObject $h $opts(wait)] + if {$ret == 0x80} { + return abandoned + } elseif {$ret == 0} { + return signalled + } elseif {$ret == 0x102} { + return timeout + } else { + error "Unexpected value $ret returned from WaitForSingleObject" + } + } + + # async option specified + + # Do not wait on manual reset events as cpu will spin continuously + # queueing events + if {[pointer? $hwait HANDLE_MANUALRESETEVENT] && + ! $opts(executeonce) + } { + error "A handle to a manual reset event cannot be waited on asynchronously unless -executeonce is specified." + } + + # If handle already registered, cancel previous registration. + if {[info exists _wait_handle_ids($h)]} { + cancel_wait_on_handle $h + } + + + set id [Twapi_RegisterWaitOnHandle $h $opts(wait) $opts(executeonce)] + + # Set now that successfully registered + set _wait_handle_scripts($id) $opts(async) + set _wait_handle_ids($h) $id + + return +} + +# +# Cancel an async wait on a handle +proc twapi::cancel_wait_on_handle {h} { + variable _wait_handle_ids + variable _wait_handle_scripts + + if {[info exists _wait_handle_ids($h)]} { + Twapi_UnregisterWaitOnHandle $_wait_handle_ids($h) + unset _wait_handle_scripts($_wait_handle_ids($h)) + unset _wait_handle_ids($h) + } +} + +# +# Called from C when a handle is signalled or times out +proc twapi::_wait_handler {id h event} { + variable _wait_handle_ids + variable _wait_handle_scripts + + # We ignore the following stale event cases - + # - _wait_handle_ids($h) does not exist : the wait was canceled while + # and event was queued + # - _wait_handle_ids($h) exists but is different from $id - same + # as prior case, except that a new wait has since been initiated + # on the same handle value (which might have be for a different + # resource + + if {[info exists _wait_handle_ids($h)] && + $_wait_handle_ids($h) == $id} { + uplevel #0 [linsert $_wait_handle_scripts($id) end $h $event] + } + + return +} + +# Get the handle for a Tcl channel +proc twapi::get_tcl_channel_handle {chan direction} { + set direction [expr {[string equal $direction "write"] ? 1 : 0}] + return [Tcl_GetChannelHandle $chan $direction] +} + +# Duplicate a OS handle +proc twapi::duplicate_handle {h args} { + variable my_process_handle + + array set opts [parseargs args { + sourcepid.int + targetpid.int + access.arg + inherit + closesource + } -maxleftover 0] + + # Assume source and target processes are us + set source_ph $my_process_handle + set target_ph $my_process_handle + + if {[string is wideinteger $h]} { + set h [pointer_from_address $h HANDLE] + } + + trap { + set me [pid] + # If source pid specified and is not us, get a handle to the process + if {[info exists opts(sourcepid)] && $opts(sourcepid) != $me} { + set source_ph [get_process_handle $opts(sourcepid) -access process_dup_handle] + } + + # Ditto for target process... + if {[info exists opts(targetpid)] && $opts(targetpid) != $me} { + set target_ph [get_process_handle $opts(targetpid) -access process_dup_handle] + } + + # Do we want to close the original handle (DUPLICATE_CLOSE_SOURCE) + set flags [expr {$opts(closesource) ? 0x1: 0}] + + if {[info exists opts(access)]} { + set access [_access_rights_to_mask $opts(access)] + } else { + # If no desired access is indicated, we want the same access as + # the original handle + set access 0 + set flags [expr {$flags | 0x2}]; # DUPLICATE_SAME_ACCESS + } + + + set dup [DuplicateHandle $source_ph $h $target_ph $access $opts(inherit) $flags] + + # IF targetpid specified, return handle else literal + # (even if targetpid is us) + if {[info exists opts(targetpid)]} { + set dup [pointer_to_address $dup] + } + } finally { + if {$source_ph != $my_process_handle} { + CloseHandle $source_ph + } + if {$target_ph != $my_process_handle} { + CloseHandle $source_ph + } + } + + return $dup +} + +proc twapi::set_handle_inheritance {h inherit} { + # 1 -> HANDLE_FLAG_INHERIT + SetHandleInformation $h 0x1 [expr {$inherit ? 1 : 0}] +} + +proc twapi::get_handle_inheritance {h} { + # 1 -> HANDLE_FLAG_INHERIT + return [expr {[GetHandleInformation $h] & 1}] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/input.tcl b/src/vendorlib_tcl8/twapi-5.0b1/input.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/input.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/input.tcl index cdae8cea..1756b8f8 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/input.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/input.tcl @@ -1,623 +1,623 @@ -# -# Copyright (c) 2012 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -package require twapi_ui; # SetCursorPos etc. - -# Enable window input -proc twapi::enable_window_input {hwin} { - return [expr {[EnableWindow $hwin 1] != 0}] -} - -# Disable window input -proc twapi::disable_window_input {hwin} { - return [expr {[EnableWindow $hwin 0] != 0}] -} - -# CHeck if window input is enabled -proc twapi::window_input_enabled {hwin} { - return [IsWindowEnabled $hwin] -} - -# Simulate user input -proc twapi::send_input {inputlist} { - array set input_defs { - MOUSEEVENTF_MOVE 0x0001 - MOUSEEVENTF_LEFTDOWN 0x0002 - MOUSEEVENTF_LEFTUP 0x0004 - MOUSEEVENTF_RIGHTDOWN 0x0008 - MOUSEEVENTF_RIGHTUP 0x0010 - MOUSEEVENTF_MIDDLEDOWN 0x0020 - MOUSEEVENTF_MIDDLEUP 0x0040 - MOUSEEVENTF_XDOWN 0x0080 - MOUSEEVENTF_XUP 0x0100 - MOUSEEVENTF_WHEEL 0x0800 - MOUSEEVENTF_VIRTUALDESK 0x4000 - MOUSEEVENTF_ABSOLUTE 0x8000 - - KEYEVENTF_EXTENDEDKEY 0x0001 - KEYEVENTF_KEYUP 0x0002 - KEYEVENTF_UNICODE 0x0004 - KEYEVENTF_SCANCODE 0x0008 - - XBUTTON1 0x0001 - XBUTTON2 0x0002 - } - - set inputs [list ] - foreach input $inputlist { - if {[string equal [lindex $input 0] "mouse"]} { - lassign $input mouse xpos ypos - set mouseopts [lrange $input 3 end] - array unset opts - array set opts [parseargs mouseopts { - relative moved - ldown lup rdown rup mdown mup x1down x1up x2down x2up - wheel.int - }] - set flags 0 - if {! $opts(relative)} { - set flags $input_defs(MOUSEEVENTF_ABSOLUTE) - } - - if {[info exists opts(wheel)]} { - if {($opts(x1down) || $opts(x1up) || $opts(x2down) || $opts(x2up))} { - error "The -wheel input event attribute may not be specified with -x1up, -x1down, -x2up or -x2down events" - } - set mousedata $opts(wheel) - set flags $input_defs(MOUSEEVENTF_WHEEL) - } else { - if {$opts(x1down) || $opts(x1up)} { - if {$opts(x2down) || $opts(x2up)} { - error "The -x1down, -x1up mouse input attributes are mutually exclusive with -x2down, -x2up attributes" - } - set mousedata $input_defs(XBUTTON1) - } else { - if {$opts(x2down) || $opts(x2up)} { - set mousedata $input_defs(XBUTTON2) - } else { - set mousedata 0 - } - } - } - foreach {opt flag} { - moved MOVE - ldown LEFTDOWN - lup LEFTUP - rdown RIGHTDOWN - rup RIGHTUP - mdown MIDDLEDOWN - mup MIDDLEUP - x1down XDOWN - x1up XUP - x2down XDOWN - x2up XUP - } { - if {$opts($opt)} { - set flags [expr {$flags | $input_defs(MOUSEEVENTF_$flag)}] - } - } - - lappend inputs [list mouse $xpos $ypos $mousedata $flags] - - } else { - lassign $input inputtype vk scan keyopts - if {"-extended" ni $keyopts} { - set extended 0 - } else { - set extended $input_defs(KEYEVENTF_EXTENDEDKEY) - } - if {"-usescan" ni $keyopts} { - set usescan 0 - } else { - set usescan $input_defs(KEYEVENTF_SCANCODE) - } - switch -exact -- $inputtype { - keydown { - lappend inputs [list key $vk $scan [expr {$extended|$usescan}]] - } - keyup { - lappend inputs [list key $vk $scan \ - [expr {$extended - | $usescan - | $input_defs(KEYEVENTF_KEYUP) - }]] - } - key { - lappend inputs [list key $vk $scan [expr {$extended|$usescan}]] - lappend inputs [list key $vk $scan \ - [expr {$extended - | $usescan - | $input_defs(KEYEVENTF_KEYUP) - }]] - } - unicode { - lappend inputs [list key 0 $scan $input_defs(KEYEVENTF_UNICODE)] - lappend inputs [list key 0 $scan \ - [expr {$input_defs(KEYEVENTF_UNICODE) - | $input_defs(KEYEVENTF_KEYUP) - }]] - } - default { - error "Unknown input type '$inputtype'" - } - } - } - } - - SendInput $inputs -} - -# Block the input -proc twapi::block_input {} { - return [BlockInput 1] -} - -# Unblock the input -proc twapi::unblock_input {} { - return [BlockInput 0] -} - -# Send the given set of characters to the input queue -proc twapi::send_input_text {s} { - return [Twapi_SendUnicode $s] -} - -# send_keys - uses same syntax as VB SendKeys function -proc twapi::send_keys {keys} { - set inputs [_parse_send_keys $keys] - send_input $inputs -} - - -# Handles a hotkey notification -proc twapi::_hotkey_handler {msg atom key msgpos ticks} { - variable _hotkeys - - # Note it is not an error if a hotkey does not exist since it could - # have been deregistered in the time between hotkey input and receiving it. - set code 0 - if {[info exists _hotkeys($atom)]} { - foreach handler $_hotkeys($atom) { - set code [catch {uplevel #0 $handler} msg] - switch -exact -- $code { - 0 { - # Normal, keep going - } - 1 { - # Error - put in background and abort - after 0 [list error $msg $::errorInfo $::errorCode] - break - } - 3 { - break; # Ignore remaining handlers - } - default { - # Keep going - } - } - } - } - return -code $code "" -} - -proc twapi::register_hotkey {hotkey script args} { - variable _hotkeys - - # 0x312 -> WM_HOTKEY - _register_script_wm_handler 0x312 [list [namespace current]::_hotkey_handler] 1 - - array set opts [parseargs args { - append - } -maxleftover 0] - -# set script [lrange $script 0 end]; # Ensure a valid list - - lassign [_hotkeysyms_to_vk $hotkey] modifiers vk - set hkid "twapi_hk_${vk}_$modifiers" - set atom [GlobalAddAtom $hkid] - if {[info exists _hotkeys($atom)]} { - GlobalDeleteAtom $atom; # Undo above AddAtom since already there - if {$opts(append)} { - lappend _hotkeys($atom) $script - } else { - set _hotkeys($atom) [list $script]; # Replace previous script - } - return $atom - } - trap { - RegisterHotKey $atom $modifiers $vk - } onerror {} { - GlobalDeleteAtom $atom; # Undo above AddAtom - rethrow - } - set _hotkeys($atom) [list $script]; # Replace previous script - return $atom -} - -proc twapi::unregister_hotkey {atom} { - variable _hotkeys - if {[info exists _hotkeys($atom)]} { - UnregisterHotKey $atom - GlobalDeleteAtom $atom - unset _hotkeys($atom) - } -} - - -# Simulate clicking a mouse button -proc twapi::click_mouse_button {button} { - switch -exact -- $button { - 1 - - left { set down -ldown ; set up -lup} - 2 - - right { set down -rdown ; set up -rup} - 3 - - middle { set down -mdown ; set up -mup} - x1 { set down -x1down ; set up -x1up} - x2 { set down -x2down ; set up -x2up} - default {error "Invalid mouse button '$button' specified"} - } - - send_input [list \ - [list mouse 0 0 $down] \ - [list mouse 0 0 $up]] - return -} - -# Simulate mouse movement -proc twapi::move_mouse {xpos ypos {mode ""}} { - # If mouse trails are enabled, it leaves traces when the mouse is - # moved and does not clear them until mouse is moved again. So - # we temporarily disable mouse trails if we can - - if {[llength [info commands ::twapi::get_system_parameters_info]] != 0} { - set trail [get_system_parameters_info SPI_GETMOUSETRAILS] - set_system_parameters_info SPI_SETMOUSETRAILS 0 - } - switch -exact -- $mode { - -relative { - lappend cmd -relative - lassign [GetCursorPos] curx cury - incr xpos $curx - incr ypos $cury - } - -absolute - - "" { } - default { error "Invalid mouse movement mode '$mode'" } - } - - SetCursorPos $xpos $ypos - - # Restore trail setting if we had disabled it and it was originally enabled - if {[info exists trail] && $trail} { - set_system_parameters_info SPI_SETMOUSETRAILS $trail - } -} - -# Simulate turning of the mouse wheel -proc twapi::turn_mouse_wheel {wheelunits} { - send_input [list [list mouse 0 0 -relative -wheel $wheelunits]] - return -} - -# Get the mouse/cursor position -proc twapi::get_mouse_location {} { - return [GetCursorPos] -} - -proc twapi::get_input_idle_time {} { - # The formats are to convert wrapped 32bit signed to unsigned - set last_event [format 0x%x [GetLastInputInfo]] - set now [format 0x%x [GetTickCount]] - - # Deal with wrap around - if {$now >= $last_event} { - return [expr {$now - $last_event}] - } else { - return [expr {$now + (0xffffffff - $last_event) + 1}] - } -} - -# Initialize the virtual key table -proc twapi::_init_vk_map {} { - variable vk_map - - if {![info exists vk_map]} { - # Map tokens to VK_* key codes - array set vk_map { - BACK {0x08 0} - BACKSPACE {0x08 0} BS {0x08 0} BKSP {0x08 0} TAB {0x09 0} - CLEAR {0x0C 0} RETURN {0x0D 0} ENTER {0x0D 0} SHIFT {0x10 0} - CONTROL {0x11 0} MENU {0x12 0} ALT {0x12 0} PAUSE {0x13 0} - BREAK {0x13 0} CAPITAL {0x14 0} CAPSLOCK {0x14 0} - KANA {0x15 0} HANGEUL {0x15 0} HANGUL {0x15 0} JUNJA {0x17 0} - FINAL {0x18 0} HANJA {0x19 0} KANJI {0x19 0} ESCAPE {0x1B 0} - ESC {0x1B 0} CONVERT {0x1C 0} NONCONVERT {0x1D 0} - ACCEPT {0x1E 0} MODECHANGE {0x1F 0} SPACE {0x20 0} - PRIOR {0x21 0} PGUP {0x21 0} NEXT {0x22 0} PGDN {0x22 0} - END {0x23 0} HOME {0x24 0} LEFT {0x25 0} UP {0x26 0} - RIGHT {0x27 0} DOWN {0x28 0} SELECT {0x29 0} - PRINT {0x2A 0} PRTSC {0x2C 0} EXECUTE {0x2B 0} - SNAPSHOT {0x2C 0} INSERT {0x2D 0} INS {0x2D 0} - DELETE {0x2E 0} DEL {0x2E 0} HELP {0x2F 0} LWIN {0x5B 0} - RWIN {0x5C 0} APPS {0x5D 0} SLEEP {0x5F 0} NUMPAD0 {0x60 0} - NUMPAD1 {0x61 0} NUMPAD2 {0x62 0} NUMPAD3 {0x63 0} - NUMPAD4 {0x64 0} NUMPAD5 {0x65 0} NUMPAD6 {0x66 0} - NUMPAD7 {0x67 0} NUMPAD8 {0x68 0} NUMPAD9 {0x69 0} - MULTIPLY {0x6A 0} ADD {0x6B 0} SEPARATOR {0x6C 0} - SUBTRACT {0x6D 0} DECIMAL {0x6E 0} DIVIDE {0x6F 0} - F1 {0x70 0} F2 {0x71 0} F3 {0x72 0} F4 {0x73 0} - F5 {0x74 0} F6 {0x75 0} F7 {0x76 0} F8 {0x77 0} - F9 {0x78 0} F10 {0x79 0} F11 {0x7A 0} F12 {0x7B 0} - F13 {0x7C 0} F14 {0x7D 0} F15 {0x7E 0} F16 {0x7F 0} - F17 {0x80 0} F18 {0x81 0} F19 {0x82 0} F20 {0x83 0} - F21 {0x84 0} F22 {0x85 0} F23 {0x86 0} F24 {0x87 0} - NUMLOCK {0x90 0} SCROLL {0x91 0} SCROLLLOCK {0x91 0} - LSHIFT {0xA0 0} RSHIFT {0xA1 0 -extended} LCONTROL {0xA2 0} - RCONTROL {0xA3 0 -extended} LMENU {0xA4 0} LALT {0xA4 0} - RMENU {0xA5 0 -extended} RALT {0xA5 0 -extended} - BROWSER_BACK {0xA6 0} BROWSER_FORWARD {0xA7 0} - BROWSER_REFRESH {0xA8 0} BROWSER_STOP {0xA9 0} - BROWSER_SEARCH {0xAA 0} BROWSER_FAVORITES {0xAB 0} - BROWSER_HOME {0xAC 0} VOLUME_MUTE {0xAD 0} - VOLUME_DOWN {0xAE 0} VOLUME_UP {0xAF 0} - MEDIA_NEXT_TRACK {0xB0 0} MEDIA_PREV_TRACK {0xB1 0} - MEDIA_STOP {0xB2 0} MEDIA_PLAY_PAUSE {0xB3 0} - LAUNCH_MAIL {0xB4 0} LAUNCH_MEDIA_SELECT {0xB5 0} - LAUNCH_APP1 {0xB6 0} LAUNCH_APP2 {0xB7 0} - } - } -} - -# Find the next token from a send_keys argument -# Returns pair token,position after token -proc twapi::_parse_send_key_token {keys start} { - set char [string index $keys $start] - if {$char ne "\{"} { - return [list $char [incr start]] - } - # Need to find the matching end brace. Note special case of - # start/end brace enclosed within braces - set n [string length $keys] - # Jump past brace and succeeding character (which may be end brace) - set terminator [string first "\}" $keys $start+2] - if {$terminator < 0} { - error "Unterminated or empty braced key token." - } - return [list [string range $keys $start $terminator] [incr terminator]] -} - -# Appends to inputs the trailer in reverse order. trailer is reset -proc twapi::_flush_send_keys_trailer {vinputs vtrailer} { - upvar 1 $vinputs inputs - upvar 1 $vtrailer trailer - - lappend inputs {*}[lreverse $trailer] - set trailer {} -} - -# Constructs a list of input events by parsing a string in the format -# used by Visual Basic's SendKeys function. See that documentation -# for syntax. -proc twapi::_parse_send_keys {keys} { - variable vk_map - - _init_vk_map - array set modifier_vk {+ 0x10 ^ 0x11 % 0x12} - - # Array state holds state of the parse. An atom refers to a single - # character or a () group. - # modifiers - list of current modifiers in order they were added including - # those coming from containing groups. - # group_modifiers - stack of modifiers state when parsing groups. - # When a group begins, state(modifiers) is pushed on this stack. - # The top of the stack is used to initialize state(modifiers) - # for every atom within the group. When the group ends, - # the top of the stack is popped and discarded and state(modifiers) - # is reinitialized to new top of stack. - # trailer - list of trailing input records to add after next atom. Note - # these are stored in order of occurence but need to be reversed - # when emitted - # group_trailers - stack of trailers to add after group ends. Each - # element is a trailer which is a list of input records. - # cleanup_trailer - to be emitted right at the end if we have to - # reset CAPSLOCK/NUMLOCK/SCROLL - set state(modifiers) {} - set state(group_modifiers) [list $state(modifiers)]; # "Global" group - set state(trailer) {} - set state(group_trailers) {} - set state(cleanup_trailer) {} - - set inputs {} - - # If {CAPS,NUM,SCROLL}LOCK are set, need to reset them and then - # set them back - foreach vk {20 144 145} { - if {[GetKeyState $vk]} { - lappend inputs [list key $vk 0] - lappend state(cleanup_trailer) [list key $vk 0] - } - } - - set keyslen [string length $keys] - set pos 0; # Current parse position - while {$pos < $keyslen} { - lassign [_parse_send_key_token $keys $pos] token pos - switch -exact -- $token { - + - - ^ - - % { - if {$token in $state(modifiers)} { - # Following VB SendKeys - error "Modifier state for $token already set." - } - lappend state(modifiers) $token - lappend inputs [list keydown $modifier_vk($token) 0] - lappend state(trailer) [list keyup $modifier_vk($token) 0] - } - "(" { - # Start a group - lappend state(group_modifiers) $state(modifiers) - lappend state(group_trailers) $state(trailer) - set state(trailer) {} - } - ")" { - # Terminates group. Illegal if no group collection in progress - if {[llength $state(group_trailers)] == 0} { - error "Unmatched \")\" in send_keys string." - } - # If there is a live trailer inside group, emit it e.g. +(ab^) - _flush_send_keys_trailer inputs state(trailer) - # Now emit the group trailer - set trailer [lpop state(group_trailers)] - _flush_send_keys_trailer inputs trailer - # Discard the initial modifier state for this group - lpop state(group_modifiers) - # Set the current modifiers to outer group state - set state(modifiers) [lindex $state(group_modifiers) end] - } - default { - if {$token eq "~"} { - set token "{ENTER}" - } - # May be a single character to send, a braced virtual key - # or a braced single char with count - if {[string length $token] == 1} { - # Single character. - set key $token - set nch 1 - } elseif {[string index $token 0] eq "\{"} { - # NOTE: a ~ inside a brace is treated as a literal ~ - # and not the ENTER key - # Look for space skipping the starting brace and following - # character which may be itself a space (to be repeated) - set space_pos [string first " " $token 2] - if {$space_pos < 0} { - # No space found - set nch 1 - set key [string range $token 1 end-1] - } else { - # A key followed by a count - # Note space_pos >= 2 - set key [string range $token 1 $space_pos-1] - set nch [string trim [string range $token $space_pos+1 end-1]] - if {![string is integer -strict $nch] || $nch < 0} { - error "Invalid count \"$nch\" in send_keys." - } - } - } else { - # Problem in token parsing. Would be a bug. - error "Internal error: invalid token \"$token\" parsing send_keys string." - } - - set vk_leader {} - set vk_trailer {} - if {[string length $key] == 1} { - # Single character - lassign [VkKeyScan $key] modifiers vk - if {$modifiers == -1 || $vk == -1} { - scan $key %c code_point - set vk_rec [list unicode 0 $code_point] - } else { - # Generates input records for modifiers that are set - # unless they are already set. NOTE: Do NOT set the - # state(modifier) state since they will be in effect - # only for the current character. This is for correctly - # showing A-Z with shift and Ctrl-A etc. with control. - if {($modifiers & 0x1) && ("+" ni $state(modifiers))} { - lappend vk_leader [list keydown 0x10 0] - lappend vk_trailer [list keyup 0x10 0] - } - if {($modifiers & 0x2) && ("^" ni $state(modifiers))} { - lappend vk_leader [list keydown 0x11 0] - lappend vk_trailer [list keyup 0x11 0] - } - - if {($modifiers & 0x4) && ("%" ni $state(modifiers))} { - lappend vk_leader [list keydown 0x12 0] - lappend vk_trailer [list keyup 0x12 0] - } - set vk_rec [list key $vk 0] - } - } else { - # Virtual key string. Note modifiers ignored here - # as for VB SendKeys - if {[info exists vk_map($key)]} { - # Virtual key - set vk_rec [list key {*}$vk_map($key)] - } else { - error "Unknown braced virtual key \"$token\"." - } - } - lappend inputs {*}$vk_leader - lappend inputs {*}[lrepeat $nch $vk_rec] - # vk_trailer arises from the character itself, e.g. A - # has shift set, Ctrl-A has control set. - _flush_send_keys_trailer inputs vk_trailer - # state(trailer) arises from preceding +,^,% This is also - # emitted and reset as it applied only to this character - _flush_send_keys_trailer inputs state(trailer) - set state(modifiers) [lindex $state(group_modifiers) end] - } - } - } - # Emit left over trailer - _flush_send_keys_trailer inputs state(trailer) - - # Restore capslock/numlock - _flush_send_keys_trailer inputs state(cleanup_trailer) - - return $inputs -} - -# utility procedure to map symbolic hotkey to {modifiers virtualkey} -# We allow modifier map to be passed in because different api's use -# different bits for key modifiers -proc twapi::_hotkeysyms_to_vk {hotkey {modifier_map {ctrl 2 control 2 alt 1 menu 1 shift 4 win 8}}} { - variable vk_map - - _init_vk_map - - set keyseq [split [string tolower $hotkey] -] - set key [lindex $keyseq end] - - # Convert modifiers to bitmask - set modifiers 0 - foreach modifier [lrange $keyseq 0 end-1] { - setbits modifiers [dict! $modifier_map [string tolower $modifier]] - } - # Map the key to a virtual key code - if {[string length $key] == 1} { - # Single character - scan $key %c unicode - - # Only allow alphanumeric keys and a few punctuation symbols - # since keyboard layouts are not standard - if {$unicode >= 0x61 && $unicode <= 0x7A} { - # Lowercase letters - change to upper case virtual keys - set vk [expr {$unicode-32}] - } elseif {($unicode >= 0x30 && $unicode <= 0x39) - || ($unicode >= 0x41 && $unicode <= 0x5A)} { - # Digits or upper case - set vk $unicode - } else { - error "Only alphanumeric characters may be specified for the key. For non-alphanumeric characters, specify the virtual key code" - } - } elseif {[info exists vk_map($key)]} { - # It is a virtual key name - set vk [lindex $vk_map($key) 0] - } elseif {[info exists vk_map([string toupper $key])]} { - # It is a virtual key name - set vk [lindex $vk_map([string toupper $key]) 0] - } elseif {[string is integer -strict $key]} { - # Actual virtual key specification - set vk $key - } else { - error "Unknown or invalid key specifier '$key'" - } - - return [list $modifiers $vk] -} +# +# Copyright (c) 2012 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +package require twapi_ui; # SetCursorPos etc. + +# Enable window input +proc twapi::enable_window_input {hwin} { + return [expr {[EnableWindow $hwin 1] != 0}] +} + +# Disable window input +proc twapi::disable_window_input {hwin} { + return [expr {[EnableWindow $hwin 0] != 0}] +} + +# CHeck if window input is enabled +proc twapi::window_input_enabled {hwin} { + return [IsWindowEnabled $hwin] +} + +# Simulate user input +proc twapi::send_input {inputlist} { + array set input_defs { + MOUSEEVENTF_MOVE 0x0001 + MOUSEEVENTF_LEFTDOWN 0x0002 + MOUSEEVENTF_LEFTUP 0x0004 + MOUSEEVENTF_RIGHTDOWN 0x0008 + MOUSEEVENTF_RIGHTUP 0x0010 + MOUSEEVENTF_MIDDLEDOWN 0x0020 + MOUSEEVENTF_MIDDLEUP 0x0040 + MOUSEEVENTF_XDOWN 0x0080 + MOUSEEVENTF_XUP 0x0100 + MOUSEEVENTF_WHEEL 0x0800 + MOUSEEVENTF_VIRTUALDESK 0x4000 + MOUSEEVENTF_ABSOLUTE 0x8000 + + KEYEVENTF_EXTENDEDKEY 0x0001 + KEYEVENTF_KEYUP 0x0002 + KEYEVENTF_UNICODE 0x0004 + KEYEVENTF_SCANCODE 0x0008 + + XBUTTON1 0x0001 + XBUTTON2 0x0002 + } + + set inputs [list ] + foreach input $inputlist { + if {[string equal [lindex $input 0] "mouse"]} { + lassign $input mouse xpos ypos + set mouseopts [lrange $input 3 end] + array unset opts + array set opts [parseargs mouseopts { + relative moved + ldown lup rdown rup mdown mup x1down x1up x2down x2up + wheel.int + }] + set flags 0 + if {! $opts(relative)} { + set flags $input_defs(MOUSEEVENTF_ABSOLUTE) + } + + if {[info exists opts(wheel)]} { + if {($opts(x1down) || $opts(x1up) || $opts(x2down) || $opts(x2up))} { + error "The -wheel input event attribute may not be specified with -x1up, -x1down, -x2up or -x2down events" + } + set mousedata $opts(wheel) + set flags $input_defs(MOUSEEVENTF_WHEEL) + } else { + if {$opts(x1down) || $opts(x1up)} { + if {$opts(x2down) || $opts(x2up)} { + error "The -x1down, -x1up mouse input attributes are mutually exclusive with -x2down, -x2up attributes" + } + set mousedata $input_defs(XBUTTON1) + } else { + if {$opts(x2down) || $opts(x2up)} { + set mousedata $input_defs(XBUTTON2) + } else { + set mousedata 0 + } + } + } + foreach {opt flag} { + moved MOVE + ldown LEFTDOWN + lup LEFTUP + rdown RIGHTDOWN + rup RIGHTUP + mdown MIDDLEDOWN + mup MIDDLEUP + x1down XDOWN + x1up XUP + x2down XDOWN + x2up XUP + } { + if {$opts($opt)} { + set flags [expr {$flags | $input_defs(MOUSEEVENTF_$flag)}] + } + } + + lappend inputs [list mouse $xpos $ypos $mousedata $flags] + + } else { + lassign $input inputtype vk scan keyopts + if {"-extended" ni $keyopts} { + set extended 0 + } else { + set extended $input_defs(KEYEVENTF_EXTENDEDKEY) + } + if {"-usescan" ni $keyopts} { + set usescan 0 + } else { + set usescan $input_defs(KEYEVENTF_SCANCODE) + } + switch -exact -- $inputtype { + keydown { + lappend inputs [list key $vk $scan [expr {$extended|$usescan}]] + } + keyup { + lappend inputs [list key $vk $scan \ + [expr {$extended + | $usescan + | $input_defs(KEYEVENTF_KEYUP) + }]] + } + key { + lappend inputs [list key $vk $scan [expr {$extended|$usescan}]] + lappend inputs [list key $vk $scan \ + [expr {$extended + | $usescan + | $input_defs(KEYEVENTF_KEYUP) + }]] + } + unicode { + lappend inputs [list key 0 $scan $input_defs(KEYEVENTF_UNICODE)] + lappend inputs [list key 0 $scan \ + [expr {$input_defs(KEYEVENTF_UNICODE) + | $input_defs(KEYEVENTF_KEYUP) + }]] + } + default { + error "Unknown input type '$inputtype'" + } + } + } + } + + SendInput $inputs +} + +# Block the input +proc twapi::block_input {} { + return [BlockInput 1] +} + +# Unblock the input +proc twapi::unblock_input {} { + return [BlockInput 0] +} + +# Send the given set of characters to the input queue +proc twapi::send_input_text {s} { + return [Twapi_SendUnicode $s] +} + +# send_keys - uses same syntax as VB SendKeys function +proc twapi::send_keys {keys} { + set inputs [_parse_send_keys $keys] + send_input $inputs +} + + +# Handles a hotkey notification +proc twapi::_hotkey_handler {msg atom key msgpos ticks} { + variable _hotkeys + + # Note it is not an error if a hotkey does not exist since it could + # have been deregistered in the time between hotkey input and receiving it. + set code 0 + if {[info exists _hotkeys($atom)]} { + foreach handler $_hotkeys($atom) { + set code [catch {uplevel #0 $handler} msg] + switch -exact -- $code { + 0 { + # Normal, keep going + } + 1 { + # Error - put in background and abort + after 0 [list error $msg $::errorInfo $::errorCode] + break + } + 3 { + break; # Ignore remaining handlers + } + default { + # Keep going + } + } + } + } + return -code $code "" +} + +proc twapi::register_hotkey {hotkey script args} { + variable _hotkeys + + # 0x312 -> WM_HOTKEY + _register_script_wm_handler 0x312 [list [namespace current]::_hotkey_handler] 1 + + array set opts [parseargs args { + append + } -maxleftover 0] + +# set script [lrange $script 0 end]; # Ensure a valid list + + lassign [_hotkeysyms_to_vk $hotkey] modifiers vk + set hkid "twapi_hk_${vk}_$modifiers" + set atom [GlobalAddAtom $hkid] + if {[info exists _hotkeys($atom)]} { + GlobalDeleteAtom $atom; # Undo above AddAtom since already there + if {$opts(append)} { + lappend _hotkeys($atom) $script + } else { + set _hotkeys($atom) [list $script]; # Replace previous script + } + return $atom + } + trap { + RegisterHotKey $atom $modifiers $vk + } onerror {} { + GlobalDeleteAtom $atom; # Undo above AddAtom + rethrow + } + set _hotkeys($atom) [list $script]; # Replace previous script + return $atom +} + +proc twapi::unregister_hotkey {atom} { + variable _hotkeys + if {[info exists _hotkeys($atom)]} { + UnregisterHotKey $atom + GlobalDeleteAtom $atom + unset _hotkeys($atom) + } +} + + +# Simulate clicking a mouse button +proc twapi::click_mouse_button {button} { + switch -exact -- $button { + 1 - + left { set down -ldown ; set up -lup} + 2 - + right { set down -rdown ; set up -rup} + 3 - + middle { set down -mdown ; set up -mup} + x1 { set down -x1down ; set up -x1up} + x2 { set down -x2down ; set up -x2up} + default {error "Invalid mouse button '$button' specified"} + } + + send_input [list \ + [list mouse 0 0 $down] \ + [list mouse 0 0 $up]] + return +} + +# Simulate mouse movement +proc twapi::move_mouse {xpos ypos {mode ""}} { + # If mouse trails are enabled, it leaves traces when the mouse is + # moved and does not clear them until mouse is moved again. So + # we temporarily disable mouse trails if we can + + if {[llength [info commands ::twapi::get_system_parameters_info]] != 0} { + set trail [get_system_parameters_info SPI_GETMOUSETRAILS] + set_system_parameters_info SPI_SETMOUSETRAILS 0 + } + switch -exact -- $mode { + -relative { + lappend cmd -relative + lassign [GetCursorPos] curx cury + incr xpos $curx + incr ypos $cury + } + -absolute - + "" { } + default { error "Invalid mouse movement mode '$mode'" } + } + + SetCursorPos $xpos $ypos + + # Restore trail setting if we had disabled it and it was originally enabled + if {[info exists trail] && $trail} { + set_system_parameters_info SPI_SETMOUSETRAILS $trail + } +} + +# Simulate turning of the mouse wheel +proc twapi::turn_mouse_wheel {wheelunits} { + send_input [list [list mouse 0 0 -relative -wheel $wheelunits]] + return +} + +# Get the mouse/cursor position +proc twapi::get_mouse_location {} { + return [GetCursorPos] +} + +proc twapi::get_input_idle_time {} { + # The formats are to convert wrapped 32bit signed to unsigned + set last_event [format 0x%x [GetLastInputInfo]] + set now [format 0x%x [GetTickCount]] + + # Deal with wrap around + if {$now >= $last_event} { + return [expr {$now - $last_event}] + } else { + return [expr {$now + (0xffffffff - $last_event) + 1}] + } +} + +# Initialize the virtual key table +proc twapi::_init_vk_map {} { + variable vk_map + + if {![info exists vk_map]} { + # Map tokens to VK_* key codes + array set vk_map { + BACK {0x08 0} + BACKSPACE {0x08 0} BS {0x08 0} BKSP {0x08 0} TAB {0x09 0} + CLEAR {0x0C 0} RETURN {0x0D 0} ENTER {0x0D 0} SHIFT {0x10 0} + CONTROL {0x11 0} MENU {0x12 0} ALT {0x12 0} PAUSE {0x13 0} + BREAK {0x13 0} CAPITAL {0x14 0} CAPSLOCK {0x14 0} + KANA {0x15 0} HANGEUL {0x15 0} HANGUL {0x15 0} JUNJA {0x17 0} + FINAL {0x18 0} HANJA {0x19 0} KANJI {0x19 0} ESCAPE {0x1B 0} + ESC {0x1B 0} CONVERT {0x1C 0} NONCONVERT {0x1D 0} + ACCEPT {0x1E 0} MODECHANGE {0x1F 0} SPACE {0x20 0} + PRIOR {0x21 0} PGUP {0x21 0} NEXT {0x22 0} PGDN {0x22 0} + END {0x23 0} HOME {0x24 0} LEFT {0x25 0} UP {0x26 0} + RIGHT {0x27 0} DOWN {0x28 0} SELECT {0x29 0} + PRINT {0x2A 0} PRTSC {0x2C 0} EXECUTE {0x2B 0} + SNAPSHOT {0x2C 0} INSERT {0x2D 0} INS {0x2D 0} + DELETE {0x2E 0} DEL {0x2E 0} HELP {0x2F 0} LWIN {0x5B 0} + RWIN {0x5C 0} APPS {0x5D 0} SLEEP {0x5F 0} NUMPAD0 {0x60 0} + NUMPAD1 {0x61 0} NUMPAD2 {0x62 0} NUMPAD3 {0x63 0} + NUMPAD4 {0x64 0} NUMPAD5 {0x65 0} NUMPAD6 {0x66 0} + NUMPAD7 {0x67 0} NUMPAD8 {0x68 0} NUMPAD9 {0x69 0} + MULTIPLY {0x6A 0} ADD {0x6B 0} SEPARATOR {0x6C 0} + SUBTRACT {0x6D 0} DECIMAL {0x6E 0} DIVIDE {0x6F 0} + F1 {0x70 0} F2 {0x71 0} F3 {0x72 0} F4 {0x73 0} + F5 {0x74 0} F6 {0x75 0} F7 {0x76 0} F8 {0x77 0} + F9 {0x78 0} F10 {0x79 0} F11 {0x7A 0} F12 {0x7B 0} + F13 {0x7C 0} F14 {0x7D 0} F15 {0x7E 0} F16 {0x7F 0} + F17 {0x80 0} F18 {0x81 0} F19 {0x82 0} F20 {0x83 0} + F21 {0x84 0} F22 {0x85 0} F23 {0x86 0} F24 {0x87 0} + NUMLOCK {0x90 0} SCROLL {0x91 0} SCROLLLOCK {0x91 0} + LSHIFT {0xA0 0} RSHIFT {0xA1 0 -extended} LCONTROL {0xA2 0} + RCONTROL {0xA3 0 -extended} LMENU {0xA4 0} LALT {0xA4 0} + RMENU {0xA5 0 -extended} RALT {0xA5 0 -extended} + BROWSER_BACK {0xA6 0} BROWSER_FORWARD {0xA7 0} + BROWSER_REFRESH {0xA8 0} BROWSER_STOP {0xA9 0} + BROWSER_SEARCH {0xAA 0} BROWSER_FAVORITES {0xAB 0} + BROWSER_HOME {0xAC 0} VOLUME_MUTE {0xAD 0} + VOLUME_DOWN {0xAE 0} VOLUME_UP {0xAF 0} + MEDIA_NEXT_TRACK {0xB0 0} MEDIA_PREV_TRACK {0xB1 0} + MEDIA_STOP {0xB2 0} MEDIA_PLAY_PAUSE {0xB3 0} + LAUNCH_MAIL {0xB4 0} LAUNCH_MEDIA_SELECT {0xB5 0} + LAUNCH_APP1 {0xB6 0} LAUNCH_APP2 {0xB7 0} + } + } +} + +# Find the next token from a send_keys argument +# Returns pair token,position after token +proc twapi::_parse_send_key_token {keys start} { + set char [string index $keys $start] + if {$char ne "\{"} { + return [list $char [incr start]] + } + # Need to find the matching end brace. Note special case of + # start/end brace enclosed within braces + set n [string length $keys] + # Jump past brace and succeeding character (which may be end brace) + set terminator [string first "\}" $keys $start+2] + if {$terminator < 0} { + error "Unterminated or empty braced key token." + } + return [list [string range $keys $start $terminator] [incr terminator]] +} + +# Appends to inputs the trailer in reverse order. trailer is reset +proc twapi::_flush_send_keys_trailer {vinputs vtrailer} { + upvar 1 $vinputs inputs + upvar 1 $vtrailer trailer + + lappend inputs {*}[lreverse $trailer] + set trailer {} +} + +# Constructs a list of input events by parsing a string in the format +# used by Visual Basic's SendKeys function. See that documentation +# for syntax. +proc twapi::_parse_send_keys {keys} { + variable vk_map + + _init_vk_map + array set modifier_vk {+ 0x10 ^ 0x11 % 0x12} + + # Array state holds state of the parse. An atom refers to a single + # character or a () group. + # modifiers - list of current modifiers in order they were added including + # those coming from containing groups. + # group_modifiers - stack of modifiers state when parsing groups. + # When a group begins, state(modifiers) is pushed on this stack. + # The top of the stack is used to initialize state(modifiers) + # for every atom within the group. When the group ends, + # the top of the stack is popped and discarded and state(modifiers) + # is reinitialized to new top of stack. + # trailer - list of trailing input records to add after next atom. Note + # these are stored in order of occurence but need to be reversed + # when emitted + # group_trailers - stack of trailers to add after group ends. Each + # element is a trailer which is a list of input records. + # cleanup_trailer - to be emitted right at the end if we have to + # reset CAPSLOCK/NUMLOCK/SCROLL + set state(modifiers) {} + set state(group_modifiers) [list $state(modifiers)]; # "Global" group + set state(trailer) {} + set state(group_trailers) {} + set state(cleanup_trailer) {} + + set inputs {} + + # If {CAPS,NUM,SCROLL}LOCK are set, need to reset them and then + # set them back + foreach vk {20 144 145} { + if {[GetKeyState $vk]} { + lappend inputs [list key $vk 0] + lappend state(cleanup_trailer) [list key $vk 0] + } + } + + set keyslen [string length $keys] + set pos 0; # Current parse position + while {$pos < $keyslen} { + lassign [_parse_send_key_token $keys $pos] token pos + switch -exact -- $token { + + - + ^ - + % { + if {$token in $state(modifiers)} { + # Following VB SendKeys + error "Modifier state for $token already set." + } + lappend state(modifiers) $token + lappend inputs [list keydown $modifier_vk($token) 0] + lappend state(trailer) [list keyup $modifier_vk($token) 0] + } + "(" { + # Start a group + lappend state(group_modifiers) $state(modifiers) + lappend state(group_trailers) $state(trailer) + set state(trailer) {} + } + ")" { + # Terminates group. Illegal if no group collection in progress + if {[llength $state(group_trailers)] == 0} { + error "Unmatched \")\" in send_keys string." + } + # If there is a live trailer inside group, emit it e.g. +(ab^) + _flush_send_keys_trailer inputs state(trailer) + # Now emit the group trailer + set trailer [lpop state(group_trailers)] + _flush_send_keys_trailer inputs trailer + # Discard the initial modifier state for this group + lpop state(group_modifiers) + # Set the current modifiers to outer group state + set state(modifiers) [lindex $state(group_modifiers) end] + } + default { + if {$token eq "~"} { + set token "{ENTER}" + } + # May be a single character to send, a braced virtual key + # or a braced single char with count + if {[string length $token] == 1} { + # Single character. + set key $token + set nch 1 + } elseif {[string index $token 0] eq "\{"} { + # NOTE: a ~ inside a brace is treated as a literal ~ + # and not the ENTER key + # Look for space skipping the starting brace and following + # character which may be itself a space (to be repeated) + set space_pos [string first " " $token 2] + if {$space_pos < 0} { + # No space found + set nch 1 + set key [string range $token 1 end-1] + } else { + # A key followed by a count + # Note space_pos >= 2 + set key [string range $token 1 $space_pos-1] + set nch [string trim [string range $token $space_pos+1 end-1]] + if {![string is integer -strict $nch] || $nch < 0} { + error "Invalid count \"$nch\" in send_keys." + } + } + } else { + # Problem in token parsing. Would be a bug. + error "Internal error: invalid token \"$token\" parsing send_keys string." + } + + set vk_leader {} + set vk_trailer {} + if {[string length $key] == 1} { + # Single character + lassign [VkKeyScan $key] modifiers vk + if {$modifiers == -1 || $vk == -1} { + scan $key %c code_point + set vk_rec [list unicode 0 $code_point] + } else { + # Generates input records for modifiers that are set + # unless they are already set. NOTE: Do NOT set the + # state(modifier) state since they will be in effect + # only for the current character. This is for correctly + # showing A-Z with shift and Ctrl-A etc. with control. + if {($modifiers & 0x1) && ("+" ni $state(modifiers))} { + lappend vk_leader [list keydown 0x10 0] + lappend vk_trailer [list keyup 0x10 0] + } + if {($modifiers & 0x2) && ("^" ni $state(modifiers))} { + lappend vk_leader [list keydown 0x11 0] + lappend vk_trailer [list keyup 0x11 0] + } + + if {($modifiers & 0x4) && ("%" ni $state(modifiers))} { + lappend vk_leader [list keydown 0x12 0] + lappend vk_trailer [list keyup 0x12 0] + } + set vk_rec [list key $vk 0] + } + } else { + # Virtual key string. Note modifiers ignored here + # as for VB SendKeys + if {[info exists vk_map($key)]} { + # Virtual key + set vk_rec [list key {*}$vk_map($key)] + } else { + error "Unknown braced virtual key \"$token\"." + } + } + lappend inputs {*}$vk_leader + lappend inputs {*}[lrepeat $nch $vk_rec] + # vk_trailer arises from the character itself, e.g. A + # has shift set, Ctrl-A has control set. + _flush_send_keys_trailer inputs vk_trailer + # state(trailer) arises from preceding +,^,% This is also + # emitted and reset as it applied only to this character + _flush_send_keys_trailer inputs state(trailer) + set state(modifiers) [lindex $state(group_modifiers) end] + } + } + } + # Emit left over trailer + _flush_send_keys_trailer inputs state(trailer) + + # Restore capslock/numlock + _flush_send_keys_trailer inputs state(cleanup_trailer) + + return $inputs +} + +# utility procedure to map symbolic hotkey to {modifiers virtualkey} +# We allow modifier map to be passed in because different api's use +# different bits for key modifiers +proc twapi::_hotkeysyms_to_vk {hotkey {modifier_map {ctrl 2 control 2 alt 1 menu 1 shift 4 win 8}}} { + variable vk_map + + _init_vk_map + + set keyseq [split [string tolower $hotkey] -] + set key [lindex $keyseq end] + + # Convert modifiers to bitmask + set modifiers 0 + foreach modifier [lrange $keyseq 0 end-1] { + setbits modifiers [dict! $modifier_map [string tolower $modifier]] + } + # Map the key to a virtual key code + if {[string length $key] == 1} { + # Single character + scan $key %c unicode + + # Only allow alphanumeric keys and a few punctuation symbols + # since keyboard layouts are not standard + if {$unicode >= 0x61 && $unicode <= 0x7A} { + # Lowercase letters - change to upper case virtual keys + set vk [expr {$unicode-32}] + } elseif {($unicode >= 0x30 && $unicode <= 0x39) + || ($unicode >= 0x41 && $unicode <= 0x5A)} { + # Digits or upper case + set vk $unicode + } else { + error "Only alphanumeric characters may be specified for the key. For non-alphanumeric characters, specify the virtual key code" + } + } elseif {[info exists vk_map($key)]} { + # It is a virtual key name + set vk [lindex $vk_map($key) 0] + } elseif {[info exists vk_map([string toupper $key])]} { + # It is a virtual key name + set vk [lindex $vk_map([string toupper $key]) 0] + } elseif {[string is integer -strict $key]} { + # Actual virtual key specification + set vk $key + } else { + error "Unknown or invalid key specifier '$key'" + } + + return [list $modifiers $vk] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/msi.tcl b/src/vendorlib_tcl8/twapi-5.0b1/msi.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/msi.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/msi.tcl index f50cea2e..25943d88 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/msi.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/msi.tcl @@ -1,403 +1,432 @@ -# -# Copyright (c) 2003-2018, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Hack to work with the various build configuration. -if {[info commands ::twapi::get_version] ne ""} { - package provide twapi_msi [::twapi::get_version -patchlevel] -} - -# Rest of this file auto-generated - - -# Automatically generated type library interface -# File: msi.dll -# Name: WindowsInstaller -# GUID: {000C1092-0000-0000-C000-000000000046} -# Version: 1.0 -# LCID: 1033 -package require twapi_com - -namespace eval windowsinstaller { - - # Array mapping coclass names to their guids - variable _coclass_guids - - # Array mapping dispatch interface names to their guids - variable _dispatch_guids - - # Returns the GUID for a coclass or empty string if not found - proc coclass_guid {coclass_name} { - variable _coclass_guids - if {[info exists _coclass_guids($coclass_name)]} { - return $_coclass_guids($coclass_name) - } - return "" - } - # Returns the GUID for a dispatch name or empty string if not found - proc dispatch_guid {dispatch_name} { - variable _dispatch_guids - if {[info exists _dispatch_guids($dispatch_name)]} { - return $_dispatch_guids($dispatch_name) - } - return "" - } - # Marks the specified object to be of a specific dispatch/coclass type - proc declare {typename comobj} { - # First check if it is the name of a dispatch interface - set guid [dispatch_guid $typename] - if {$guid ne ""} { - $comobj -interfaceguid $guid - return - } - - # If not, check if it is the name of a coclass with a dispatch interface - set guid [coclass_guid $typename] - if {$guid ne ""} { - if {[info exists ::twapi::_coclass_idispatch_guids($guid)]} { - $comobj -interfaceguid $::twapi::_coclass_idispatch_guids($guid) - return - } - } - - error "Could not resolve interface for $coclass_name." - } - - # Enum MsiUILevel - array set MsiUILevel {msiUILevelNoChange 0 msiUILevelDefault 1 msiUILevelNone 2 msiUILevelBasic 3 msiUILevelReduced 4 msiUILevelFull 5 msiUILevelHideCancel 32 msiUILevelProgressOnly 64 msiUILevelEndDialog 128 msiUILevelSourceResOnly 256} - - # Enum MsiReadStream - array set MsiReadStream {msiReadStreamInteger 0 msiReadStreamBytes 1 msiReadStreamAnsi 2 msiReadStreamDirect 3} - - # Enum MsiRunMode - array set MsiRunMode {msiRunModeAdmin 0 msiRunModeAdvertise 1 msiRunModeMaintenance 2 msiRunModeRollbackEnabled 3 msiRunModeLogEnabled 4 msiRunModeOperations 5 msiRunModeRebootAtEnd 6 msiRunModeRebootNow 7 msiRunModeCabinet 8 msiRunModeSourceShortNames 9 msiRunModeTargetShortNames 10 msiRunModeWindows9x 12 msiRunModeZawEnabled 13 msiRunModeScheduled 16 msiRunModeRollback 17 msiRunModeCommit 18} - - # Enum MsiDatabaseState - array set MsiDatabaseState {msiDatabaseStateRead 0 msiDatabaseStateWrite 1} - - # Enum MsiViewModify - array set MsiViewModify {msiViewModifySeek -1 msiViewModifyRefresh 0 msiViewModifyInsert 1 msiViewModifyUpdate 2 msiViewModifyAssign 3 msiViewModifyReplace 4 msiViewModifyMerge 5 msiViewModifyDelete 6 msiViewModifyInsertTemporary 7 msiViewModifyValidate 8 msiViewModifyValidateNew 9 msiViewModifyValidateField 10 msiViewModifyValidateDelete 11} - - # Enum MsiColumnInfo - array set MsiColumnInfo {msiColumnInfoNames 0 msiColumnInfoTypes 1} - - # Enum MsiTransformError - array set MsiTransformError {msiTransformErrorNone 0 msiTransformErrorAddExistingRow 1 msiTransformErrorDeleteNonExistingRow 2 msiTransformErrorAddExistingTable 4 msiTransformErrorDeleteNonExistingTable 8 msiTransformErrorUpdateNonExistingRow 16 msiTransformErrorChangeCodePage 32 msiTransformErrorViewTransform 256} - - # Enum MsiEvaluateCondition - array set MsiEvaluateCondition {msiEvaluateConditionFalse 0 msiEvaluateConditionTrue 1 msiEvaluateConditionNone 2 msiEvaluateConditionError 3} - - # Enum MsiTransformValidation - array set MsiTransformValidation {msiTransformValidationNone 0 msiTransformValidationLanguage 1 msiTransformValidationProduct 2 msiTransformValidationPlatform 4 msiTransformValidationMajorVer 8 msiTransformValidationMinorVer 16 msiTransformValidationUpdateVer 32 msiTransformValidationLess 64 msiTransformValidationLessOrEqual 128 msiTransformValidationEqual 256 msiTransformValidationGreaterOrEqual 512 msiTransformValidationGreater 1024 msiTransformValidationUpgradeCode 2048} - - # Enum MsiDoActionStatus - array set MsiDoActionStatus {msiDoActionStatusNoAction 0 msiDoActionStatusSuccess 1 msiDoActionStatusUserExit 2 msiDoActionStatusFailure 3 msiDoActionStatusSuspend 4 msiDoActionStatusFinished 5 msiDoActionStatusWrongState 6 msiDoActionStatusBadActionData 7} - - # Enum MsiMessageStatus - array set MsiMessageStatus {msiMessageStatusError -1 msiMessageStatusNone 0 msiMessageStatusOk 1 msiMessageStatusCancel 2 msiMessageStatusAbort 3 msiMessageStatusRetry 4 msiMessageStatusIgnore 5 msiMessageStatusYes 6 msiMessageStatusNo 7} - - # Enum MsiMessageType - array set MsiMessageType {msiMessageTypeFatalExit 0 msiMessageTypeError 16777216 msiMessageTypeWarning 33554432 msiMessageTypeUser 50331648 msiMessageTypeInfo 67108864 msiMessageTypeFilesInUse 83886080 msiMessageTypeResolveSource 100663296 msiMessageTypeOutOfDiskSpace 117440512 msiMessageTypeActionStart 134217728 msiMessageTypeActionData 150994944 msiMessageTypeProgress 167772160 msiMessageTypeCommonData 184549376 msiMessageTypeOk 0 msiMessageTypeOkCancel 1 msiMessageTypeAbortRetryIgnore 2 msiMessageTypeYesNoCancel 3 msiMessageTypeYesNo 4 msiMessageTypeRetryCancel 5 msiMessageTypeDefault1 0 msiMessageTypeDefault2 256 msiMessageTypeDefault3 512} - - # Enum MsiInstallState - array set MsiInstallState {msiInstallStateNotUsed -7 msiInstallStateBadConfig -6 msiInstallStateIncomplete -5 msiInstallStateSourceAbsent -4 msiInstallStateInvalidArg -2 msiInstallStateUnknown -1 msiInstallStateBroken 0 msiInstallStateAdvertised 1 msiInstallStateRemoved 1 msiInstallStateAbsent 2 msiInstallStateLocal 3 msiInstallStateSource 4 msiInstallStateDefault 5} - - # Enum MsiCostTree - array set MsiCostTree {msiCostTreeSelfOnly 0 msiCostTreeChildren 1 msiCostTreeParents 2} - - # Enum MsiReinstallMode - array set MsiReinstallMode {msiReinstallModeFileMissing 2 msiReinstallModeFileOlderVersion 4 msiReinstallModeFileEqualVersion 8 msiReinstallModeFileExact 16 msiReinstallModeFileVerify 32 msiReinstallModeFileReplace 64 msiReinstallModeMachineData 128 msiReinstallModeUserData 256 msiReinstallModeShortcut 512 msiReinstallModePackage 1024} - - # Enum MsiInstallType - array set MsiInstallType {msiInstallTypeDefault 0 msiInstallTypeNetworkImage 1 msiInstallTypeSingleInstance 2} - - # Enum MsiInstallMode - array set MsiInstallMode {msiInstallModeNoSourceResolution -3 msiInstallModeNoDetection -2 msiInstallModeExisting -1 msiInstallModeDefault 0} - - # Enum MsiSignatureInfo - array set MsiSignatureInfo {msiSignatureInfoCertificate 0 msiSignatureInfoHash 1} - - # Enum MsiInstallContext - array set MsiInstallContext {msiInstallContextFirstVisible 0 msiInstallContextUserManaged 1 msiInstallContextUser 2 msiInstallContextMachine 4 msiInstallContextAllUserManaged 8} - - # Enum MsiInstallSourceType - array set MsiInstallSourceType {msiInstallSourceTypeUnknown 0 msiInstallSourceTypeNetwork 1 msiInstallSourceTypeURL 2 msiInstallSourceTypeMedia 4} - - # Enum MsiAssemblyType - array set MsiAssemblyType {msiProvideAssemblyNet 0 msiProvideAssemblyWin32 1} - - # Enum MsiProductScriptInfo - array set MsiProductScriptInfo {msiProductScriptInfoProductCode 0 msiProductScriptInfoProductLanguage 1 msiProductScriptInfoProductVersion 2 msiProductScriptInfoProductName 3 msiProductScriptInfoPackageName 4} - - # Enum MsiAdvertiseProductContext - array set MsiAdvertiseProductContext {msiAdvertiseProductMachine 0 msiAdvertiseProductUser 1} - - # Enum Constants - array set Constants {msiDatabaseNullInteger -2147483648} - - # Enum MsiOpenDatabaseMode - array set MsiOpenDatabaseMode {msiOpenDatabaseModeReadOnly 0 msiOpenDatabaseModeTransact 1 msiOpenDatabaseModeDirect 2 msiOpenDatabaseModeCreate 3 msiOpenDatabaseModeCreateDirect 4 msiOpenDatabaseModePatchFile 32} - - # Enum MsiSignatureOption - array set MsiSignatureOption {msiSignatureOptionInvalidHashFatal 1} - - # Enum MsiAdvertiseProductPlatform - array set MsiAdvertiseProductPlatform {msiAdvertiseCurrentPlatform 0 msiAdvertiseX86Platform 1 msiAdvertiseIA64Platform 2 msiAdvertiseX64Platform 4} - - # Enum MsiAdvertiseProductOptions - array set MsiAdvertiseProductOptions {msiAdvertiseDefault 0 msiAdvertiseSingleInstance 1} - - # Enum MsiAdvertiseScriptFlags - array set MsiAdvertiseScriptFlags {msiAdvertiseScriptCacheInfo 1 msiAdvertiseScriptShortcuts 4 msiAdvertiseScriptMachineAssign 8 msiAdvertiseScriptConfigurationRegistration 32 msiAdvertiseScriptValidateTransformsList 64 msiAdvertiseScriptClassInfoRegistration 128 msiAdvertiseScriptExtensionInfoRegistration 256 msiAdvertiseScriptAppInfo 384 msiAdvertiseScriptRegData 416} -} - -# Dispatch Interface Installer -set windowsinstaller::_dispatch_guids(Installer) "{000C1090-0000-0000-C000-000000000046}" -# Installer Methods -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CreateRecord 1033 1 {1 1033 1 {26 {29 256}} {{3 1}} Count} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenPackage 1033 1 {2 1033 1 {26 {29 512}} {{12 1} {3 {49 {3 0}}}} {PackagePath Options}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenProduct 1033 1 {3 1033 1 {26 {29 512}} {{8 1}} ProductCode} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenDatabase 1033 1 {4 1033 1 {26 {29 768}} {{8 1} {12 1}} {DatabasePath OpenMode}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} SummaryInformation 1033 2 {5 1033 2 {26 {29 1024}} {{8 1} {3 {49 {3 0}}}} {PackagePath UpdateCount}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} EnableLog 1033 1 {7 1033 1 24 {{8 1} {8 1}} {LogMode LogFile}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} InstallProduct 1033 1 {8 1033 1 24 {{8 1} {8 {49 {8 0}}}} {PackagePath PropertyValues}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Version 1033 2 {9 1033 2 8 {} {}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} LastErrorRecord 1033 1 {10 1033 1 {26 {29 256}} {} {}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RegistryValue 1033 1 {11 1033 1 8 {{12 1} {8 1} {12 17}} {Root Key Value}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileAttributes 1033 1 {13 1033 1 3 {{8 1}} FilePath} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileSize 1033 1 {15 1033 1 3 {{8 1}} FilePath} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileVersion 1033 1 {16 1033 1 8 {{8 1} {12 17}} {FilePath Language}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Environment 1033 2 {12 1033 2 8 {{8 1}} Variable} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Environment 1033 4 {12 1033 4 24 {{8 1} {8 1}} Variable} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductState 1033 2 {17 1033 2 {29 2432} {{8 1}} Product} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductInfo 1033 2 {18 1033 2 8 {{8 1} {8 1}} {Product Attribute}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ConfigureProduct 1033 1 {19 1033 1 24 {{8 1} {3 1} {3 1}} {Product InstallLevel InstallState}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ReinstallProduct 1033 1 {20 1033 1 24 {{8 1} {3 1}} {Product ReinstallMode}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CollectUserInfo 1033 1 {21 1033 1 24 {{8 1}} Product} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ApplyPatch 1033 1 {22 1033 1 24 {{8 1} {8 1} {3 1} {8 1}} {PatchPackage InstallPackage InstallType CommandLine}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureParent 1033 2 {23 1033 2 8 {{8 1} {8 1}} {Product Feature}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureState 1033 2 {24 1033 2 {29 2432} {{8 1} {8 1}} {Product Feature}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UseFeature 1033 1 {25 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature InstallMode}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureUsageCount 1033 2 {26 1033 2 3 {{8 1} {8 1}} {Product Feature}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureUsageDate 1033 2 {27 1033 2 7 {{8 1} {8 1}} {Product Feature}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ConfigureFeature 1033 1 {28 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature InstallState}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ReinstallFeature 1033 1 {29 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature ReinstallMode}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideComponent 1033 1 {30 1033 1 8 {{8 1} {8 1} {8 1} {3 1}} {Product Feature Component InstallMode}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentPath 1033 2 {31 1033 2 8 {{8 1} {8 1}} {Product Component}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideQualifiedComponent 1033 1 {32 1033 1 8 {{8 1} {8 1} {3 1}} {Category Qualifier InstallMode}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} QualifierDescription 1033 2 {33 1033 2 8 {{8 1} {8 1}} {Category Qualifier}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentQualifiers 1033 2 {34 1033 2 {26 {29 3328}} {{8 1}} Category} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Products 1033 2 {35 1033 2 {26 {29 3328}} {} {}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Features 1033 2 {36 1033 2 {26 {29 3328}} {{8 1}} Product} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Components 1033 2 {37 1033 2 {26 {29 3328}} {} {}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentClients 1033 2 {38 1033 2 {26 {29 3328}} {{8 1}} Component} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Patches 1033 2 {39 1033 2 {26 {29 3328}} {{8 1}} Product} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RelatedProducts 1033 2 {40 1033 2 {26 {29 3328}} {{8 1}} UpgradeCode} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchInfo 1033 2 {41 1033 2 8 {{8 1} {8 1}} {Patch Attribute}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchTransforms 1033 2 {42 1033 2 8 {{8 1} {8 1}} {Product Patch}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AddSource 1033 1 {43 1033 1 24 {{8 1} {8 1} {8 1}} {Product User Source}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ClearSourceList 1033 1 {44 1033 1 24 {{8 1} {8 1}} {Product User}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ForceSourceListResolution 1033 1 {45 1033 1 24 {{8 1} {8 1}} {Product User}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} GetShortcutTarget 1033 2 {46 1033 2 {26 {29 256}} {{8 1}} ShortcutPath} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileHash 1033 1 {47 1033 1 {26 {29 256}} {{8 1} {3 1}} {FilePath Options}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileSignatureInfo 1033 1 {48 1033 1 {27 17} {{8 1} {3 1} {3 1}} {FilePath Options Format}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RemovePatches 1033 1 {49 1033 1 24 {{8 1} {8 1} {3 1} {8 {49 {8 0}}}} {PatchList Product UninstallType PropertyList}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ApplyMultiplePatches 1033 1 {51 1033 1 24 {{8 1} {8 1} {8 1}} {PatchPackage Product PropertiesList}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Product 1033 2 {53 1033 2 25 {{8 1} {8 1} {3 1} {{26 9} 10}} {Product UserSid iContext retval}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Patch 1033 2 {56 1033 2 25 {{8 1} {8 1} {8 1} {3 1} {{26 9} 10}} {PatchCode ProductCode UserSid iContext retval}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductsEx 1033 2 {52 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1}} {Product UserSid Contexts}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchesEx 1033 2 {55 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1} {3 1}} {Product UserSid Contexts filter}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ExtractPatchXMLData 1033 1 {57 1033 1 8 {{8 1}} PatchPath} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductCode 1033 2 {58 1033 2 8 {{8 1}} Component} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductElevated 1033 2 {59 1033 2 11 {{8 1}} Product} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideAssembly 1033 1 {60 1033 1 8 {{8 1} {8 1} {3 1} {3 1}} {Assembly Context InstallMode AssemblyInfo}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductInfoFromScript 1033 2 {61 1033 2 12 {{8 1} {3 1}} {ScriptFile ProductInfo}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AdvertiseProduct 1033 1 {62 1033 1 24 {{8 1} {3 1} {8 {49 {8 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}}} {PackagePath iContext Transforms Language Options}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CreateAdvertiseScript 1033 1 {63 1033 1 24 {{8 1} {8 1} {8 {49 {8 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}}} {PackagePath ScriptFilePath Transforms Language Platform Options}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AdvertiseScript 1033 1 {64 1033 1 24 {{8 1} {3 1} {11 1}} {ScriptPath ScriptFlags RemoveItems}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchFiles 1033 2 {65 1033 2 {26 {29 3328}} {{8 1} {8 1}} {Product PatchPackages}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentsEx 1033 2 {66 1033 2 {26 {29 2816}} {{8 1} {3 1}} {UserSid Context}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentClientsEx 1033 2 {67 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1}} {ComponentCode UserSid Context}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentPathEx 1033 2 {9068 1033 2 {26 {29 4480}} {{8 1} {8 1} {8 1} {3 1}} {ProductCode ComponentCode UserSid Context}} -# Installer Properties -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UILevel 1033 2 {6 1033 2 {29 128} {} {}} -::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UILevel 1033 4 {6 1033 4 24 {{{29 128} 1}} {}} - -# Dispatch Interface Record -set windowsinstaller::_dispatch_guids(Record) "{000C1093-0000-0000-C000-000000000046}" -# Record Methods -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} StringData 1033 2 {1 1033 2 8 {{3 1}} Field} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} StringData 1033 4 {1 1033 4 24 {{3 1} {8 1}} Field} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IntegerData 1033 2 {2 1033 2 3 {{3 1}} Field} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IntegerData 1033 4 {2 1033 4 24 {{3 1} {3 1}} Field} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} SetStream 1033 1 {3 1033 1 24 {{3 1} {8 1}} {Field FilePath}} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} ReadStream 1033 1 {4 1033 1 8 {{3 1} {3 1} {3 1}} {Field Length Format}} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} FieldCount 1033 2 {0 1033 2 3 {} {}} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IsNull 1033 2 {6 1033 2 11 {{3 1}} Field} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} DataSize 1033 2 {5 1033 2 3 {{3 1}} Field} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} ClearData 1033 1 {7 1033 1 24 {} {}} -::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} FormatText 1033 1 {8 1033 1 8 {} {}} - -# Dispatch Interface Session -set windowsinstaller::_dispatch_guids(Session) "{000C109E-0000-0000-C000-000000000046}" -# Session Methods -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Installer 1033 2 {1 1033 2 {26 {29 0}} {} {}} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Property 1033 2 {2 1033 2 8 {{8 1}} Name} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Property 1033 4 {2 1033 4 24 {{8 1} {8 1}} Name} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Language 1033 2 {3 1033 2 3 {} {}} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Mode 1033 2 {4 1033 2 11 {{3 1}} Flag} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Mode 1033 4 {4 1033 4 24 {{3 1} {11 1}} Flag} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Database 1033 2 {5 1033 2 {26 {29 768}} {} {}} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} SourcePath 1033 2 {6 1033 2 8 {{8 1}} Folder} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} TargetPath 1033 2 {7 1033 2 8 {{8 1}} Folder} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} TargetPath 1033 4 {7 1033 4 24 {{8 1} {8 1}} Folder} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} DoAction 1033 1 {8 1033 1 {29 2048} {{8 1}} Action} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Sequence 1033 1 {9 1033 1 {29 2048} {{8 1} {12 17}} {Table Mode}} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} EvaluateCondition 1033 1 {10 1033 1 {29 1792} {{8 1}} Expression} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FormatRecord 1033 1 {11 1033 1 8 {{9 1}} Record} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Message 1033 1 {12 1033 1 {29 2176} {{3 1} {9 1}} {Kind Record}} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureCurrentState 1033 2 {13 1033 2 {29 2432} {{8 1}} Feature} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureRequestState 1033 2 {14 1033 2 {29 2432} {{8 1}} Feature} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureRequestState 1033 4 {14 1033 4 24 {{8 1} {3 1}} Feature} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureValidStates 1033 2 {15 1033 2 3 {{8 1}} Feature} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureCost 1033 2 {16 1033 2 3 {{8 1} {3 1} {3 1}} {Feature CostTree State}} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentCurrentState 1033 2 {17 1033 2 {29 2432} {{8 1}} Component} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentRequestState 1033 2 {18 1033 2 {29 2432} {{8 1}} Component} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentRequestState 1033 4 {18 1033 4 24 {{8 1} {3 1}} Component} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} SetInstallLevel 1033 1 {19 1033 1 24 {{3 1}} Level} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} VerifyDiskSpace 1033 2 {20 1033 2 11 {} {}} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ProductProperty 1033 2 {21 1033 2 8 {{8 1}} Property} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureInfo 1033 2 {22 1033 2 {26 {29 2688}} {{8 1}} Feature} -::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentCosts 1033 2 {23 1033 2 {26 {29 2816}} {{8 1} {3 1}} {Component State}} - -# Dispatch Interface Database -set windowsinstaller::_dispatch_guids(Database) "{000C109D-0000-0000-C000-000000000046}" -# Database Methods -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} DatabaseState 1033 2 {1 1033 2 {29 896} {} {}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} SummaryInformation 1033 2 {2 1033 2 {26 {29 1024}} {{3 {49 {3 0}}}} UpdateCount} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} OpenView 1033 1 {3 1033 1 {26 {29 1152}} {{8 1}} Sql} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Commit 1033 1 {4 1033 1 24 {} {}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} PrimaryKeys 1033 2 {5 1033 2 {26 {29 256}} {{8 1}} Table} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Import 1033 1 {6 1033 1 24 {{8 1} {8 1}} {Folder File}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Export 1033 1 {7 1033 1 24 {{8 1} {8 1} {8 1}} {Table Folder File}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Merge 1033 1 {8 1033 1 11 {{9 1} {8 {49 {8 0}}}} {Database ErrorTable}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} GenerateTransform 1033 1 {9 1033 1 11 {{9 1} {8 {49 {8 0}}}} {ReferenceDatabase TransformFile}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} ApplyTransform 1033 1 {10 1033 1 24 {{8 1} {3 1}} {TransformFile ErrorConditions}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} EnableUIPreview 1033 1 {11 1033 1 {26 {29 1664}} {} {}} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} TablePersistent 1033 2 {12 1033 2 {29 1792} {{8 1}} Table} -::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} CreateTransformSummaryInfo 1033 1 {13 1033 1 24 {{9 1} {8 1} {3 1} {3 1}} {ReferenceDatabase TransformFile ErrorConditions Validation}} - -# Dispatch Interface SummaryInfo -set windowsinstaller::_dispatch_guids(SummaryInfo) "{000C109B-0000-0000-C000-000000000046}" -# SummaryInfo Methods -::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Property 1033 2 {1 1033 2 12 {{3 1}} Pid} -::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Property 1033 4 {1 1033 4 24 {{3 1} {12 1}} Pid} -::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} PropertyCount 1033 2 {2 1033 2 3 {} {}} -::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Persist 1033 1 {3 1033 1 24 {} {}} - -# Dispatch Interface View -set windowsinstaller::_dispatch_guids(View) "{000C109C-0000-0000-C000-000000000046}" -# View Methods -::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Execute 1033 1 {1 1033 1 24 {{9 {49 {3 0}}}} Params} -::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Fetch 1033 1 {2 1033 1 {26 {29 256}} {} {}} -::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Modify 1033 1 {3 1033 1 24 {{3 1} {9 0}} {Mode Record}} -::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} ColumnInfo 1033 2 {5 1033 2 {26 {29 256}} {{3 1}} Info} -::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Close 1033 1 {4 1033 1 24 {} {}} -::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} GetError 1033 1 {6 1033 1 8 {} {}} - -# Dispatch Interface UIPreview -set windowsinstaller::_dispatch_guids(UIPreview) "{000C109A-0000-0000-C000-000000000046}" -# UIPreview Methods -::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} Property 1033 2 {1 1033 2 8 {{8 1}} Name} -::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} Property 1033 4 {1 1033 4 24 {{8 1} {8 1}} Name} -::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} ViewDialog 1033 1 {2 1033 1 24 {{8 1}} Dialog} -::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} ViewBillboard 1033 1 {3 1033 1 24 {{8 1} {8 1}} {Control Billboard}} - -# Dispatch Interface FeatureInfo -set windowsinstaller::_dispatch_guids(FeatureInfo) "{000C109F-0000-0000-C000-000000000046}" -# FeatureInfo Methods -::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Title 1033 2 {1 1033 2 8 {} {}} -::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Description 1033 2 {2 1033 2 8 {} {}} -# FeatureInfo Properties -::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Attributes 1033 2 {3 1033 2 3 {} {}} -::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Attributes 1033 4 {3 1033 4 24 {{3 1}} {}} - -# Dispatch Interface RecordList -set windowsinstaller::_dispatch_guids(RecordList) "{000C1096-0000-0000-C000-000000000046}" -# RecordList Methods -::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} _NewEnum 1033 1 {-4 1033 1 13 {} {}} -::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} Item 1033 2 {0 1033 2 {26 {29 256}} {{3 0}} Index} -::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} Count 1033 2 {1 1033 2 3 {} {}} - -# Dispatch Interface StringList -set windowsinstaller::_dispatch_guids(StringList) "{000C1095-0000-0000-C000-000000000046}" -# StringList Methods -::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} _NewEnum 1033 1 {-4 1033 1 13 {} {}} -::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} Item 1033 2 {0 1033 2 8 {{3 0}} Index} -::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} Count 1033 2 {1 1033 2 3 {} {}} - -# Dispatch Interface Product -set windowsinstaller::_dispatch_guids(Product) "{000C10A0-0000-0000-C000-000000000046}" -# Product Methods -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} ProductCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} UserSid 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} Context 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} State 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} InstallProperty 1033 2 {5 1033 2 25 {{8 1} {{26 8} 10}} {Name retval}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} ComponentState 1033 2 {6 1033 2 25 {{8 1} {{26 3} 10}} {Component retval}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} FeatureState 1033 2 {7 1033 2 25 {{8 1} {{26 3} 10}} {Feature retval}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} Sources 1033 2 {14 1033 2 25 {{3 1} {{26 9} 10}} {SourceType retval}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} MediaDisks 1033 2 {15 1033 2 25 {{{26 9} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListAddSource 1033 1 {8 1033 1 25 {{3 1} {8 1} {3 1}} {iSourceType Source dwIndex}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListAddMediaDisk 1033 1 {9 1033 1 25 {{3 1} {8 1} {8 1}} {dwDiskId VolumeLabel DiskPrompt}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearSource 1033 1 {10 1033 1 25 {{3 1} {8 1}} {iSourceType Source}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearMediaDisk 1033 1 {11 1033 1 25 {{3 1}} iDiskId} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearAll 1033 1 {12 1033 1 25 {{3 1}} iSourceType} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListForceResolution 1033 1 {13 1033 1 25 {} {}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListInfo 1033 2 {16 1033 2 25 {{8 1} {{26 8} 10}} {Property retval}} -::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListInfo 1033 4 {16 1033 4 25 {{8 1} {8 1}} {Property retval}} - -# Dispatch Interface Patch -set windowsinstaller::_dispatch_guids(Patch) "{000C10A1-0000-0000-C000-000000000046}" -# Patch Methods -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} PatchCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} ProductCode 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} UserSid 1033 2 {3 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} Context 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} State 1033 2 {5 1033 2 25 {{{26 3} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} Sources 1033 2 {12 1033 2 25 {{3 1} {{26 9} 10}} {SourceType retval}} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} MediaDisks 1033 2 {13 1033 2 25 {{{26 9} 10}} retval} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListAddSource 1033 1 {6 1033 1 25 {{3 1} {8 1} {3 1}} {iSourceType Source dwIndex}} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListAddMediaDisk 1033 1 {7 1033 1 25 {{3 1} {8 1} {8 1}} {dwDiskId VolumeLabel DiskPrompt}} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearSource 1033 1 {8 1033 1 25 {{3 1} {8 1}} {iSourceType Source}} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearMediaDisk 1033 1 {9 1033 1 25 {{3 1}} iDiskId} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearAll 1033 1 {10 1033 1 25 {{3 1}} iSourceType} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListForceResolution 1033 1 {11 1033 1 25 {} {}} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListInfo 1033 2 {14 1033 2 25 {{8 1} {{26 8} 10}} {Property retval}} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListInfo 1033 4 {14 1033 4 25 {{8 1} {8 1}} {Property retval}} -::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} PatchProperty 1033 2 {15 1033 2 25 {{8 1} {{26 8} 10}} {Property Value}} - -# Dispatch Interface ComponentPath -set windowsinstaller::_dispatch_guids(ComponentPath) "{000C1099-0000-0000-C000-000000000046}" -# ComponentPath Methods -::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} Path 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} State 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} - -# Dispatch Interface Component -set windowsinstaller::_dispatch_guids(Component) "{000C1097-0000-0000-C000-000000000046}" -# Component Methods -::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} UserSid 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} Context 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} - -# Dispatch Interface ComponentClient -set windowsinstaller::_dispatch_guids(ComponentClient) "{000C1098-0000-0000-C000-000000000046}" -# ComponentClient Methods -::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} ProductCode 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} UserSid 1033 2 {3 1033 2 25 {{{26 8} 10}} retval} -::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} Context 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} - +# +# Copyright (c) 2003-2018, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Hack to work with the various build configuration. +if {[info commands ::twapi::get_version] ne ""} { + package provide twapi_msi [::twapi::get_version -patchlevel] +} + +# Rest of this file auto-generated + + +# Automatically generated type library interface +# File: msi.dll +# Name: WindowsInstaller +# GUID: {000C1092-0000-0000-C000-000000000046} +# Version: 1.0 +# LCID: 1033 +package require twapi_com + +namespace eval windowsinstaller { + + # Array mapping coclass names to their guids + variable _coclass_guids + + # Array mapping dispatch interface names to their guids + variable _dispatch_guids + + # Returns the GUID for a coclass or empty string if not found + proc coclass_guid {coclass_name} { + variable _coclass_guids + if {[info exists _coclass_guids($coclass_name)]} { + return $_coclass_guids($coclass_name) + } + return "" + } + # Returns the GUID for a dispatch name or empty string if not found + proc dispatch_guid {dispatch_name} { + variable _dispatch_guids + if {[info exists _dispatch_guids($dispatch_name)]} { + return $_dispatch_guids($dispatch_name) + } + return "" + } + # Marks the specified object to be of a specific dispatch/coclass type + proc declare {typename comobj} { + # First check if it is the name of a dispatch interface + set guid [dispatch_guid $typename] + if {$guid ne ""} { + $comobj -interfaceguid $guid + return + } + + # If not, check if it is the name of a coclass with a dispatch interface + set guid [coclass_guid $typename] + if {$guid ne ""} { + if {[info exists ::twapi::_coclass_idispatch_guids($guid)]} { + $comobj -interfaceguid $::twapi::_coclass_idispatch_guids($guid) + return + } + } + + error "Could not resolve interface for $coclass_name." + } + + # Enum MsiUILevel + variable MsiUILevel + array set MsiUILevel {msiUILevelNoChange 0 msiUILevelDefault 1 msiUILevelNone 2 msiUILevelBasic 3 msiUILevelReduced 4 msiUILevelFull 5 msiUILevelHideCancel 32 msiUILevelProgressOnly 64 msiUILevelEndDialog 128 msiUILevelSourceResOnly 256} + + # Enum MsiReadStream + variable MsiReadStream + array set MsiReadStream {msiReadStreamInteger 0 msiReadStreamBytes 1 msiReadStreamAnsi 2 msiReadStreamDirect 3} + + # Enum MsiRunMode + variable MsiRunMode + array set MsiRunMode {msiRunModeAdmin 0 msiRunModeAdvertise 1 msiRunModeMaintenance 2 msiRunModeRollbackEnabled 3 msiRunModeLogEnabled 4 msiRunModeOperations 5 msiRunModeRebootAtEnd 6 msiRunModeRebootNow 7 msiRunModeCabinet 8 msiRunModeSourceShortNames 9 msiRunModeTargetShortNames 10 msiRunModeWindows9x 12 msiRunModeZawEnabled 13 msiRunModeScheduled 16 msiRunModeRollback 17 msiRunModeCommit 18} + + # Enum MsiDatabaseState + variable MsiDatabaseState + array set MsiDatabaseState {msiDatabaseStateRead 0 msiDatabaseStateWrite 1} + + # Enum MsiViewModify + variable MsiViewModify + array set MsiViewModify {msiViewModifySeek -1 msiViewModifyRefresh 0 msiViewModifyInsert 1 msiViewModifyUpdate 2 msiViewModifyAssign 3 msiViewModifyReplace 4 msiViewModifyMerge 5 msiViewModifyDelete 6 msiViewModifyInsertTemporary 7 msiViewModifyValidate 8 msiViewModifyValidateNew 9 msiViewModifyValidateField 10 msiViewModifyValidateDelete 11} + + # Enum MsiColumnInfo + variable MsiColumnInfo + array set MsiColumnInfo {msiColumnInfoNames 0 msiColumnInfoTypes 1} + + # Enum MsiTransformError + variable MsiTransformError + array set MsiTransformError {msiTransformErrorNone 0 msiTransformErrorAddExistingRow 1 msiTransformErrorDeleteNonExistingRow 2 msiTransformErrorAddExistingTable 4 msiTransformErrorDeleteNonExistingTable 8 msiTransformErrorUpdateNonExistingRow 16 msiTransformErrorChangeCodePage 32 msiTransformErrorViewTransform 256} + + # Enum MsiEvaluateCondition + variable MsiEvaluateCondition + array set MsiEvaluateCondition {msiEvaluateConditionFalse 0 msiEvaluateConditionTrue 1 msiEvaluateConditionNone 2 msiEvaluateConditionError 3} + + # Enum MsiTransformValidation + variable MsiTransformValidation + array set MsiTransformValidation {msiTransformValidationNone 0 msiTransformValidationLanguage 1 msiTransformValidationProduct 2 msiTransformValidationPlatform 4 msiTransformValidationMajorVer 8 msiTransformValidationMinorVer 16 msiTransformValidationUpdateVer 32 msiTransformValidationLess 64 msiTransformValidationLessOrEqual 128 msiTransformValidationEqual 256 msiTransformValidationGreaterOrEqual 512 msiTransformValidationGreater 1024 msiTransformValidationUpgradeCode 2048} + + # Enum MsiDoActionStatus + variable MsiDoActionStatus + array set MsiDoActionStatus {msiDoActionStatusNoAction 0 msiDoActionStatusSuccess 1 msiDoActionStatusUserExit 2 msiDoActionStatusFailure 3 msiDoActionStatusSuspend 4 msiDoActionStatusFinished 5 msiDoActionStatusWrongState 6 msiDoActionStatusBadActionData 7} + + # Enum MsiMessageStatus + variable MsiMessageStatus + array set MsiMessageStatus {msiMessageStatusError -1 msiMessageStatusNone 0 msiMessageStatusOk 1 msiMessageStatusCancel 2 msiMessageStatusAbort 3 msiMessageStatusRetry 4 msiMessageStatusIgnore 5 msiMessageStatusYes 6 msiMessageStatusNo 7} + + # Enum MsiMessageType + variable MsiMessageType + array set MsiMessageType {msiMessageTypeFatalExit 0 msiMessageTypeError 16777216 msiMessageTypeWarning 33554432 msiMessageTypeUser 50331648 msiMessageTypeInfo 67108864 msiMessageTypeFilesInUse 83886080 msiMessageTypeResolveSource 100663296 msiMessageTypeOutOfDiskSpace 117440512 msiMessageTypeActionStart 134217728 msiMessageTypeActionData 150994944 msiMessageTypeProgress 167772160 msiMessageTypeCommonData 184549376 msiMessageTypeOk 0 msiMessageTypeOkCancel 1 msiMessageTypeAbortRetryIgnore 2 msiMessageTypeYesNoCancel 3 msiMessageTypeYesNo 4 msiMessageTypeRetryCancel 5 msiMessageTypeDefault1 0 msiMessageTypeDefault2 256 msiMessageTypeDefault3 512} + + # Enum MsiInstallState + variable MsiInstallState + array set MsiInstallState {msiInstallStateNotUsed -7 msiInstallStateBadConfig -6 msiInstallStateIncomplete -5 msiInstallStateSourceAbsent -4 msiInstallStateInvalidArg -2 msiInstallStateUnknown -1 msiInstallStateBroken 0 msiInstallStateAdvertised 1 msiInstallStateRemoved 1 msiInstallStateAbsent 2 msiInstallStateLocal 3 msiInstallStateSource 4 msiInstallStateDefault 5} + + # Enum MsiCostTree + variable MsiCostTree + array set MsiCostTree {msiCostTreeSelfOnly 0 msiCostTreeChildren 1 msiCostTreeParents 2} + + # Enum MsiReinstallMode + variable MsiReinstallMode + array set MsiReinstallMode {msiReinstallModeFileMissing 2 msiReinstallModeFileOlderVersion 4 msiReinstallModeFileEqualVersion 8 msiReinstallModeFileExact 16 msiReinstallModeFileVerify 32 msiReinstallModeFileReplace 64 msiReinstallModeMachineData 128 msiReinstallModeUserData 256 msiReinstallModeShortcut 512 msiReinstallModePackage 1024} + + # Enum MsiInstallType + variable MsiInstallType + array set MsiInstallType {msiInstallTypeDefault 0 msiInstallTypeNetworkImage 1 msiInstallTypeSingleInstance 2} + + # Enum MsiInstallMode + variable MsiInstallMode + array set MsiInstallMode {msiInstallModeNoSourceResolution -3 msiInstallModeNoDetection -2 msiInstallModeExisting -1 msiInstallModeDefault 0} + + # Enum MsiSignatureInfo + variable MsiSignatureInfo + array set MsiSignatureInfo {msiSignatureInfoCertificate 0 msiSignatureInfoHash 1} + + # Enum MsiInstallContext + variable MsiInstallContext + array set MsiInstallContext {msiInstallContextFirstVisible 0 msiInstallContextUserManaged 1 msiInstallContextUser 2 msiInstallContextMachine 4 msiInstallContextAllUserManaged 8} + + # Enum MsiInstallSourceType + variable MsiInstallSourceType + array set MsiInstallSourceType {msiInstallSourceTypeUnknown 0 msiInstallSourceTypeNetwork 1 msiInstallSourceTypeURL 2 msiInstallSourceTypeMedia 4} + + # Enum MsiAssemblyType + variable MsiAssemblyType + array set MsiAssemblyType {msiProvideAssemblyNet 0 msiProvideAssemblyWin32 1} + + # Enum MsiProductScriptInfo + variable MsiProductScriptInfo + array set MsiProductScriptInfo {msiProductScriptInfoProductCode 0 msiProductScriptInfoProductLanguage 1 msiProductScriptInfoProductVersion 2 msiProductScriptInfoProductName 3 msiProductScriptInfoPackageName 4} + + # Enum MsiAdvertiseProductContext + variable MsiAdvertiseProductContext + array set MsiAdvertiseProductContext {msiAdvertiseProductMachine 0 msiAdvertiseProductUser 1} + + # Enum Constants + variable Constants + array set Constants {msiDatabaseNullInteger -2147483648} + + # Enum MsiOpenDatabaseMode + variable MsiOpenDatabaseMode + array set MsiOpenDatabaseMode {msiOpenDatabaseModeReadOnly 0 msiOpenDatabaseModeTransact 1 msiOpenDatabaseModeDirect 2 msiOpenDatabaseModeCreate 3 msiOpenDatabaseModeCreateDirect 4 msiOpenDatabaseModePatchFile 32} + + # Enum MsiSignatureOption + variable MsiSignatureOption + array set MsiSignatureOption {msiSignatureOptionInvalidHashFatal 1} + + # Enum MsiAdvertiseProductPlatform + variable MsiAdvertiseProductPlatform + array set MsiAdvertiseProductPlatform {msiAdvertiseCurrentPlatform 0 msiAdvertiseX86Platform 1 msiAdvertiseIA64Platform 2 msiAdvertiseX64Platform 4} + + # Enum MsiAdvertiseProductOptions + variable MsiAdvertiseProductOptions + array set MsiAdvertiseProductOptions {msiAdvertiseDefault 0 msiAdvertiseSingleInstance 1} + + # Enum MsiAdvertiseScriptFlags + variable MsiAdvertiseScriptFlags + array set MsiAdvertiseScriptFlags {msiAdvertiseScriptCacheInfo 1 msiAdvertiseScriptShortcuts 4 msiAdvertiseScriptMachineAssign 8 msiAdvertiseScriptConfigurationRegistration 32 msiAdvertiseScriptValidateTransformsList 64 msiAdvertiseScriptClassInfoRegistration 128 msiAdvertiseScriptExtensionInfoRegistration 256 msiAdvertiseScriptAppInfo 384 msiAdvertiseScriptRegData 416} +} + +# Dispatch Interface Installer +set windowsinstaller::_dispatch_guids(Installer) "{000C1090-0000-0000-C000-000000000046}" +# Installer Methods +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CreateRecord 1033 1 {1 1033 1 {26 {29 256}} {{3 1}} Count} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenPackage 1033 1 {2 1033 1 {26 {29 512}} {{12 1} {3 {49 {3 0}}}} {PackagePath Options}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenProduct 1033 1 {3 1033 1 {26 {29 512}} {{8 1}} ProductCode} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} OpenDatabase 1033 1 {4 1033 1 {26 {29 768}} {{8 1} {12 1}} {DatabasePath OpenMode}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} SummaryInformation 1033 2 {5 1033 2 {26 {29 1024}} {{8 1} {3 {49 {3 0}}}} {PackagePath UpdateCount}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} EnableLog 1033 1 {7 1033 1 24 {{8 1} {8 1}} {LogMode LogFile}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} InstallProduct 1033 1 {8 1033 1 24 {{8 1} {8 {49 {8 0}}}} {PackagePath PropertyValues}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Version 1033 2 {9 1033 2 8 {} {}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} LastErrorRecord 1033 1 {10 1033 1 {26 {29 256}} {} {}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RegistryValue 1033 1 {11 1033 1 8 {{12 1} {8 1} {12 17}} {Root Key Value}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileAttributes 1033 1 {13 1033 1 3 {{8 1}} FilePath} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileSize 1033 1 {15 1033 1 3 {{8 1}} FilePath} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileVersion 1033 1 {16 1033 1 8 {{8 1} {12 17}} {FilePath Language}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Environment 1033 2 {12 1033 2 8 {{8 1}} Variable} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Environment 1033 4 {12 1033 4 24 {{8 1} {8 1}} Variable} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductState 1033 2 {17 1033 2 {29 2432} {{8 1}} Product} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductInfo 1033 2 {18 1033 2 8 {{8 1} {8 1}} {Product Attribute}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ConfigureProduct 1033 1 {19 1033 1 24 {{8 1} {3 1} {3 1}} {Product InstallLevel InstallState}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ReinstallProduct 1033 1 {20 1033 1 24 {{8 1} {3 1}} {Product ReinstallMode}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CollectUserInfo 1033 1 {21 1033 1 24 {{8 1}} Product} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ApplyPatch 1033 1 {22 1033 1 24 {{8 1} {8 1} {3 1} {8 1}} {PatchPackage InstallPackage InstallType CommandLine}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureParent 1033 2 {23 1033 2 8 {{8 1} {8 1}} {Product Feature}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureState 1033 2 {24 1033 2 {29 2432} {{8 1} {8 1}} {Product Feature}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UseFeature 1033 1 {25 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature InstallMode}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureUsageCount 1033 2 {26 1033 2 3 {{8 1} {8 1}} {Product Feature}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FeatureUsageDate 1033 2 {27 1033 2 7 {{8 1} {8 1}} {Product Feature}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ConfigureFeature 1033 1 {28 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature InstallState}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ReinstallFeature 1033 1 {29 1033 1 24 {{8 1} {8 1} {3 1}} {Product Feature ReinstallMode}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideComponent 1033 1 {30 1033 1 8 {{8 1} {8 1} {8 1} {3 1}} {Product Feature Component InstallMode}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentPath 1033 2 {31 1033 2 8 {{8 1} {8 1}} {Product Component}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideQualifiedComponent 1033 1 {32 1033 1 8 {{8 1} {8 1} {3 1}} {Category Qualifier InstallMode}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} QualifierDescription 1033 2 {33 1033 2 8 {{8 1} {8 1}} {Category Qualifier}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentQualifiers 1033 2 {34 1033 2 {26 {29 3328}} {{8 1}} Category} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Products 1033 2 {35 1033 2 {26 {29 3328}} {} {}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Features 1033 2 {36 1033 2 {26 {29 3328}} {{8 1}} Product} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Components 1033 2 {37 1033 2 {26 {29 3328}} {} {}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentClients 1033 2 {38 1033 2 {26 {29 3328}} {{8 1}} Component} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Patches 1033 2 {39 1033 2 {26 {29 3328}} {{8 1}} Product} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RelatedProducts 1033 2 {40 1033 2 {26 {29 3328}} {{8 1}} UpgradeCode} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchInfo 1033 2 {41 1033 2 8 {{8 1} {8 1}} {Patch Attribute}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchTransforms 1033 2 {42 1033 2 8 {{8 1} {8 1}} {Product Patch}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AddSource 1033 1 {43 1033 1 24 {{8 1} {8 1} {8 1}} {Product User Source}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ClearSourceList 1033 1 {44 1033 1 24 {{8 1} {8 1}} {Product User}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ForceSourceListResolution 1033 1 {45 1033 1 24 {{8 1} {8 1}} {Product User}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} GetShortcutTarget 1033 2 {46 1033 2 {26 {29 256}} {{8 1}} ShortcutPath} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileHash 1033 1 {47 1033 1 {26 {29 256}} {{8 1} {3 1}} {FilePath Options}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} FileSignatureInfo 1033 1 {48 1033 1 {27 17} {{8 1} {3 1} {3 1}} {FilePath Options Format}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} RemovePatches 1033 1 {49 1033 1 24 {{8 1} {8 1} {3 1} {8 {49 {8 0}}}} {PatchList Product UninstallType PropertyList}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ApplyMultiplePatches 1033 1 {51 1033 1 24 {{8 1} {8 1} {8 1}} {PatchPackage Product PropertiesList}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Product 1033 2 {53 1033 2 25 {{8 1} {8 1} {3 1} {{26 9} 10}} {Product UserSid iContext retval}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} Patch 1033 2 {56 1033 2 25 {{8 1} {8 1} {8 1} {3 1} {{26 9} 10}} {PatchCode ProductCode UserSid iContext retval}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductsEx 1033 2 {52 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1}} {Product UserSid Contexts}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchesEx 1033 2 {55 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1} {3 1}} {Product UserSid Contexts filter}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ExtractPatchXMLData 1033 1 {57 1033 1 8 {{8 1}} PatchPath} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductCode 1033 2 {58 1033 2 8 {{8 1}} Component} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductElevated 1033 2 {59 1033 2 11 {{8 1}} Product} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProvideAssembly 1033 1 {60 1033 1 8 {{8 1} {8 1} {3 1} {3 1}} {Assembly Context InstallMode AssemblyInfo}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ProductInfoFromScript 1033 2 {61 1033 2 12 {{8 1} {3 1}} {ScriptFile ProductInfo}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AdvertiseProduct 1033 1 {62 1033 1 24 {{8 1} {3 1} {8 {49 {8 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}}} {PackagePath iContext Transforms Language Options}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} CreateAdvertiseScript 1033 1 {63 1033 1 24 {{8 1} {8 1} {8 {49 {8 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}} {3 {49 {3 0}}}} {PackagePath ScriptFilePath Transforms Language Platform Options}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} AdvertiseScript 1033 1 {64 1033 1 24 {{8 1} {3 1} {11 1}} {ScriptPath ScriptFlags RemoveItems}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} PatchFiles 1033 2 {65 1033 2 {26 {29 3328}} {{8 1} {8 1}} {Product PatchPackages}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentsEx 1033 2 {66 1033 2 {26 {29 2816}} {{8 1} {3 1}} {UserSid Context}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentClientsEx 1033 2 {67 1033 2 {26 {29 2816}} {{8 1} {8 1} {3 1}} {ComponentCode UserSid Context}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} ComponentPathEx 1033 2 {9068 1033 2 {26 {29 4480}} {{8 1} {8 1} {8 1} {3 1}} {ProductCode ComponentCode UserSid Context}} +# Installer Properties +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UILevel 1033 2 {6 1033 2 {29 128} {} {}} +::twapi::dispatch_prototype_set {{000C1090-0000-0000-C000-000000000046}} UILevel 1033 4 {6 1033 4 24 {{{29 128} 1}} {}} + +# Dispatch Interface Record +set windowsinstaller::_dispatch_guids(Record) "{000C1093-0000-0000-C000-000000000046}" +# Record Methods +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} StringData 1033 2 {1 1033 2 8 {{3 1}} Field} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} StringData 1033 4 {1 1033 4 24 {{3 1} {8 1}} Field} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IntegerData 1033 2 {2 1033 2 3 {{3 1}} Field} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IntegerData 1033 4 {2 1033 4 24 {{3 1} {3 1}} Field} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} SetStream 1033 1 {3 1033 1 24 {{3 1} {8 1}} {Field FilePath}} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} ReadStream 1033 1 {4 1033 1 8 {{3 1} {3 1} {3 1}} {Field Length Format}} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} FieldCount 1033 2 {0 1033 2 3 {} {}} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} IsNull 1033 2 {6 1033 2 11 {{3 1}} Field} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} DataSize 1033 2 {5 1033 2 3 {{3 1}} Field} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} ClearData 1033 1 {7 1033 1 24 {} {}} +::twapi::dispatch_prototype_set {{000C1093-0000-0000-C000-000000000046}} FormatText 1033 1 {8 1033 1 8 {} {}} + +# Dispatch Interface Session +set windowsinstaller::_dispatch_guids(Session) "{000C109E-0000-0000-C000-000000000046}" +# Session Methods +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Installer 1033 2 {1 1033 2 {26 {29 0}} {} {}} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Property 1033 2 {2 1033 2 8 {{8 1}} Name} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Property 1033 4 {2 1033 4 24 {{8 1} {8 1}} Name} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Language 1033 2 {3 1033 2 3 {} {}} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Mode 1033 2 {4 1033 2 11 {{3 1}} Flag} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Mode 1033 4 {4 1033 4 24 {{3 1} {11 1}} Flag} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Database 1033 2 {5 1033 2 {26 {29 768}} {} {}} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} SourcePath 1033 2 {6 1033 2 8 {{8 1}} Folder} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} TargetPath 1033 2 {7 1033 2 8 {{8 1}} Folder} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} TargetPath 1033 4 {7 1033 4 24 {{8 1} {8 1}} Folder} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} DoAction 1033 1 {8 1033 1 {29 2048} {{8 1}} Action} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Sequence 1033 1 {9 1033 1 {29 2048} {{8 1} {12 17}} {Table Mode}} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} EvaluateCondition 1033 1 {10 1033 1 {29 1792} {{8 1}} Expression} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FormatRecord 1033 1 {11 1033 1 8 {{9 1}} Record} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} Message 1033 1 {12 1033 1 {29 2176} {{3 1} {9 1}} {Kind Record}} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureCurrentState 1033 2 {13 1033 2 {29 2432} {{8 1}} Feature} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureRequestState 1033 2 {14 1033 2 {29 2432} {{8 1}} Feature} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureRequestState 1033 4 {14 1033 4 24 {{8 1} {3 1}} Feature} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureValidStates 1033 2 {15 1033 2 3 {{8 1}} Feature} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureCost 1033 2 {16 1033 2 3 {{8 1} {3 1} {3 1}} {Feature CostTree State}} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentCurrentState 1033 2 {17 1033 2 {29 2432} {{8 1}} Component} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentRequestState 1033 2 {18 1033 2 {29 2432} {{8 1}} Component} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentRequestState 1033 4 {18 1033 4 24 {{8 1} {3 1}} Component} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} SetInstallLevel 1033 1 {19 1033 1 24 {{3 1}} Level} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} VerifyDiskSpace 1033 2 {20 1033 2 11 {} {}} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ProductProperty 1033 2 {21 1033 2 8 {{8 1}} Property} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} FeatureInfo 1033 2 {22 1033 2 {26 {29 2688}} {{8 1}} Feature} +::twapi::dispatch_prototype_set {{000C109E-0000-0000-C000-000000000046}} ComponentCosts 1033 2 {23 1033 2 {26 {29 2816}} {{8 1} {3 1}} {Component State}} + +# Dispatch Interface Database +set windowsinstaller::_dispatch_guids(Database) "{000C109D-0000-0000-C000-000000000046}" +# Database Methods +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} DatabaseState 1033 2 {1 1033 2 {29 896} {} {}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} SummaryInformation 1033 2 {2 1033 2 {26 {29 1024}} {{3 {49 {3 0}}}} UpdateCount} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} OpenView 1033 1 {3 1033 1 {26 {29 1152}} {{8 1}} Sql} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Commit 1033 1 {4 1033 1 24 {} {}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} PrimaryKeys 1033 2 {5 1033 2 {26 {29 256}} {{8 1}} Table} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Import 1033 1 {6 1033 1 24 {{8 1} {8 1}} {Folder File}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Export 1033 1 {7 1033 1 24 {{8 1} {8 1} {8 1}} {Table Folder File}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} Merge 1033 1 {8 1033 1 11 {{9 1} {8 {49 {8 0}}}} {Database ErrorTable}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} GenerateTransform 1033 1 {9 1033 1 11 {{9 1} {8 {49 {8 0}}}} {ReferenceDatabase TransformFile}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} ApplyTransform 1033 1 {10 1033 1 24 {{8 1} {3 1}} {TransformFile ErrorConditions}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} EnableUIPreview 1033 1 {11 1033 1 {26 {29 1664}} {} {}} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} TablePersistent 1033 2 {12 1033 2 {29 1792} {{8 1}} Table} +::twapi::dispatch_prototype_set {{000C109D-0000-0000-C000-000000000046}} CreateTransformSummaryInfo 1033 1 {13 1033 1 24 {{9 1} {8 1} {3 1} {3 1}} {ReferenceDatabase TransformFile ErrorConditions Validation}} + +# Dispatch Interface SummaryInfo +set windowsinstaller::_dispatch_guids(SummaryInfo) "{000C109B-0000-0000-C000-000000000046}" +# SummaryInfo Methods +::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Property 1033 2 {1 1033 2 12 {{3 1}} Pid} +::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Property 1033 4 {1 1033 4 24 {{3 1} {12 1}} Pid} +::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} PropertyCount 1033 2 {2 1033 2 3 {} {}} +::twapi::dispatch_prototype_set {{000C109B-0000-0000-C000-000000000046}} Persist 1033 1 {3 1033 1 24 {} {}} + +# Dispatch Interface View +set windowsinstaller::_dispatch_guids(View) "{000C109C-0000-0000-C000-000000000046}" +# View Methods +::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Execute 1033 1 {1 1033 1 24 {{9 {49 {3 0}}}} Params} +::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Fetch 1033 1 {2 1033 1 {26 {29 256}} {} {}} +::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Modify 1033 1 {3 1033 1 24 {{3 1} {9 0}} {Mode Record}} +::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} ColumnInfo 1033 2 {5 1033 2 {26 {29 256}} {{3 1}} Info} +::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} Close 1033 1 {4 1033 1 24 {} {}} +::twapi::dispatch_prototype_set {{000C109C-0000-0000-C000-000000000046}} GetError 1033 1 {6 1033 1 8 {} {}} + +# Dispatch Interface UIPreview +set windowsinstaller::_dispatch_guids(UIPreview) "{000C109A-0000-0000-C000-000000000046}" +# UIPreview Methods +::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} Property 1033 2 {1 1033 2 8 {{8 1}} Name} +::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} Property 1033 4 {1 1033 4 24 {{8 1} {8 1}} Name} +::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} ViewDialog 1033 1 {2 1033 1 24 {{8 1}} Dialog} +::twapi::dispatch_prototype_set {{000C109A-0000-0000-C000-000000000046}} ViewBillboard 1033 1 {3 1033 1 24 {{8 1} {8 1}} {Control Billboard}} + +# Dispatch Interface FeatureInfo +set windowsinstaller::_dispatch_guids(FeatureInfo) "{000C109F-0000-0000-C000-000000000046}" +# FeatureInfo Methods +::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Title 1033 2 {1 1033 2 8 {} {}} +::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Description 1033 2 {2 1033 2 8 {} {}} +# FeatureInfo Properties +::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Attributes 1033 2 {3 1033 2 3 {} {}} +::twapi::dispatch_prototype_set {{000C109F-0000-0000-C000-000000000046}} Attributes 1033 4 {3 1033 4 24 {{3 1}} {}} + +# Dispatch Interface RecordList +set windowsinstaller::_dispatch_guids(RecordList) "{000C1096-0000-0000-C000-000000000046}" +# RecordList Methods +::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} _NewEnum 1033 1 {-4 1033 1 13 {} {}} +::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} Item 1033 2 {0 1033 2 {26 {29 256}} {{3 0}} Index} +::twapi::dispatch_prototype_set {{000C1096-0000-0000-C000-000000000046}} Count 1033 2 {1 1033 2 3 {} {}} + +# Dispatch Interface StringList +set windowsinstaller::_dispatch_guids(StringList) "{000C1095-0000-0000-C000-000000000046}" +# StringList Methods +::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} _NewEnum 1033 1 {-4 1033 1 13 {} {}} +::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} Item 1033 2 {0 1033 2 8 {{3 0}} Index} +::twapi::dispatch_prototype_set {{000C1095-0000-0000-C000-000000000046}} Count 1033 2 {1 1033 2 3 {} {}} + +# Dispatch Interface Product +set windowsinstaller::_dispatch_guids(Product) "{000C10A0-0000-0000-C000-000000000046}" +# Product Methods +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} ProductCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} UserSid 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} Context 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} State 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} InstallProperty 1033 2 {5 1033 2 25 {{8 1} {{26 8} 10}} {Name retval}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} ComponentState 1033 2 {6 1033 2 25 {{8 1} {{26 3} 10}} {Component retval}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} FeatureState 1033 2 {7 1033 2 25 {{8 1} {{26 3} 10}} {Feature retval}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} Sources 1033 2 {14 1033 2 25 {{3 1} {{26 9} 10}} {SourceType retval}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} MediaDisks 1033 2 {15 1033 2 25 {{{26 9} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListAddSource 1033 1 {8 1033 1 25 {{3 1} {8 1} {3 1}} {iSourceType Source dwIndex}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListAddMediaDisk 1033 1 {9 1033 1 25 {{3 1} {8 1} {8 1}} {dwDiskId VolumeLabel DiskPrompt}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearSource 1033 1 {10 1033 1 25 {{3 1} {8 1}} {iSourceType Source}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearMediaDisk 1033 1 {11 1033 1 25 {{3 1}} iDiskId} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListClearAll 1033 1 {12 1033 1 25 {{3 1}} iSourceType} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListForceResolution 1033 1 {13 1033 1 25 {} {}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListInfo 1033 2 {16 1033 2 25 {{8 1} {{26 8} 10}} {Property retval}} +::twapi::dispatch_prototype_set {{000C10A0-0000-0000-C000-000000000046}} SourceListInfo 1033 4 {16 1033 4 25 {{8 1} {8 1}} {Property retval}} + +# Dispatch Interface Patch +set windowsinstaller::_dispatch_guids(Patch) "{000C10A1-0000-0000-C000-000000000046}" +# Patch Methods +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} PatchCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} ProductCode 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} UserSid 1033 2 {3 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} Context 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} State 1033 2 {5 1033 2 25 {{{26 3} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} Sources 1033 2 {12 1033 2 25 {{3 1} {{26 9} 10}} {SourceType retval}} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} MediaDisks 1033 2 {13 1033 2 25 {{{26 9} 10}} retval} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListAddSource 1033 1 {6 1033 1 25 {{3 1} {8 1} {3 1}} {iSourceType Source dwIndex}} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListAddMediaDisk 1033 1 {7 1033 1 25 {{3 1} {8 1} {8 1}} {dwDiskId VolumeLabel DiskPrompt}} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearSource 1033 1 {8 1033 1 25 {{3 1} {8 1}} {iSourceType Source}} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearMediaDisk 1033 1 {9 1033 1 25 {{3 1}} iDiskId} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListClearAll 1033 1 {10 1033 1 25 {{3 1}} iSourceType} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListForceResolution 1033 1 {11 1033 1 25 {} {}} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListInfo 1033 2 {14 1033 2 25 {{8 1} {{26 8} 10}} {Property retval}} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} SourceListInfo 1033 4 {14 1033 4 25 {{8 1} {8 1}} {Property retval}} +::twapi::dispatch_prototype_set {{000C10A1-0000-0000-C000-000000000046}} PatchProperty 1033 2 {15 1033 2 25 {{8 1} {{26 8} 10}} {Property Value}} + +# Dispatch Interface ComponentPath +set windowsinstaller::_dispatch_guids(ComponentPath) "{000C1099-0000-0000-C000-000000000046}" +# ComponentPath Methods +::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} Path 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C1099-0000-0000-C000-000000000046}} State 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} + +# Dispatch Interface Component +set windowsinstaller::_dispatch_guids(Component) "{000C1097-0000-0000-C000-000000000046}" +# Component Methods +::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} UserSid 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C1097-0000-0000-C000-000000000046}} Context 1033 2 {3 1033 2 25 {{{26 3} 10}} retval} + +# Dispatch Interface ComponentClient +set windowsinstaller::_dispatch_guids(ComponentClient) "{000C1098-0000-0000-C000-000000000046}" +# ComponentClient Methods +::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} ProductCode 1033 2 {2 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} ComponentCode 1033 2 {1 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} UserSid 1033 2 {3 1033 2 25 {{{26 8} 10}} retval} +::twapi::dispatch_prototype_set {{000C1098-0000-0000-C000-000000000046}} Context 1033 2 {4 1033 2 25 {{{26 3} 10}} retval} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/mstask.tcl b/src/vendorlib_tcl8/twapi-5.0b1/mstask.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/mstask.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/mstask.tcl index d1e37686..d5e3e6b6 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/mstask.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/mstask.tcl @@ -1,745 +1,745 @@ -# -# Copyright (c) 2006-2013 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Task scheduler API - -package require twapi_com - -namespace eval twapi { - variable CLSID_ITaskScheduler {{148BD52A-A2AB-11CE-B11F-00AA00530503}} - variable CLSID_ITask {{148BD520-A2AB-11CE-B11F-00AA00530503}} -} - -# Return an instance of the task scheduler -proc twapi::itaskscheduler_new {args} { - array set opts [parseargs args { - system.arg - } -maxleftover 0] - - # Get ITaskScheduler interface - set its [com_create_instance $::twapi::CLSID_ITaskScheduler -model inprocserver -interface ITaskScheduler -raw] - if {![info exists opts(system)]} { - return $its - } - trap { - itaskscheduler_set_target_system $its $opts(system) - } onerror {} { - IUnknown_Release $its - rethrow - } - return $its -} - -interp alias {} ::twapi::itaskscheduler_release {} ::twapi::IUnknown_Release - -# Return a new task interface -proc twapi::itaskscheduler_new_itask {its taskname} { - set iid_itask [name_to_iid ITask] - set iunk [ITaskScheduler_NewWorkItem $its $taskname $::twapi::CLSID_ITask $iid_itask] - trap { - set itask [Twapi_IUnknown_QueryInterface $iunk $iid_itask ITask] - } finally { - IUnknown_Release $iunk - } - return $itask -} - -# Get an existing task -proc twapi::itaskscheduler_get_itask {its taskname} { - set iid_itask [name_to_iid ITask] - set iunk [ITaskScheduler_Activate $its $taskname $iid_itask] - trap { - set itask [Twapi_IUnknown_QueryInterface $iunk $iid_itask ITask] - } finally { - IUnknown_Release $iunk - } - return $itask -} - -# Check if an itask exists -proc twapi::itaskscheduler_task_exists {its taskname} { - return [expr {[ITaskScheduler_IsOfType $its $taskname [name_to_iid ITask]] == 0 ? true : false}] -} - -# Return list of tasks -proc twapi::itaskscheduler_get_tasks {its} { - set ienum [ITaskScheduler_Enum $its] - trap { - set result [list ] - set more 1 - while {$more} { - lassign [IEnumWorkItems_Next $ienum 20] more items - set result [concat $result $items] - } - } finally { - IUnknown_Release $ienum - } - return $result -} - -# Sets the specified properties of the ITask -proc twapi::itask_configure {itask args} { - - array set opts [parseargs args { - application.arg - maxruntime.int - params.arg - priority.arg - workingdir.arg - account.arg - password.arg - comment.arg - creator.arg - data.arg - idlewait.int - idlewaitdeadline.int - interactive.bool - deletewhendone.bool - disabled.bool - hidden.bool - runonlyifloggedon.bool - startonlyifidle.bool - resumesystem.bool - killonidleend.bool - restartonidleresume.bool - dontstartonbatteries.bool - killifonbatteries.bool - } -maxleftover 0] - - if {[info exists opts(priority)]} { - switch -exact -- $opts(priority) { - normal {set opts(priority) 0x00000020} - abovenormal {set opts(priority) 0x00008000} - belownormal {set opts(priority) 0x00004000} - high {set opts(priority) 0x00000080} - realtime {set opts(priority) 0x00000100} - idle {set opts(priority) 0x00000040} - default {error "Unknown priority '$opts(priority)'. Must be one of 'normal', 'high', 'idle' or 'realtime'"} - } - } - - foreach {opt fn} { - application ITask_SetApplicationName - maxruntime ITask_SetMaxRunTime - params ITask_SetParameters - workingdir ITask_SetWorkingDirectory - priority ITask_SetPriority - comment IScheduledWorkItem_SetComment - creator IScheduledWorkItem_SetCreator - data IScheduledWorkItem_SetWorkItemData - errorretrycount IScheduledWorkItem_SetErrorRetryCount - errorretryinterval IScheduledWorkItem_SetErrorRetryInterval - } { - if {[info exists opts($opt)]} { - $fn $itask $opts($opt) - } - } - - if {[info exists opts(account)]} { - if {$opts(account) ne ""} { - if {![info exists opts(password)]} { - error "Option -password must be specified if -account is specified" - } - } else { - # System account. Set password to NULL pointer indicated - # by magic null pointer - set opts(password) $::twapi::nullptr - } - IScheduledWorkItem_SetAccountInformation $itask $opts(account) $opts(password) - } - - if {[info exists opts(idlewait)] || [info exists opts(idlewaitdeadline)]} { - # If either one is not specified, get the current settings - if {! ([info exists opts(idlewait)] && - [info exists opts(idlewaitdeadline)]) } { - lassign [IScheduledWorkItem_GetIdleWait $itask] idle dead - if {![info exists opts(idlewait)]} { - set opts(idlewait) $idle - } - if {![info exists opts(idlewaitdeadline)]} { - set opts(idlewaitdeadline) $dead - } - } - IScheduledWorkItem_SetIdleWait $itask $opts(idlewait) $opts(idlewaitdeadline) - } - - # Finally figure out and set the flags if needed - if {[info exists opts(interactive)] || - [info exists opts(deletewhendone)] || - [info exists opts(disabled)] || - [info exists opts(hidden)] || - [info exists opts(runonlyifloggedon)] || - [info exists opts(startonlyifidle)] || - [info exists opts(resumesystem)] || - [info exists opts(killonidleend)] || - [info exists opts(restartonidleresume)] || - [info exists opts(dontstartonbatteries)] || - [info exists opts(killifonbatteries)]} { - - # First, get the current flags - set flags [IScheduledWorkItem_GetFlags $itask] - foreach {opt val} { - interactive 0x1 - deletewhendone 0x2 - disabled 0x4 - startonlyifidle 0x10 - hidden 0x200 - runonlyifloggedon 0x2000 - resumesystem 0x1000 - killonidleend 0x20 - restartonidleresume 0x800 - dontstartonbatteries 0x40 - killifonbatteries 0x80 - } { - # Set / reset the bit if specified - if {[info exists opts($opt)]} { - if {$opts($opt)} { - setbits flags $val - } else { - resetbits flags $val - } - } - } - - # Now set the new value of flags - IScheduledWorkItem_SetFlags $itask $flags - } - - - return -} - -proc twapi::itask_get_info {itask args} { - # Note options errorretrycount and errorretryinterval are not implemented - # by the OS so left out - array set opts [parseargs args { - all - application - maxruntime - params - priority - workingdir - account - comment - creator - data - idlewait - idlewaitdeadline - interactive - deletewhendone - disabled - hidden - runonlyifloggedon - startonlyifidle - resumesystem - killonidleend - restartonidleresume - dontstartonbatteries - killifonbatteries - lastruntime - nextruntime - status - } -maxleftover 0] - - set result [list ] - if {$opts(all) || $opts(priority)} { - switch -exact -- [twapi::ITask_GetPriority $itask] { - 32 { set priority normal } - 64 { set priority idle } - 128 { set priority high } - 256 { set priority realtime } - 16384 { set priority belownormal } - 32768 { set priority abovenormal } - default { set priority unknown } - } - lappend result -priority $priority - } - - foreach {opt fn} { - application ITask_GetApplicationName - maxruntime ITask_GetMaxRunTime - params ITask_GetParameters - workingdir ITask_GetWorkingDirectory - account IScheduledWorkItem_GetAccountInformation - comment IScheduledWorkItem_GetComment - creator IScheduledWorkItem_GetCreator - data IScheduledWorkItem_GetWorkItemData - } { - if {$opts(all) || $opts($opt)} { - trap { - lappend result -$opt [$fn $itask] - } onerror {TWAPI_WIN32 -2147216625} { - # THe information is empty in the scheduler database - lappend result -$opt {} - } - } - } - - if {$opts(all) || $opts(lastruntime)} { - trap { - lappend result -lastruntime [_timelist_to_timestring [IScheduledWorkItem_GetMostRecentRunTime $itask]] - } onerror {TWAPI_WIN32 267011} { - # Not run yet at all - lappend result -lastruntime {} - } - } - - if {$opts(all) || $opts(nextruntime)} { - trap { - lappend result -nextruntime [_timelist_to_timestring [IScheduledWorkItem_GetNextRunTime $itask]] - } onerror {TWAPI_WIN32 267010} { - # Task is disabled - lappend result -nextruntime disabled - } onerror {TWAPI_WIN32 267015} { - # No triggers set - lappend result -nextruntime notriggers - } onerror {TWAPI_WIN32 267016} { - # No triggers set - lappend result -nextruntime oneventonly - } - } - - if {$opts(all) || $opts(status)} { - set status [IScheduledWorkItem_GetStatus $itask] - if {$status == 0x41300} { - set status ready - } elseif {$status == 0x41301} { - set status running - } elseif {$status == 0x41302} { - set status disabled - } elseif {$status == 0x41305} { - set status partiallydefined - } else { - set status unknown - } - lappend result -status $status - } - - - if {$opts(all) || $opts(idlewait) || $opts(idlewaitdeadline)} { - lassign [IScheduledWorkItem_GetIdleWait $itask] idle dead - if {$opts(all) || $opts(idlewait)} { - lappend result -idlewait $idle - } - if {$opts(all) || $opts(idlewaitdeadline)} { - lappend result -idlewaitdeadline $dead - } - } - - # Finally figure out and set the flags if needed - if {$opts(all) || - $opts(interactive) || - $opts(deletewhendone) || - $opts(disabled) || - $opts(hidden) || - $opts(runonlyifloggedon) || - $opts(startonlyifidle) || - $opts(resumesystem) || - $opts(killonidleend) || - $opts(restartonidleresume) || - $opts(dontstartonbatteries) || - $opts(killifonbatteries)} { - - # First, get the current flags - set flags [IScheduledWorkItem_GetFlags $itask] - foreach {opt val} { - interactive 0x1 - deletewhendone 0x2 - disabled 0x4 - startonlyifidle 0x10 - hidden 0x200 - runonlyifloggedon 0x2000 - resumesystem 0x1000 - killonidleend 0x20 - restartonidleresume 0x800 - dontstartonbatteries 0x40 - killifonbatteries 0x80 - } { - if {$opts(all) || $opts($opt)} { - lappend result -$opt [expr {($flags & $val) ? true : false}] - } - } - } - - - return $result -} - -# Get the runtimes for a task within an interval -proc twapi::itask_get_runtimes_within_interval {itask args} { - array set opts [parseargs args { - start.arg - end.arg - {count.int 1} - statusvar.arg - } -maxleftover 0] - - if {[info exists opts(start)]} { - set start [_timestring_to_timelist $opts(start)] - } else { - set start [_seconds_to_timelist [clock seconds]] - } - if {[info exists opts(end)]} { - set end [_timestring_to_timelist $opts(end)] - } else { - set end {2038 1 1 0 0 0 0} - } - - set result [list ] - if {[info exists opts(statusvar)]} { - upvar $opts(statusvar) status - } - lassign [IScheduledWorkItem_GetRunTimes $itask $start $end $opts(count)] status timelist - - foreach time $timelist { - lappend result [_timelist_to_timestring $time] - } - - - return $result -} - -# Saves the specified ITask -proc twapi::itask_save {itask} { - set ipersist [Twapi_IUnknown_QueryInterface $itask [name_to_iid IPersistFile] IPersistFile] - trap { - IPersistFile_Save $ipersist "" 1 - } finally { - IUnknown_Release $ipersist - } - return -} - -# Show property editor for a task -proc twapi::itask_edit_dialog {itask args} { - array set opts [parseargs args { - {hwin.arg 0} - } -maxleftover 0] - - return [twapi::IScheduledWorkItem_EditWorkItem $itask $opts(hwin) 0] -} - - -interp alias {} ::twapi::itask_release {} ::twapi::IUnknown_Release - -# Get information about a trigger -proc twapi::itasktrigger_get_info {itt} { - array set data [ITaskTrigger_GetTrigger $itt] - - set result(-begindate) [format %04d-%02d-%02d $data(wBeginYear) $data(wBeginMonth) $data(wBeginDay)] - - set result(-starttime) [format %02d:%02d $data(wStartHour) $data(wStartMinute)] - - if {$data(rgFlags) & 1} { - set result(-enddate) [format %04d-%02d-%02d $data(wEndYear) $data(wEndMonth) $data(wEndDay)] - } else { - set result(-enddate) "" - } - - set result(-duration) $data(MinutesDuration) - set result(-interval) $data(MinutesInterval) - if {$data(rgFlags) & 2} { - set result(-killatdurationend) true - } else { - set result(-killatdurationend) false - } - - if {$data(rgFlags) & 4} { - set result(-disabled) true - } else { - set result(-disabled) false - } - - switch -exact -- [lindex $data(type) 0] { - 0 { - set result(-type) once - } - 1 { - set result(-type) daily - set result(-period) [lindex $data(type) 1] - } - 2 { - set result(-type) weekly - set result(-period) [lindex $data(type) 1] - set result(-weekdays) [format 0x%x [lindex $data(type) 2]] - } - 3 { - set result(-type) monthlydate - set result(-daysofmonth) [format 0x%x [lindex $data(type) 1]] - set result(-months) [format 0x%x [lindex $data(type) 2]] - } - 4 { - set result(-type) monthlydow - set result(-weekofmonth) [lindex {first second third fourth last} [lindex $data(type) 2]] - set result(-weekdays) [format 0x%x [lindex $data(type) 2]] - set result(-months) [format 0x%x [lindex $data(type) 3]] - } - 5 { - set result(-type) onidle - } - 6 { - set result(-type) atsystemstart - } - 7 { - set result(-type) atlogon - } - } - return [array get result] -} - - -# Configure a task trigger -proc twapi::itasktrigger_configure {itt args} { - array set opts [parseargs args { - begindate.arg - enddate.arg - starttime.arg - interval.int - duration.int - killatdurationend.bool - disabled.bool - type.arg - weekofmonth.int - {period.int 1} - {weekdays.int 0x7f} - {daysofmonth.int 0x7fffffff} - {months.int 0xfff} - } -maxleftover 0] - - - array set data [ITaskTrigger_GetTrigger $itt] - - if {[info exists opts(begindate)]} { - lassign [split $opts(begindate) -] year month day - # Note we trim leading zeroes else Tcl thinks its octal - set data(wBeginYear) [scan $year %d] - set data(wBeginMonth) [scan $month %d] - set data(wBeginDay) [scan $day %d] - } - - if {[info exists opts(starttime)]} { - lassign [split $opts(starttime) :] hour minute - # Note we trim leading zeroes else Tcl thinks its octal - set data(wStartHour) [scan $hour %d] - set data(wStartMinute) [scan $minute %d] - } - - if {[info exists opts(enddate)]} { - if {$opts(enddate) ne ""} { - setbits data(rgFlags) 1; # Indicate end date is present - lassign [split $opts(enddate) -] year month day - # Note we trim leading zeroes else Tcl thinks its octal - set data(wEndYear) [scan $year %d] - set data(wEndMonth) [scan $month %d] - set data(wEndDay) [scan $day %d] - } else { - resetbits data(rgFlags) 1; # Indicate no end date - } - } - - - if {[info exists opts(duration)]} { - set data(MinutesDuration) $opts(duration) - } - - if {[info exists opts(interval)]} { - set data(MinutesInterval) $opts(interval) - } - - if {[info exists opts(killatdurationend)]} { - if {$opts(killatdurationend)} { - setbits data(rgFlags) 2 - } else { - resetbits data(rgFlags) 2 - } - } - - if {[info exists opts(disabled)]} { - if {$opts(disabled)} { - setbits data(rgFlags) 4 - } else { - resetbits data(rgFlags) 4 - } - } - - # Note the type specific options are only used if -type is specified - if {[info exists opts(type)]} { - switch -exact -- $opts(type) { - once { - set data(type) [list 0] - } - daily { - set data(type) [list 1 $opts(period)] - } - weekly { - set data(type) [list 2 $opts(period) $opts(weekdays)] - } - monthlydate { - set data(type) [list 3 $opts(daysofmonth) $opts(months)] - } - monthlydow { - set data(type) [list 4 $opts(weekofmonth) $opts(weekdays) $opts(months)] - } - onidle { - set data(type) [list 5] - } - atsystemstart { - set data(type) [list 6] - } - atlogon { - set data(type) [list 7] - } - } - } - - ITaskTrigger_SetTrigger $itt [array get data] - return -} - -interp alias {} ::twapi::itasktrigger_release {} ::twapi::IUnknown_Release - -# Create a new task from scratch. Basically a wrapper around the -# corresponding itaskscheduler, itask and itasktrigger calls -proc twapi::mstask_create {taskname args} { - - # The options are a combination of itask_configure and - # itasktrigger_configure. - # Note the disabled option default to false explicitly. This is because - # the task trigger will default to disabled unless specifically set. - array set opts [parseargs args { - system.arg - application.arg - maxruntime.int - params.arg - priority.arg - workingdir.arg - account.arg - password.arg - comment.arg - creator.arg - data.arg - idlewait.int - idlewaitdeadline.int - interactive.bool - deletewhendone.bool - {disabled.bool false} - hidden.bool - runonlyifloggedon.bool - startonlyifidle.bool - resumesystem.bool - killonidleend.bool - restartonidleresume.bool - dontstartonbatteries.bool - killifonbatteries.bool - begindate.arg - enddate.arg - starttime.arg - interval.int - duration.int - killatdurationend.bool - type.arg - period.int - weekdays.int - daysofmonth.int - months.int - } -maxleftover 0] - - set its [itaskscheduler_new] - trap { - if {[info exists opts(system)]} { - itaskscheduler_set_target_system $opts(system) - } - - set itask [itaskscheduler_new_itask $its $taskname] - # Construct the command line for configuring the task - set cmd [list itask_configure $itask] - foreach opt { - application - maxruntime - params - priority - workingdir - account - password - comment - creator - data - idlewait - idlewaitdeadline - interactive - deletewhendone - disabled - hidden - runonlyifloggedon - startonlyifidle - resumesystem - killonidleend - restartonidleresume - dontstartonbatteries - killifonbatteries - } { - if {[info exists opts($opt)]} { - lappend cmd -$opt $opts($opt) - } - } - eval $cmd - - # Now get a trigger and configure it - set itt [lindex [itask_new_itasktrigger $itask] 1] - set cmd [list itasktrigger_configure $itt] - foreach opt { - begindate - enddate - interval - starttime - duration - killatdurationend - type - period - weekdays - daysofmonth - months - disabled - } { - if {[info exists opts($opt)]} { - lappend cmd -$opt $opts($opt) - } - } - eval $cmd - - # Save the task - itask_save $itask - - } finally { - IUnknown_Release $its - if {[info exists itask]} { - IUnknown_Release $itask - } - if {[info exists itt]} { - IUnknown_Release $itt - } - } - return -} - -# Delete a task -proc twapi::mstask_delete {taskname args} { - # The options are a combination of itask_configure and - # itasktrigger_configure - array set opts [parseargs args { - system.arg - } -maxleftover 0] - set its [itaskscheduler_new] - trap { - if {[info exists opts(system)]} { - itaskscheduler_set_target_system $opts(system) - } - itaskscheduler_delete_task $its $taskname - } finally { - IUnknown_Release $its - } - return -} +# +# Copyright (c) 2006-2013 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Task scheduler API + +package require twapi_com + +namespace eval twapi { + variable CLSID_ITaskScheduler {{148BD52A-A2AB-11CE-B11F-00AA00530503}} + variable CLSID_ITask {{148BD520-A2AB-11CE-B11F-00AA00530503}} +} + +# Return an instance of the task scheduler +proc twapi::itaskscheduler_new {args} { + array set opts [parseargs args { + system.arg + } -maxleftover 0] + + # Get ITaskScheduler interface + set its [com_create_instance $::twapi::CLSID_ITaskScheduler -model inprocserver -interface ITaskScheduler -raw] + if {![info exists opts(system)]} { + return $its + } + trap { + itaskscheduler_set_target_system $its $opts(system) + } onerror {} { + IUnknown_Release $its + rethrow + } + return $its +} + +interp alias {} ::twapi::itaskscheduler_release {} ::twapi::IUnknown_Release + +# Return a new task interface +proc twapi::itaskscheduler_new_itask {its taskname} { + set iid_itask [name_to_iid ITask] + set iunk [ITaskScheduler_NewWorkItem $its $taskname $::twapi::CLSID_ITask $iid_itask] + trap { + set itask [Twapi_IUnknown_QueryInterface $iunk $iid_itask ITask] + } finally { + IUnknown_Release $iunk + } + return $itask +} + +# Get an existing task +proc twapi::itaskscheduler_get_itask {its taskname} { + set iid_itask [name_to_iid ITask] + set iunk [ITaskScheduler_Activate $its $taskname $iid_itask] + trap { + set itask [Twapi_IUnknown_QueryInterface $iunk $iid_itask ITask] + } finally { + IUnknown_Release $iunk + } + return $itask +} + +# Check if an itask exists +proc twapi::itaskscheduler_task_exists {its taskname} { + return [expr {[ITaskScheduler_IsOfType $its $taskname [name_to_iid ITask]] == 0 ? true : false}] +} + +# Return list of tasks +proc twapi::itaskscheduler_get_tasks {its} { + set ienum [ITaskScheduler_Enum $its] + trap { + set result [list ] + set more 1 + while {$more} { + lassign [IEnumWorkItems_Next $ienum 20] more items + set result [concat $result $items] + } + } finally { + IUnknown_Release $ienum + } + return $result +} + +# Sets the specified properties of the ITask +proc twapi::itask_configure {itask args} { + + array set opts [parseargs args { + application.arg + maxruntime.int + params.arg + priority.arg + workingdir.arg + account.arg + password.arg + comment.arg + creator.arg + data.arg + idlewait.int + idlewaitdeadline.int + interactive.bool + deletewhendone.bool + disabled.bool + hidden.bool + runonlyifloggedon.bool + startonlyifidle.bool + resumesystem.bool + killonidleend.bool + restartonidleresume.bool + dontstartonbatteries.bool + killifonbatteries.bool + } -maxleftover 0] + + if {[info exists opts(priority)]} { + switch -exact -- $opts(priority) { + normal {set opts(priority) 0x00000020} + abovenormal {set opts(priority) 0x00008000} + belownormal {set opts(priority) 0x00004000} + high {set opts(priority) 0x00000080} + realtime {set opts(priority) 0x00000100} + idle {set opts(priority) 0x00000040} + default {error "Unknown priority '$opts(priority)'. Must be one of 'normal', 'high', 'idle' or 'realtime'"} + } + } + + foreach {opt fn} { + application ITask_SetApplicationName + maxruntime ITask_SetMaxRunTime + params ITask_SetParameters + workingdir ITask_SetWorkingDirectory + priority ITask_SetPriority + comment IScheduledWorkItem_SetComment + creator IScheduledWorkItem_SetCreator + data IScheduledWorkItem_SetWorkItemData + errorretrycount IScheduledWorkItem_SetErrorRetryCount + errorretryinterval IScheduledWorkItem_SetErrorRetryInterval + } { + if {[info exists opts($opt)]} { + $fn $itask $opts($opt) + } + } + + if {[info exists opts(account)]} { + if {$opts(account) ne ""} { + if {![info exists opts(password)]} { + error "Option -password must be specified if -account is specified" + } + } else { + # System account. Set password to NULL pointer indicated + # by magic null pointer + set opts(password) $::twapi::nullptr + } + IScheduledWorkItem_SetAccountInformation $itask $opts(account) $opts(password) + } + + if {[info exists opts(idlewait)] || [info exists opts(idlewaitdeadline)]} { + # If either one is not specified, get the current settings + if {! ([info exists opts(idlewait)] && + [info exists opts(idlewaitdeadline)]) } { + lassign [IScheduledWorkItem_GetIdleWait $itask] idle dead + if {![info exists opts(idlewait)]} { + set opts(idlewait) $idle + } + if {![info exists opts(idlewaitdeadline)]} { + set opts(idlewaitdeadline) $dead + } + } + IScheduledWorkItem_SetIdleWait $itask $opts(idlewait) $opts(idlewaitdeadline) + } + + # Finally figure out and set the flags if needed + if {[info exists opts(interactive)] || + [info exists opts(deletewhendone)] || + [info exists opts(disabled)] || + [info exists opts(hidden)] || + [info exists opts(runonlyifloggedon)] || + [info exists opts(startonlyifidle)] || + [info exists opts(resumesystem)] || + [info exists opts(killonidleend)] || + [info exists opts(restartonidleresume)] || + [info exists opts(dontstartonbatteries)] || + [info exists opts(killifonbatteries)]} { + + # First, get the current flags + set flags [IScheduledWorkItem_GetFlags $itask] + foreach {opt val} { + interactive 0x1 + deletewhendone 0x2 + disabled 0x4 + startonlyifidle 0x10 + hidden 0x200 + runonlyifloggedon 0x2000 + resumesystem 0x1000 + killonidleend 0x20 + restartonidleresume 0x800 + dontstartonbatteries 0x40 + killifonbatteries 0x80 + } { + # Set / reset the bit if specified + if {[info exists opts($opt)]} { + if {$opts($opt)} { + setbits flags $val + } else { + resetbits flags $val + } + } + } + + # Now set the new value of flags + IScheduledWorkItem_SetFlags $itask $flags + } + + + return +} + +proc twapi::itask_get_info {itask args} { + # Note options errorretrycount and errorretryinterval are not implemented + # by the OS so left out + array set opts [parseargs args { + all + application + maxruntime + params + priority + workingdir + account + comment + creator + data + idlewait + idlewaitdeadline + interactive + deletewhendone + disabled + hidden + runonlyifloggedon + startonlyifidle + resumesystem + killonidleend + restartonidleresume + dontstartonbatteries + killifonbatteries + lastruntime + nextruntime + status + } -maxleftover 0] + + set result [list ] + if {$opts(all) || $opts(priority)} { + switch -exact -- [twapi::ITask_GetPriority $itask] { + 32 { set priority normal } + 64 { set priority idle } + 128 { set priority high } + 256 { set priority realtime } + 16384 { set priority belownormal } + 32768 { set priority abovenormal } + default { set priority unknown } + } + lappend result -priority $priority + } + + foreach {opt fn} { + application ITask_GetApplicationName + maxruntime ITask_GetMaxRunTime + params ITask_GetParameters + workingdir ITask_GetWorkingDirectory + account IScheduledWorkItem_GetAccountInformation + comment IScheduledWorkItem_GetComment + creator IScheduledWorkItem_GetCreator + data IScheduledWorkItem_GetWorkItemData + } { + if {$opts(all) || $opts($opt)} { + trap { + lappend result -$opt [$fn $itask] + } onerror {TWAPI_WIN32 -2147216625} { + # THe information is empty in the scheduler database + lappend result -$opt {} + } + } + } + + if {$opts(all) || $opts(lastruntime)} { + trap { + lappend result -lastruntime [_timelist_to_timestring [IScheduledWorkItem_GetMostRecentRunTime $itask]] + } onerror {TWAPI_WIN32 267011} { + # Not run yet at all + lappend result -lastruntime {} + } + } + + if {$opts(all) || $opts(nextruntime)} { + trap { + lappend result -nextruntime [_timelist_to_timestring [IScheduledWorkItem_GetNextRunTime $itask]] + } onerror {TWAPI_WIN32 267010} { + # Task is disabled + lappend result -nextruntime disabled + } onerror {TWAPI_WIN32 267015} { + # No triggers set + lappend result -nextruntime notriggers + } onerror {TWAPI_WIN32 267016} { + # No triggers set + lappend result -nextruntime oneventonly + } + } + + if {$opts(all) || $opts(status)} { + set status [IScheduledWorkItem_GetStatus $itask] + if {$status == 0x41300} { + set status ready + } elseif {$status == 0x41301} { + set status running + } elseif {$status == 0x41302} { + set status disabled + } elseif {$status == 0x41305} { + set status partiallydefined + } else { + set status unknown + } + lappend result -status $status + } + + + if {$opts(all) || $opts(idlewait) || $opts(idlewaitdeadline)} { + lassign [IScheduledWorkItem_GetIdleWait $itask] idle dead + if {$opts(all) || $opts(idlewait)} { + lappend result -idlewait $idle + } + if {$opts(all) || $opts(idlewaitdeadline)} { + lappend result -idlewaitdeadline $dead + } + } + + # Finally figure out and set the flags if needed + if {$opts(all) || + $opts(interactive) || + $opts(deletewhendone) || + $opts(disabled) || + $opts(hidden) || + $opts(runonlyifloggedon) || + $opts(startonlyifidle) || + $opts(resumesystem) || + $opts(killonidleend) || + $opts(restartonidleresume) || + $opts(dontstartonbatteries) || + $opts(killifonbatteries)} { + + # First, get the current flags + set flags [IScheduledWorkItem_GetFlags $itask] + foreach {opt val} { + interactive 0x1 + deletewhendone 0x2 + disabled 0x4 + startonlyifidle 0x10 + hidden 0x200 + runonlyifloggedon 0x2000 + resumesystem 0x1000 + killonidleend 0x20 + restartonidleresume 0x800 + dontstartonbatteries 0x40 + killifonbatteries 0x80 + } { + if {$opts(all) || $opts($opt)} { + lappend result -$opt [expr {($flags & $val) ? true : false}] + } + } + } + + + return $result +} + +# Get the runtimes for a task within an interval +proc twapi::itask_get_runtimes_within_interval {itask args} { + array set opts [parseargs args { + start.arg + end.arg + {count.int 1} + statusvar.arg + } -maxleftover 0] + + if {[info exists opts(start)]} { + set start [_timestring_to_timelist $opts(start)] + } else { + set start [_seconds_to_timelist [clock seconds]] + } + if {[info exists opts(end)]} { + set end [_timestring_to_timelist $opts(end)] + } else { + set end {2038 1 1 0 0 0 0} + } + + set result [list ] + if {[info exists opts(statusvar)]} { + upvar $opts(statusvar) status + } + lassign [IScheduledWorkItem_GetRunTimes $itask $start $end $opts(count)] status timelist + + foreach time $timelist { + lappend result [_timelist_to_timestring $time] + } + + + return $result +} + +# Saves the specified ITask +proc twapi::itask_save {itask} { + set ipersist [Twapi_IUnknown_QueryInterface $itask [name_to_iid IPersistFile] IPersistFile] + trap { + IPersistFile_Save $ipersist "" 1 + } finally { + IUnknown_Release $ipersist + } + return +} + +# Show property editor for a task +proc twapi::itask_edit_dialog {itask args} { + array set opts [parseargs args { + {hwin.arg 0} + } -maxleftover 0] + + return [twapi::IScheduledWorkItem_EditWorkItem $itask $opts(hwin) 0] +} + + +interp alias {} ::twapi::itask_release {} ::twapi::IUnknown_Release + +# Get information about a trigger +proc twapi::itasktrigger_get_info {itt} { + array set data [ITaskTrigger_GetTrigger $itt] + + set result(-begindate) [format %04d-%02d-%02d $data(wBeginYear) $data(wBeginMonth) $data(wBeginDay)] + + set result(-starttime) [format %02d:%02d $data(wStartHour) $data(wStartMinute)] + + if {$data(rgFlags) & 1} { + set result(-enddate) [format %04d-%02d-%02d $data(wEndYear) $data(wEndMonth) $data(wEndDay)] + } else { + set result(-enddate) "" + } + + set result(-duration) $data(MinutesDuration) + set result(-interval) $data(MinutesInterval) + if {$data(rgFlags) & 2} { + set result(-killatdurationend) true + } else { + set result(-killatdurationend) false + } + + if {$data(rgFlags) & 4} { + set result(-disabled) true + } else { + set result(-disabled) false + } + + switch -exact -- [lindex $data(type) 0] { + 0 { + set result(-type) once + } + 1 { + set result(-type) daily + set result(-period) [lindex $data(type) 1] + } + 2 { + set result(-type) weekly + set result(-period) [lindex $data(type) 1] + set result(-weekdays) [format 0x%x [lindex $data(type) 2]] + } + 3 { + set result(-type) monthlydate + set result(-daysofmonth) [format 0x%x [lindex $data(type) 1]] + set result(-months) [format 0x%x [lindex $data(type) 2]] + } + 4 { + set result(-type) monthlydow + set result(-weekofmonth) [lindex {first second third fourth last} [lindex $data(type) 2]] + set result(-weekdays) [format 0x%x [lindex $data(type) 2]] + set result(-months) [format 0x%x [lindex $data(type) 3]] + } + 5 { + set result(-type) onidle + } + 6 { + set result(-type) atsystemstart + } + 7 { + set result(-type) atlogon + } + } + return [array get result] +} + + +# Configure a task trigger +proc twapi::itasktrigger_configure {itt args} { + array set opts [parseargs args { + begindate.arg + enddate.arg + starttime.arg + interval.int + duration.int + killatdurationend.bool + disabled.bool + type.arg + weekofmonth.int + {period.int 1} + {weekdays.int 0x7f} + {daysofmonth.int 0x7fffffff} + {months.int 0xfff} + } -maxleftover 0] + + + array set data [ITaskTrigger_GetTrigger $itt] + + if {[info exists opts(begindate)]} { + lassign [split $opts(begindate) -] year month day + # Note we trim leading zeroes else Tcl thinks its octal + set data(wBeginYear) [scan $year %d] + set data(wBeginMonth) [scan $month %d] + set data(wBeginDay) [scan $day %d] + } + + if {[info exists opts(starttime)]} { + lassign [split $opts(starttime) :] hour minute + # Note we trim leading zeroes else Tcl thinks its octal + set data(wStartHour) [scan $hour %d] + set data(wStartMinute) [scan $minute %d] + } + + if {[info exists opts(enddate)]} { + if {$opts(enddate) ne ""} { + setbits data(rgFlags) 1; # Indicate end date is present + lassign [split $opts(enddate) -] year month day + # Note we trim leading zeroes else Tcl thinks its octal + set data(wEndYear) [scan $year %d] + set data(wEndMonth) [scan $month %d] + set data(wEndDay) [scan $day %d] + } else { + resetbits data(rgFlags) 1; # Indicate no end date + } + } + + + if {[info exists opts(duration)]} { + set data(MinutesDuration) $opts(duration) + } + + if {[info exists opts(interval)]} { + set data(MinutesInterval) $opts(interval) + } + + if {[info exists opts(killatdurationend)]} { + if {$opts(killatdurationend)} { + setbits data(rgFlags) 2 + } else { + resetbits data(rgFlags) 2 + } + } + + if {[info exists opts(disabled)]} { + if {$opts(disabled)} { + setbits data(rgFlags) 4 + } else { + resetbits data(rgFlags) 4 + } + } + + # Note the type specific options are only used if -type is specified + if {[info exists opts(type)]} { + switch -exact -- $opts(type) { + once { + set data(type) [list 0] + } + daily { + set data(type) [list 1 $opts(period)] + } + weekly { + set data(type) [list 2 $opts(period) $opts(weekdays)] + } + monthlydate { + set data(type) [list 3 $opts(daysofmonth) $opts(months)] + } + monthlydow { + set data(type) [list 4 $opts(weekofmonth) $opts(weekdays) $opts(months)] + } + onidle { + set data(type) [list 5] + } + atsystemstart { + set data(type) [list 6] + } + atlogon { + set data(type) [list 7] + } + } + } + + ITaskTrigger_SetTrigger $itt [array get data] + return +} + +interp alias {} ::twapi::itasktrigger_release {} ::twapi::IUnknown_Release + +# Create a new task from scratch. Basically a wrapper around the +# corresponding itaskscheduler, itask and itasktrigger calls +proc twapi::mstask_create {taskname args} { + + # The options are a combination of itask_configure and + # itasktrigger_configure. + # Note the disabled option default to false explicitly. This is because + # the task trigger will default to disabled unless specifically set. + array set opts [parseargs args { + system.arg + application.arg + maxruntime.int + params.arg + priority.arg + workingdir.arg + account.arg + password.arg + comment.arg + creator.arg + data.arg + idlewait.int + idlewaitdeadline.int + interactive.bool + deletewhendone.bool + {disabled.bool false} + hidden.bool + runonlyifloggedon.bool + startonlyifidle.bool + resumesystem.bool + killonidleend.bool + restartonidleresume.bool + dontstartonbatteries.bool + killifonbatteries.bool + begindate.arg + enddate.arg + starttime.arg + interval.int + duration.int + killatdurationend.bool + type.arg + period.int + weekdays.int + daysofmonth.int + months.int + } -maxleftover 0] + + set its [itaskscheduler_new] + trap { + if {[info exists opts(system)]} { + itaskscheduler_set_target_system $opts(system) + } + + set itask [itaskscheduler_new_itask $its $taskname] + # Construct the command line for configuring the task + set cmd [list itask_configure $itask] + foreach opt { + application + maxruntime + params + priority + workingdir + account + password + comment + creator + data + idlewait + idlewaitdeadline + interactive + deletewhendone + disabled + hidden + runonlyifloggedon + startonlyifidle + resumesystem + killonidleend + restartonidleresume + dontstartonbatteries + killifonbatteries + } { + if {[info exists opts($opt)]} { + lappend cmd -$opt $opts($opt) + } + } + eval $cmd + + # Now get a trigger and configure it + set itt [lindex [itask_new_itasktrigger $itask] 1] + set cmd [list itasktrigger_configure $itt] + foreach opt { + begindate + enddate + interval + starttime + duration + killatdurationend + type + period + weekdays + daysofmonth + months + disabled + } { + if {[info exists opts($opt)]} { + lappend cmd -$opt $opts($opt) + } + } + eval $cmd + + # Save the task + itask_save $itask + + } finally { + IUnknown_Release $its + if {[info exists itask]} { + IUnknown_Release $itask + } + if {[info exists itt]} { + IUnknown_Release $itt + } + } + return +} + +# Delete a task +proc twapi::mstask_delete {taskname args} { + # The options are a combination of itask_configure and + # itasktrigger_configure + array set opts [parseargs args { + system.arg + } -maxleftover 0] + set its [itaskscheduler_new] + trap { + if {[info exists opts(system)]} { + itaskscheduler_set_target_system $opts(system) + } + itaskscheduler_delete_task $its $taskname + } finally { + IUnknown_Release $its + } + return +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/multimedia.tcl b/src/vendorlib_tcl8/twapi-5.0b1/multimedia.tcl similarity index 95% rename from src/vendorlib_tcl8/twapi4.7.2/multimedia.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/multimedia.tcl index 57665197..f3678571 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/multimedia.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/multimedia.tcl @@ -1,75 +1,75 @@ -# -# Copyright (c) 2012 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Generate sound for the specified duration -proc twapi::beep {args} { - array set opts [parseargs args { - {frequency.int 1000} - {duration.int 100} - {type.arg} - }] - - if {[info exists opts(type)]} { - switch -exact -- $opts(type) { - ok {MessageBeep 0} - hand {MessageBeep 0x10} - question {MessageBeep 0x20} - exclaimation {MessageBeep 0x30} - exclamation {MessageBeep 0x30} - asterisk {MessageBeep 0x40} - default {error "Unknown sound type '$opts(type)'"} - } - return - } - Beep $opts(frequency) $opts(duration) - return -} - -# Play the specified sound -proc twapi::play_sound {name args} { - array set opts [parseargs args { - alias - async - loop - nodefault - wait - nostop - }] - - if {$opts(alias)} { - set flags 0x00010000; #SND_ALIAS - } else { - set flags 0x00020000; #SND_FILENAME - } - if {$opts(loop)} { - # Note LOOP requires ASYNC - setbits flags 0x9; #SND_LOOP | SND_ASYNC - } else { - if {$opts(async)} { - setbits flags 0x0001; #SND_ASYNC - } else { - setbits flags 0x0000; #SND_SYNC - } - } - - if {$opts(nodefault)} { - setbits flags 0x0002; #SND_NODEFAULT - } - - if {! $opts(wait)} { - setbits flags 0x00002000; #SND_NOWAIT - } - - if {$opts(nostop)} { - setbits flags 0x0010; #SND_NOSTOP - } - - return [PlaySound $name 0 $flags] -} - -proc twapi::stop_sound {} { - PlaySound "" 0 0x0040; #SND_PURGE -} +# +# Copyright (c) 2012 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Generate sound for the specified duration +proc twapi::beep {args} { + array set opts [parseargs args { + {frequency.int 1000} + {duration.int 100} + {type.arg} + }] + + if {[info exists opts(type)]} { + switch -exact -- $opts(type) { + ok {MessageBeep 0} + hand {MessageBeep 0x10} + question {MessageBeep 0x20} + exclaimation {MessageBeep 0x30} + exclamation {MessageBeep 0x30} + asterisk {MessageBeep 0x40} + default {error "Unknown sound type '$opts(type)'"} + } + return + } + Beep $opts(frequency) $opts(duration) + return +} + +# Play the specified sound +proc twapi::play_sound {name args} { + array set opts [parseargs args { + alias + async + loop + nodefault + wait + nostop + }] + + if {$opts(alias)} { + set flags 0x00010000; #SND_ALIAS + } else { + set flags 0x00020000; #SND_FILENAME + } + if {$opts(loop)} { + # Note LOOP requires ASYNC + setbits flags 0x9; #SND_LOOP | SND_ASYNC + } else { + if {$opts(async)} { + setbits flags 0x0001; #SND_ASYNC + } else { + setbits flags 0x0000; #SND_SYNC + } + } + + if {$opts(nodefault)} { + setbits flags 0x0002; #SND_NODEFAULT + } + + if {! $opts(wait)} { + setbits flags 0x00002000; #SND_NOWAIT + } + + if {$opts(nostop)} { + setbits flags 0x0010; #SND_NOSTOP + } + + return [PlaySound $name 0 $flags] +} + +proc twapi::stop_sound {} { + PlaySound "" 0 0x0040; #SND_PURGE +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/namedpipe.tcl b/src/vendorlib_tcl8/twapi-5.0b1/namedpipe.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/namedpipe.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/namedpipe.tcl index 7e222f13..30518a08 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/namedpipe.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/namedpipe.tcl @@ -1,103 +1,103 @@ -# -# Copyright (c) 2010-2011, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Implementation of named pipes - -proc twapi::namedpipe_server {name args} { - set name [file nativename $name] - - # Only byte mode currently supported. Message mode does - # not mesh well with Tcl channel infrastructure. - # readmode.arg - # writemode.arg - - array set opts [twapi::parseargs args { - {access.arg {read write}} - {writedacl 0 0x00040000} - {writeowner 0 0x00080000} - {writesacl 0 0x01000000} - {writethrough 0 0x80000000} - denyremote - {timeout.int 50} - {maxinstances.int 255} - {secd.arg {}} - {inherit.bool 0} - } -maxleftover 0] - - # 0x40000000 -> OVERLAPPED I/O - set open_mode [expr { - [twapi::_parse_symbolic_bitmask $opts(access) {read 1 write 2}] | - $opts(writedacl) | $opts(writeowner) | - $opts(writesacl) | $opts(writethrough) | - 0x40000000 - }] - - set pipe_mode 0 - if {$opts(denyremote)} { - if {! [twapi::min_os_version 6]} { - error "Option -denyremote not supported on this operating system." - } - set pipe_mode [expr {$pipe_mode | 8}] - } - - return [twapi::Twapi_NPipeServer $name $open_mode $pipe_mode \ - $opts(maxinstances) 4000 4000 $opts(timeout) \ - [_make_secattr $opts(secd) $opts(inherit)]] -} - -proc twapi::namedpipe_client {name args} { - set name [file nativename $name] - - # Only byte mode currently supported. Message mode does - # not mesh well with Tcl channel infrastructure. - # readmode.arg - # writemode.arg - - array set opts [twapi::parseargs args { - {access.arg {read write}} - impersonationlevel.arg - {impersonateeffectiveonly.bool false 0x00080000} - {impersonatecontexttracking.bool false 0x00040000} - } -maxleftover 0] - - # FILE_READ_DATA 0x00000001 - # FILE_WRITE_DATA 0x00000002 - # Note - use _parse_symbolic_bitmask because we allow user to specify - # numeric masks as well - set desired_access [twapi::_parse_symbolic_bitmask $opts(access) { - read 1 - write 2 - }] - - set flags 0 - if {[info exists opts(impersonationlevel)]} { - switch -exact -- $opts(impersonationlevel) { - anonymous { set flags 0x00100000 } - identification { set flags 0x00110000 } - impersonation { set flags 0x00120000 } - delegation { set flags 0x00130000 } - default { - # ERROR_BAD_IMPERSONATION_LEVEL - win32_error 1346 "Invalid impersonation level '$opts(impersonationlevel)'." - } - } - set flags [expr {$flags | $opts(impersonateeffectiveonly) | - $opts(impersonatecontexttracking)}] - } - - set share_mode 0; # Share none - set secattr {}; # At some point use this for "inherit" flag - set create_disposition 3; # OPEN_EXISTING - return [twapi::Twapi_NPipeClient $name $desired_access $share_mode \ - $secattr $create_disposition $flags] -} - -# Impersonate a named pipe client -proc twapi::impersonate_namedpipe_client {chan} { - set h [get_tcl_channel_handle $chan read] - ImpersonateNamedPipeClient $h -} - +# +# Copyright (c) 2010-2011, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Implementation of named pipes + +proc twapi::namedpipe_server {name args} { + set name [file nativename $name] + + # Only byte mode currently supported. Message mode does + # not mesh well with Tcl channel infrastructure. + # readmode.arg + # writemode.arg + + array set opts [twapi::parseargs args { + {access.arg {read write}} + {writedacl 0 0x00040000} + {writeowner 0 0x00080000} + {writesacl 0 0x01000000} + {writethrough 0 0x80000000} + denyremote + {timeout.int 50} + {maxinstances.int 255} + {secd.arg {}} + {inherit.bool 0} + } -maxleftover 0] + + # 0x40000000 -> OVERLAPPED I/O + set open_mode [expr { + [twapi::_parse_symbolic_bitmask $opts(access) {read 1 write 2}] | + $opts(writedacl) | $opts(writeowner) | + $opts(writesacl) | $opts(writethrough) | + 0x40000000 + }] + + set pipe_mode 0 + if {$opts(denyremote)} { + if {! [twapi::min_os_version 6]} { + error "Option -denyremote not supported on this operating system." + } + set pipe_mode [expr {$pipe_mode | 8}] + } + + return [twapi::Twapi_NPipeServer $name $open_mode $pipe_mode \ + $opts(maxinstances) 4000 4000 $opts(timeout) \ + [_make_secattr $opts(secd) $opts(inherit)]] +} + +proc twapi::namedpipe_client {name args} { + set name [file nativename $name] + + # Only byte mode currently supported. Message mode does + # not mesh well with Tcl channel infrastructure. + # readmode.arg + # writemode.arg + + array set opts [twapi::parseargs args { + {access.arg {read write}} + impersonationlevel.arg + {impersonateeffectiveonly.bool false 0x00080000} + {impersonatecontexttracking.bool false 0x00040000} + } -maxleftover 0] + + # FILE_READ_DATA 0x00000001 + # FILE_WRITE_DATA 0x00000002 + # Note - use _parse_symbolic_bitmask because we allow user to specify + # numeric masks as well + set desired_access [twapi::_parse_symbolic_bitmask $opts(access) { + read 1 + write 2 + }] + + set flags 0 + if {[info exists opts(impersonationlevel)]} { + switch -exact -- $opts(impersonationlevel) { + anonymous { set flags 0x00100000 } + identification { set flags 0x00110000 } + impersonation { set flags 0x00120000 } + delegation { set flags 0x00130000 } + default { + # ERROR_BAD_IMPERSONATION_LEVEL + win32_error 1346 "Invalid impersonation level '$opts(impersonationlevel)'." + } + } + set flags [expr {$flags | $opts(impersonateeffectiveonly) | + $opts(impersonatecontexttracking)}] + } + + set share_mode 0; # Share none + set secattr {}; # At some point use this for "inherit" flag + set create_disposition 3; # OPEN_EXISTING + return [twapi::Twapi_NPipeClient $name $desired_access $share_mode \ + $secattr $create_disposition $flags] +} + +# Impersonate a named pipe client +proc twapi::impersonate_namedpipe_client {chan} { + set h [get_tcl_channel_handle $chan read] + ImpersonateNamedPipeClient $h +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/network.tcl b/src/vendorlib_tcl8/twapi-5.0b1/network.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/network.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/network.tcl index 4cdbba87..7c8fa392 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/network.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/network.tcl @@ -1,1124 +1,1124 @@ -# -# Copyright (c) 2004-2014, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - record IP_ADAPTER_ADDRESSES_XP { - -ipv4ifindex -adaptername -unicastaddresses -anycastaddresses - -multicastaddresses -dnsservers -dnssuffix -description - -friendlyname -physicaladdress -flags -mtu -type -operstatus - -ipv6ifindex -zoneindices -prefixes - } - - if {[min_os_version 6]} { - record IP_ADAPTER_ADDRESSES [list {*}[IP_ADAPTER_ADDRESSES_XP] -transmitspeed -receivespeed -winsaddresses -gatewayaddresses -ipv4metric -ipv6metric -luid -dhcpv4server -compartmentid -networkguid -connectiontype -tunneltype -dhcpv6server -dhcpv6clientduid -dhcpv6iaid -dnssuffixes] - } else { - record IP_ADAPTER_ADDRESSES [IP_ADAPTER_ADDRESSES_XP] - } - - record IP_ADAPTER_UNICAST_ADDRESS { - -flags -address -prefixorigin -suffixorigin -dadstate -validlifetime -preferredlifetime -leaselifetime - } - - record IP_ADAPTER_ANYCAST_ADDRESS {-flags -address} - record IP_ADAPTER_MULTICAST_ADDRESS [IP_ADAPTER_ANYCAST_ADDRESS] - record IP_ADAPTER_DNS_SERVER_ADDRESS [IP_ADAPTER_ANYCAST_ADDRESS] -} - -proc twapi::get_network_adapters {} { - # 0x20 -> SKIP_FRIENDLYNAME - # 0x0f -> SKIP_DNS_SERVER, SKIP_UNICAST/MULTICAST/ANYCAST - return [lpick [GetAdaptersAddresses 0 0x2f] [enum [IP_ADAPTER_ADDRESSES] -adaptername]] -} - -proc twapi::get_network_adapters_detail {} { - set recs {} - # We only return fields common to all platforms - set fields [IP_ADAPTER_ADDRESSES_XP] - foreach rec [GetAdaptersAddresses 0 0] { - set rec [IP_ADAPTER_ADDRESSES set $rec \ - -physicaladdress [_hwaddr_binary_to_string [IP_ADAPTER_ADDRESSES -physicaladdress $rec]] \ - -unicastaddresses [ntwine [IP_ADAPTER_UNICAST_ADDRESS] [IP_ADAPTER_ADDRESSES -unicastaddresses $rec]] \ - -multicastaddresses [ntwine [IP_ADAPTER_MULTICAST_ADDRESS] [IP_ADAPTER_ADDRESSES -multicastaddresses $rec]] \ - -anycastaddresses [ntwine [IP_ADAPTER_ANYCAST_ADDRESS] [IP_ADAPTER_ADDRESSES -anycastaddresses $rec]] \ - -dnsservers [ntwine [IP_ADAPTER_DNS_SERVER_ADDRESS] [IP_ADAPTER_ADDRESSES -dnsservers $rec]]] - - lappend recs [IP_ADAPTER_ADDRESSES select $rec $fields] - } - return [list $fields $recs] -} - -# Get the list of local IP addresses -proc twapi::get_system_ipaddrs {args} { - array set opts [parseargs args { - {ipversion.arg 0} - {types.arg unicast} - adaptername.arg - } -maxleftover 0] - - # 0x20 -> SKIP_FRIENDLYNAME - # 0x08 -> SKIP_DNS_SERVER - set flags 0x2f - if {"all" in $opts(types)} { - set flags 0x20 - } else { - if {"unicast" in $opts(types)} {incr flags -1} - if {"anycast" in $opts(types)} {incr flags -2} - if {"multicast" in $opts(types)} {incr flags -4} - } - - set addrs {} - trap { - set entries [GetAdaptersAddresses [_ipversion_to_af $opts(ipversion)] $flags] - } onerror {TWAPI_WIN32 232} { - # Not installed, so no addresses - return {} - } - - foreach entry $entries { - if {[info exists opts(adaptername)] && - [string compare -nocase [IP_ADAPTER_ADDRESSES -adaptername $entry] $opts(adaptername)]} { - continue - } - - foreach rec [IP_ADAPTER_ADDRESSES -unicastaddresses $entry] { - lappend addrs [IP_ADAPTER_UNICAST_ADDRESS -address $rec] - } - foreach rec [IP_ADAPTER_ADDRESSES -anycastaddresses $entry] { - lappend addrs [IP_ADAPTER_ANYCAST_ADDRESS -address $rec] - } - foreach rec [IP_ADAPTER_ADDRESSES -multicastaddresses $entry] { - lappend addrs [IP_ADAPTER_MULTICAST_ADDRESS -address $rec] - } - } - - return [lsort -unique $addrs] -} - -# Get network related information -proc twapi::get_network_info {args} { - # Map options into the positions in result of GetNetworkParams - array set getnetworkparams_opts { - hostname 0 - domain 1 - dnsservers 2 - dhcpscopeid 4 - routingenabled 5 - arpproxyenabled 6 - dnsenabled 7 - } - - array set opts [parseargs args \ - [concat [list all] \ - [array names getnetworkparams_opts]]] - set result [list ] - foreach opt [array names getnetworkparams_opts] { - if {!$opts(all) && !$opts($opt)} continue - if {![info exists netparams]} { - set netparams [GetNetworkParams] - } - lappend result -$opt [lindex $netparams $getnetworkparams_opts($opt)] - } - - return $result -} - - -proc twapi::get_network_adapter_info {interface args} { - array set opts [parseargs args { - all - adaptername - anycastaddresses - description - dhcpenabled - dnsservers - dnssuffix - friendlyname - ipv4ifindex - ipv6ifindex - multicastaddresses - mtu - operstatus - physicaladdress - prefixes - type - unicastaddresses - zoneindices - - {ipversion.arg 0} - } -maxleftover 0 -hyphenated] - - set ipversion [_ipversion_to_af $opts(-ipversion)] - - set flags 0 - if {! $opts(-all)} { - # If not asked for some fields, don't bother getting them - if {! $opts(-unicastaddresses)} { incr flags 0x1 } - if {! $opts(-anycastaddresses)} { incr flags 0x2 } - if {! $opts(-multicastaddresses)} { incr flags 0x4 } - if {! $opts(-dnsservers)} { incr flags 0x8 } - if {! $opts(-friendlyname)} { incr flags 0x20 } - - if {$opts(-prefixes)} { incr flags 0x10 } - } else { - incr flags 0x10; # Want prefixes also - } - - set entries [GetAdaptersAddresses $ipversion $flags] - set nameindex [enum [IP_ADAPTER_ADDRESSES] -adaptername] - set entry [lsearch -nocase -exact -inline -index $nameindex $entries $interface] - if {[llength $entry] == 0} { - error "No interface matching '$interface'." - } - - array set result [IP_ADAPTER_ADDRESSES $entry] - if {$opts(-all) || $opts(-dhcpenabled)} { - set result(-dhcpenabled) [expr {($result(-flags) & 0x4) != 0}] - } - # Note even if -all is specified, we still loop through because - # the fields of IP_ADAPTER_ADDRESSES are a superset of options - foreach opt [IP_ADAPTER_ADDRESSES] { - # Select only those fields that have an option defined - # and that option is selected - if {!([info exists opts($opt)] && ($opts(-all) || $opts($opt)))} { - unset result($opt) - } - } - if {[info exists result(-physicaladdress)]} { - set result(-physicaladdress) [_hwaddr_binary_to_string $result(-physicaladdress)] - } - if {[info exists result(-unicastaddresses)]} { - set result(-unicastaddresses) [ntwine [IP_ADAPTER_UNICAST_ADDRESS] $result(-unicastaddresses)] - } - if {[info exists result(-multicastaddresses)]} { - set result(-multicastaddresses) [ntwine [IP_ADAPTER_MULTICAST_ADDRESS] $result(-multicastaddresses)] - } - if {[info exists result(-anycastaddresses)]} { - set result(-anycastaddresses) [ntwine [IP_ADAPTER_ANYCAST_ADDRESS] $result(-anycastaddresses)] - } - if {[info exists result(-dnsservers)]} { - set result(-dnsservers) [ntwine [IP_ADAPTER_DNS_SERVER_ADDRESS] $result(-dnsservers)] - } - - return [array get result] -} - -# Get the address->h/w address table -proc twapi::get_arp_table {args} { - array set opts [parseargs args { - sort - }] - - set arps [list ] - - foreach arp [GetIpNetTable $opts(sort)] { - lassign $arp ifindex hwaddr ipaddr type - # Token for enry 0 1 2 3 4 - set type [lindex {other other invalid dynamic static} $type] - if {$type == ""} { - set type other - } - lappend arps [list $ifindex [_hwaddr_binary_to_string $hwaddr] $ipaddr $type] - } - return [list [list ifindex hwaddr ipaddr type] $arps] -} - -# Return IP address for a hw address -proc twapi::ipaddr_to_hwaddr {ipaddr {varname ""}} { - if {![Twapi_IPAddressFamily $ipaddr]} { - error "$ipaddr is not a valid IP V4 address" - } - - foreach arp [GetIpNetTable 0] { - if {[lindex $arp 3] == 2} continue; # Invalid entry type - if {[string equal $ipaddr [lindex $arp 2]]} { - set result [_hwaddr_binary_to_string [lindex $arp 1]] - break - } - } - - # If could not get from ARP table, see if it is one of our own - # Ignore errors - if {![info exists result]} { - foreach ifc [get_network_adapters] { - catch { - array set netifinfo [get_network_adapter_info $ifc -unicastaddresses -physicaladdress] - if {$netifinfo(-physicaladdress) eq ""} continue - foreach elem $netifinfo(-unicastaddresses) { - if {[dict get $elem -address] eq $ipaddr} { - set result $netifinfo(-physicaladdress) - break - } - } - } - if {[info exists result]} { - break - } - } - } - - if {[info exists result]} { - if {$varname == ""} { - return $result - } - upvar $varname var - set var $result - return 1 - } else { - if {$varname == ""} { - error "Could not map IP address $ipaddr to a hardware address" - } - return 0 - } -} - -# Return hw address for a IP address -proc twapi::hwaddr_to_ipaddr {hwaddr {varname ""}} { - set hwaddr [string map {- "" : ""} $hwaddr] - foreach arp [GetIpNetTable 0] { - if {[lindex $arp 3] == 2} continue; # Invalid entry type - if {[string equal $hwaddr [_hwaddr_binary_to_string [lindex $arp 1] ""]]} { - set result [lindex $arp 2] - break - } - } - - # If could not get from ARP table, see if it is one of our own - # Ignore errors - if {![info exists result]} { - foreach ifc [get_network_adapters] { - catch { - array set netifinfo [get_network_adapter_info $ifc -unicastaddresses -physicaladdress] - if {$netifinfo(-physicaladdress) eq ""} continue - set ifhwaddr [string map {- ""} $netifinfo(-physicaladdress)] - if {[string equal -nocase $hwaddr $ifhwaddr]} { - foreach elem $netifinfo(-unicastaddresses) { - if {[dict get $elem -address] ne ""} { - set result [dict get $elem -address] - break - } - } - } - } - if {[info exists result]} { - break - } - } - } - - if {[info exists result]} { - if {$varname == ""} { - return $result - } - upvar $varname var - set var $result - return 1 - } else { - if {$varname == ""} { - error "Could not map hardware address $hwaddr to an IP address" - } - return 0 - } -} - -# Flush the arp table for a given interface -proc twapi::flush_arp_tables {args} { - if {[llength $args] == 0} { - set args [get_network_adapters] - } - foreach arg $args { - array set ifc [get_network_adapter_info $arg -type -ipv4ifindex] - if {$ifc(-type) != 24} { - trap { - FlushIpNetTable $ifc(-ipv4ifindex) - } onerror {} { - # Ignore - flush not supported for that interface type - } - } - } -} - -# Return the list of TCP connections -twapi::proc* twapi::get_tcp_connections {args} { - variable tcp_statenames - variable tcp_statevalues - - array set tcp_statevalues { - closed 1 - listen 2 - syn_sent 3 - syn_rcvd 4 - estab 5 - fin_wait1 6 - fin_wait2 7 - close_wait 8 - closing 9 - last_ack 10 - time_wait 11 - delete_tcb 12 - } - foreach {name val} [array get tcp_statevalues] { - set tcp_statenames($val) $name - } -} { - variable tcp_statenames - variable tcp_statevalues - - array set opts [parseargs args { - state - {ipversion.arg 0} - localaddr - remoteaddr - localport - remoteport - pid - modulename - modulepath - bindtime - all - matchstate.arg - matchlocaladdr.arg - matchremoteaddr.arg - matchlocalport.int - matchremoteport.int - matchpid.int - } -maxleftover 0] - - set opts(ipversion) [_ipversion_to_af $opts(ipversion)] - - if {! ($opts(state) || $opts(localaddr) || $opts(remoteaddr) || $opts(localport) || $opts(remoteport) || $opts(pid) || $opts(modulename) || $opts(modulepath) || $opts(bindtime))} { - set opts(all) 1 - } - - # Convert state to appropriate symbol if necessary - if {[info exists opts(matchstate)]} { - set matchstates [list ] - foreach stateval $opts(matchstate) { - if {[info exists tcp_statevalues($stateval)]} { - lappend matchstates $stateval - continue - } - if {[info exists tcp_statenames($stateval)]} { - lappend matchstates $tcp_statenames($stateval) - continue - } - error "Unrecognized connection state '$stateval' specified for option -matchstate" - } - } - - foreach opt {matchlocaladdr matchremoteaddr} { - if {[info exists opts($opt)]} { - # Note this also normalizes the address format - set $opt [_hosts_to_ip_addrs $opts($opt)] - if {[llength [set $opt]] == 0} { - return [list ]; # No addresses, so no connections will match - } - } - } - - # Get the complete list of connections - if {$opts(modulename) || $opts(modulepath) || $opts(bindtime) || $opts(all)} { - set level 8 - } else { - set level 5 - } - - # See if any matching needs to be done - if {[info exists opts(matchlocaladdr)] || [info exists opts(matchlocalport)] || - [info exist opts(matchremoteaddr)] || [info exists opts(matchremoteport)] || - [info exists opts(matchpid)] || [info exists opts(matchstate)]} { - set need_matching 1 - } else { - set need_matching 0 - } - - - set conns [list ] - foreach entry [_get_all_tcp 0 $level $opts(ipversion)] { - lassign $entry state localaddr localport remoteaddr remoteport pid bindtime modulename modulepath - - if {[string equal $remoteaddr 0.0.0.0]} { - # Socket not connected. WIndows passes some random value - # for remote port in this case. Set it to 0 - set remoteport 0 - } - - if {[info exists tcp_statenames($state)]} { - set state $tcp_statenames($state) - } - if {$need_matching} { - if {[info exists opts(matchpid)]} { - # See if this platform even returns the PID - if {$pid == ""} { - error "Connection process id not available on this system." - } - if {$pid != $opts(matchpid)} { - continue - } - } - if {[info exists matchlocaladdr] && - [lsearch -exact $matchlocaladdr $localaddr] < 0} { - # Not in match list - continue - } - if {[info exists matchremoteaddr] && - [lsearch -exact $matchremoteaddr $remoteaddr] < 0} { - # Not in match list - continue - } - if {[info exists opts(matchlocalport)] && - $opts(matchlocalport) != $localport} { - continue - } - if {[info exists opts(matchremoteport)] && - $opts(matchremoteport) != $remoteport} { - continue - } - if {[info exists matchstates] && [lsearch -exact $matchstates $state] < 0} { - continue - } - } - - # OK, now we have matched. Include specified fields in the result - set conn [list ] - foreach opt {localaddr localport remoteaddr remoteport state pid bindtime modulename modulepath} { - if {$opts(all) || $opts($opt)} { - lappend conn [set $opt] - } - } - lappend conns $conn - } - - # ORDER MUST MATCH ORDER ABOVE - set fields [list ] - foreach opt {localaddr localport remoteaddr remoteport state pid bindtime modulename modulepath} { - if {$opts(all) || $opts($opt)} { - lappend fields -$opt - } - } - - return [list $fields $conns] -} - - -# Return the list of UDP connections -proc twapi::get_udp_connections {args} { - array set opts [parseargs args { - {ipversion.arg 0} - localaddr - localport - pid - modulename - modulepath - bindtime - all - matchlocaladdr.arg - matchlocalport.int - matchpid.int - } -maxleftover 0] - - set opts(ipversion) [_ipversion_to_af $opts(ipversion)] - - if {! ($opts(localaddr) || $opts(localport) || $opts(pid) || $opts(modulename) || $opts(modulepath) || $opts(bindtime))} { - set opts(all) 1 - } - - if {[info exists opts(matchlocaladdr)]} { - # Note this also normalizes the address format - set matchlocaladdr [_hosts_to_ip_addrs $opts(matchlocaladdr)] - if {[llength $matchlocaladdr] == 0} { - return [list ]; # No addresses, so no connections will match - } - } - - # Get the complete list of connections - # Get the complete list of connections - if {$opts(modulename) || $opts(modulepath) || $opts(bindtime) || $opts(all)} { - set level 2 - } else { - set level 1 - } - set conns [list ] - foreach entry [_get_all_udp 0 $level $opts(ipversion)] { - foreach {localaddr localport pid bindtime modulename modulepath} $entry { - break - } - if {[info exists opts(matchpid)]} { - # See if this platform even returns the PID - if {$pid == ""} { - error "Connection process id not available on this system." - } - if {$pid != $opts(matchpid)} { - continue - } - } - if {[info exists matchlocaladdr] && - [lsearch -exact $matchlocaladdr $localaddr] < 0} { - continue - } - if {[info exists opts(matchlocalport)] && - $opts(matchlocalport) != $localport} { - continue - } - - # OK, now we have matched. Include specified fields in the result - set conn [list ] - foreach opt {localaddr localport pid bindtime modulename modulepath} { - if {$opts(all) || $opts($opt)} { - lappend conn [set $opt] - } - } - lappend conns $conn - } - - # ORDER MUST MATCH THAT ABOVE - set fields [list ] - foreach opt {localaddr localport pid bindtime modulename modulepath} { - if {$opts(all) || $opts($opt)} { - lappend fields -$opt - } - } - - return [list $fields $conns] -} - -# Terminates a TCP connection. Does not generate an error if connection -# does not exist -proc twapi::terminate_tcp_connections {args} { - array set opts [parseargs args { - matchstate.arg - matchlocaladdr.arg - matchremoteaddr.arg - matchlocalport.int - matchremoteport.int - matchpid.int - } -maxleftover 0] - - # TBD - ignore 'no such connection' errors - - # If local and remote endpoints fully specified, just directly call - # SetTcpEntry. Note pid must NOT be specified since we must then - # fall through and check for that pid - if {[info exists opts(matchlocaladdr)] && [info exists opts(matchlocalport)] && - [info exists opts(matchremoteaddr)] && [info exists opts(matchremoteport)] && - ! [info exists opts(matchpid)]} { - # 12 is "delete" code - catch { - SetTcpEntry [list 12 $opts(matchlocaladdr) $opts(matchlocalport) $opts(matchremoteaddr) $opts(matchremoteport)] - } - return - } - - # Get connection list and go through matching on each - # TBD - optimize by precalculating if *ANY* matching is to be done - # and if not, skip the whole matching sequence - foreach conn [twapi::recordarray getlist [get_tcp_connections {*}[_get_array_as_options opts]] -format dict] { - array set aconn $conn - # TBD - should we handle integer values of opts(state) ? - if {[info exists opts(matchstate)] && - $opts(matchstate) != $aconn(-state)} { - continue - } - if {[info exists opts(matchlocaladdr)] && - $opts(matchlocaladdr) != $aconn(-localaddr)} { - continue - } - if {[info exists opts(matchlocalport)] && - $opts(matchlocalport) != $aconn(-localport)} { - continue - } - if {[info exists opts(matchremoteaddr)] && - $opts(matchremoteaddr) != $aconn(-remoteaddr)} { - continue - } - if {[info exists opts(remoteport)] && - $opts(matchremoteport) != $aconn(-remoteport)} { - continue - } - if {[info exists opts(matchpid)] && - $opts(matchpid) != $aconn(-pid)} { - continue - } - # Matching conditions fulfilled - # 12 is "delete" code - catch { - SetTcpEntry [list 12 $aconn(-localaddr) $aconn(-localport) $aconn(-remoteaddr) $aconn(-remoteport)] - } - } - return -} - -# Flush cache of host names and ports. -# Backward compatibility - no op since we no longer have a cache -proc twapi::flush_network_name_cache {} {} - -# IP addr -> hostname -proc twapi::resolve_address {addr args} { - - # flushcache is ignored (for backward compatibility only) - array set opts [parseargs args { - flushcache - async.arg - } -maxleftover 0] - - # Note as a special case, we treat 0.0.0.0 explicitly since - # win32 getnameinfo translates this to the local host name which - # is completely bogus. - if {$addr eq "0.0.0.0"} { - if {[info exists opts(async)]} { - after idle [list after 0 $opts(async) [list $addr success $addr]] - return "" - } else { - return $addr - } - } - - # If async option, we will call back our internal function which - # will update the cache and then invoke the caller's script - if {[info exists opts(async)]} { - variable _address_handler_scripts - set id [Twapi_ResolveAddressAsync $addr] - set _address_handler_scripts($id) [list $addr $opts(async)] - return "" - } - - # Synchronous - set name [lindex [twapi::getnameinfo [list $addr] 8] 0] - if {$name eq $addr} { - # Could not resolve. - set name "" - } - - return $name -} - -# host name -> IP addresses -proc twapi::resolve_hostname {name args} { - set name [string tolower $name] - - # -flushcache option ignored (for backward compat only) - array set opts [parseargs args { - flushcache - async.arg - {ipversion.arg 0} - } -maxleftover 0] - - # NOTE: we do not pass the IP version to getaddrinfo but always - # give it 0 and then filter the results based on IP version ourselves - # if necessary. This is because of some confusion over behaviour - # with various combination of flags. - - set opts(ipversion) [_ipversion_to_af $opts(ipversion)] - set flags 0 - if {[min_os_version 6]} { - # 0x100 -> AI_ALL. By default, Windows enables the AI_ADDRCONFIG - # flat which will hide IPv6 addresses if the local system does - # not have an *global* IPv6 addr configured. We don't want that - # so set AI_ALL to override it and get back all addresses. - set flags 0x100; # AI_ALL - } - - # If async option, we will call back our internal function which - # will update the cache and then invoke the caller's script - if {[info exists opts(async)]} { - variable _hostname_handler_scripts - set id [Twapi_ResolveHostnameAsync $name 0 $flags] - set _hostname_handler_scripts($id) [list $opts(ipversion) $name $opts(async)] - return "" - } - - # Resolve address synchronously - set addrs [list ] - trap { - foreach endpt [twapi::getaddrinfo $name 0 0 0 0 $flags] { - # endpt is {family address port} - if {$opts(ipversion) == 0 || $opts(ipversion) == [lindex $endpt 0]} { - lappend addrs [lindex $endpt 1] - } - } - } onerror {TWAPI_WIN32 11001} { - # Ignore - 11001 -> no such host, so just return empty list - } onerror {TWAPI_WIN32 11002} { - # Ignore - 11002 -> no such host, non-authoritative - } onerror {TWAPI_WIN32 11003} { - # Ignore - 11001 -> no such host, non recoverable - } onerror {TWAPI_WIN32 11004} { - # Ignore - 11004 -> no such host, though valid syntax - } - - return $addrs -} - -# Look up a port name -proc twapi::port_to_service {port} { - set name "" - trap { - set name [lindex [twapi::getnameinfo [list 0.0.0.0 $port] 2] 1] - if {[string is integer $name] && $name == $port} { - # Some platforms return the port itself if no name exists - set name "" - } - } onerror {TWAPI_WIN32 11001} { - # Ignore - 11001 -> no such host, so just return empty list - } onerror {TWAPI_WIN32 11002} { - # Ignore - 11002 -> no such host, non-authoritative - } onerror {TWAPI_WIN32 11003} { - # Ignore - 11001 -> no such host, non recoverable - } onerror {TWAPI_WIN32 11004} { - # Ignore - 11004 -> no such host, though valid syntax - } - - # If we did not get a name back, check for some well known names - # that windows does not translate. Note some of these are names - # that windows does translate in the reverse direction! - if {$name eq ""} { - foreach {p n} { - 123 ntp - 137 netbios-ns - 138 netbios-dgm - 500 isakmp - 1900 ssdp - 4500 ipsec-nat-t - } { - if {$port == $p} { - set name $n - break - } - } - } - - return $name -} - - -# Port name -> number -proc twapi::service_to_port {name} { - - # TBD - add option for specifying protocol - set protocol 0 - - if {[string is integer $name]} { - return $name - } - - if {[catch { - # Return the first port - set port [lindex [lindex [twapi::getaddrinfo "" $name $protocol] 0] 2] - }]} { - set port "" - } - return $port -} - -# Get the routing table -proc twapi::get_routing_table {args} { - array set opts [parseargs args { - sort - } -maxleftover 0] - - set routes [list ] - foreach route [twapi::GetIpForwardTable $opts(sort)] { - lappend routes [_format_route $route] - } - - return $routes -} - -# Get the best route for given destination -proc twapi::get_route {args} { - array set opts [parseargs args { - {dest.arg 0.0.0.0} - {source.arg 0.0.0.0} - } -maxleftover 0] - return [_format_route [GetBestRoute $opts(dest) $opts(source)]] -} - -# Get the interface for a destination -proc twapi::get_outgoing_interface {{dest 0.0.0.0}} { - return [GetBestInterfaceEx $dest] -} - -proc twapi::get_ipaddr_version {addr} { - set af [Twapi_IPAddressFamily $addr] - if {$af == 2} { - return 4 - } elseif {$af == 23} { - return 6 - } else { - return 0 - } -} - -################################################################ -# Utility procs - -# Convert a route as returned by C code to Tcl format route -proc twapi::_format_route {route} { - foreach fld { - addr - mask - policy - nexthop - ifindex - type - protocol - age - nexthopas - metric1 - metric2 - metric3 - metric4 - metric5 - } val $route { - set r(-$fld) $val - } - - switch -exact -- $r(-type) { - 2 { set r(-type) invalid } - 3 { set r(-type) local } - 4 { set r(-type) remote } - 1 - - default { set r(-type) other } - } - - switch -exact -- $r(-protocol) { - 2 { set r(-protocol) local } - 3 { set r(-protocol) netmgmt } - 4 { set r(-protocol) icmp } - 5 { set r(-protocol) egp } - 6 { set r(-protocol) ggp } - 7 { set r(-protocol) hello } - 8 { set r(-protocol) rip } - 9 { set r(-protocol) is_is } - 10 { set r(-protocol) es_is } - 11 { set r(-protocol) cisco } - 12 { set r(-protocol) bbn } - 13 { set r(-protocol) ospf } - 14 { set r(-protocol) bgp } - 1 - - default { set r(-protocol) other } - } - - return [array get r] -} - - -# Convert binary hardware address to string format -proc twapi::_hwaddr_binary_to_string {b {joiner -}} { - if {[binary scan $b H* str]} { - set s "" - foreach {x y} [split $str ""] { - lappend s $x$y - } - return [join $s $joiner] - } else { - error "Could not convert binary hardware address" - } -} - -# Callback for address resolution -proc twapi::_address_resolve_handler {id status hostname} { - variable _address_handler_scripts - - if {![info exists _address_handler_scripts($id)]} { - # Queue a background error - after 0 [list error "Error: No entry found for id $id in address request table"] - return - } - lassign $_address_handler_scripts($id) addr script - unset _address_handler_scripts($id) - - # Before invoking the callback, store result if available - uplevel #0 [linsert $script end $addr $status $hostname] - return -} - -# Callback for hostname resolution -proc twapi::_hostname_resolve_handler {id status addrandports} { - variable _hostname_handler_scripts - - if {![info exists _hostname_handler_scripts($id)]} { - # Queue a background error - after 0 [list error "Error: No entry found for id $id in hostname request table"] - return - } - lassign $_hostname_handler_scripts($id) ipver name script - unset _hostname_handler_scripts($id) - - set addrs {} - if {$status eq "success"} { - foreach addr $addrandports { - lassign $addr ver addr - if {$ipver == 0 || $ipver == $ver} { - lappend addrs $addr - } - } - } elseif {$addrandports == 11001 || $addrandports == 11004} { - # For compatibility with the sync version and address resolution, - # We return an success if empty list if in fact the failure was - # that no name->address mapping exists - set status success - } - - uplevel #0 [linsert $script end $name $status $addrs] - return -} - -# Return list of all TCP connections -# Uses GetExtendedTcpTable if available, else AllocateAndGetTcpExTableFromStack -# $level is passed to GetExtendedTcpTable and dtermines format of returned -# data. Level 5 (default) matches what AllocateAndGetTcpExTableFromStack -# returns. Note level 6 and higher is two orders of magnitude more expensive -# to get for IPv4 and crashes in Windows for IPv6 (silently downgraded to -# level 5 for IPv6) -twapi::proc* twapi::_get_all_tcp {sort level address_family} { - variable _tcp_buf - set _tcp_buf(ptr) NULL - set _tcp_buf(size) 0 -} { - variable _tcp_buf - - if {$address_family == 0} { - return [concat [_get_all_tcp $sort $level 2] [_get_all_tcp $sort $level 23]] - } - - if {$address_family == 23 && $level > 5} { - set level 5; # IPv6 crashes for levels > 5 - Windows bug - } - - # Get required size of buffer. This also verifies that the - # GetExtendedTcpTable API exists on this system - # TBD - modify to do this check only once and not on every call - - if {[catch {twapi::GetExtendedTcpTable $_tcp_buf(ptr) $_tcp_buf(size) $sort $address_family $level} bufsz]} { - # No workee, try AllocateAndGetTcpExTableFromStack - # Note if GetExtendedTcpTable is not present, ipv6 is not - # available - if {$address_family == 2} { - return [AllocateAndGetTcpExTableFromStack $sort 0] - } else { - return {} - } - } - - # The required buffer size might change as connections - # are added or deleted. So we sit in a loop. - # Non-0 value indicates buffer was not large enough - # For safety, we only retry 10 times - set i 0 - while {$bufsz && [incr i] <= 10} { - if {! [pointer_null? $_tcp_buf(ptr)]} { - free $_tcp_buf(ptr) - set _tcp_buf(ptr) NULL - set _tcp_buf(size) 0 - } - - set _tcp_buf(ptr) [malloc $bufsz] - set _tcp_buf(size) $bufsz - - set bufsz [GetExtendedTcpTable $_tcp_buf(ptr) $_tcp_buf(size) $sort $address_family $level] - } - - if ($bufsz) { - # Repeated attempts failed - win32_error 122 - } - - return [Twapi_FormatExtendedTcpTable $_tcp_buf(ptr) $address_family $level] -} - -# See comments for _get_all_tcp above except this is for _get_all_udp -twapi::proc* twapi::_get_all_udp {sort level address_family} { - variable _udp_buf - set _udp_buf(ptr) NULL - set _udp_buf(size) 0 -} { - variable _udp_buf - - if {$address_family == 0} { - return [concat [_get_all_udp $sort $level 2] [_get_all_udp $sort $level 23]] - } - - if {$address_family == 23 && $level > 5} { - set level 5; # IPv6 crashes for levels > 5 - Windows bug - } - - # Get required size of buffer. This also verifies that the - # GetExtendedTcpTable API exists on this system - if {[catch {twapi::GetExtendedUdpTable $_udp_buf(ptr) $_udp_buf(size) $sort $address_family $level} bufsz]} { - # No workee, try AllocateAndGetUdpExTableFromStack - if {$address_family == 2} { - return [AllocateAndGetUdpExTableFromStack $sort 0] - } else { - return {} - } - } - - # The required buffer size might change as connections - # are added or deleted. So we sit in a loop. - # Non-0 value indicates buffer was not large enough - # For safety, we only retry 10 times - set i 0 - while {$bufsz && [incr i] <= 10} { - if {! [pointer_null? $_udp_buf(ptr)]} { - free $_udp_buf(ptr) - set _udp_buf(ptr) NULL - set _udp_buf(size) 0 - } - - set _udp_buf(ptr) [malloc $bufsz] - set _udp_buf(size) $bufsz - - set bufsz [GetExtendedUdpTable $_udp_buf(ptr) $_udp_buf(size) $sort $address_family $level] - } - - if ($bufsz) { - # Repeated attempts failed - win32_error 122 - } - - return [Twapi_FormatExtendedUdpTable $_udp_buf(ptr) $address_family $level] -} - - -# valid IP address -proc twapi::_valid_ipaddr_format {ipaddr} { - return [expr {[Twapi_IPAddressFamily $ipaddr] != 0}] -} - -# Given lists of IP addresses and DNS names, returns -# a list purely of IP addresses in normalized form -proc twapi::_hosts_to_ip_addrs hosts { - set addrs [list ] - foreach host $hosts { - if {[_valid_ipaddr_format $host]} { - lappend addrs [Twapi_NormalizeIPAddress $host] - } else { - # Not IP address. Try to resolve, ignoring errors - if {![catch {resolve_hostname $host} hostaddrs]} { - foreach addr $hostaddrs { - lappend addrs [Twapi_NormalizeIPAddress $addr] - } - } - } - } - return $addrs -} - -proc twapi::_ipversion_to_af {opt} { - if {[string is integer -strict $opt]} { - incr opt 0; # Normalize ints for switch - } - switch -exact -- [string tolower $opt] { - 4 - - inet { return 2 } - 6 - - inet6 { return 23 } - 0 - - any - - all { return 0 } - } - error "Invalid IP version '$opt'" -} +# +# Copyright (c) 2004-2014, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + record IP_ADAPTER_ADDRESSES_XP { + -ipv4ifindex -adaptername -unicastaddresses -anycastaddresses + -multicastaddresses -dnsservers -dnssuffix -description + -friendlyname -physicaladdress -flags -mtu -type -operstatus + -ipv6ifindex -zoneindices -prefixes + } + + if {[min_os_version 6]} { + record IP_ADAPTER_ADDRESSES [list {*}[IP_ADAPTER_ADDRESSES_XP] -transmitspeed -receivespeed -winsaddresses -gatewayaddresses -ipv4metric -ipv6metric -luid -dhcpv4server -compartmentid -networkguid -connectiontype -tunneltype -dhcpv6server -dhcpv6clientduid -dhcpv6iaid -dnssuffixes] + } else { + record IP_ADAPTER_ADDRESSES [IP_ADAPTER_ADDRESSES_XP] + } + + record IP_ADAPTER_UNICAST_ADDRESS { + -flags -address -prefixorigin -suffixorigin -dadstate -validlifetime -preferredlifetime -leaselifetime + } + + record IP_ADAPTER_ANYCAST_ADDRESS {-flags -address} + record IP_ADAPTER_MULTICAST_ADDRESS [IP_ADAPTER_ANYCAST_ADDRESS] + record IP_ADAPTER_DNS_SERVER_ADDRESS [IP_ADAPTER_ANYCAST_ADDRESS] +} + +proc twapi::get_network_adapters {} { + # 0x20 -> SKIP_FRIENDLYNAME + # 0x0f -> SKIP_DNS_SERVER, SKIP_UNICAST/MULTICAST/ANYCAST + return [lpick [GetAdaptersAddresses 0 0x2f] [enum [IP_ADAPTER_ADDRESSES] -adaptername]] +} + +proc twapi::get_network_adapters_detail {} { + set recs {} + # We only return fields common to all platforms + set fields [IP_ADAPTER_ADDRESSES_XP] + foreach rec [GetAdaptersAddresses 0 0] { + set rec [IP_ADAPTER_ADDRESSES set $rec \ + -physicaladdress [_hwaddr_binary_to_string [IP_ADAPTER_ADDRESSES -physicaladdress $rec]] \ + -unicastaddresses [ntwine [IP_ADAPTER_UNICAST_ADDRESS] [IP_ADAPTER_ADDRESSES -unicastaddresses $rec]] \ + -multicastaddresses [ntwine [IP_ADAPTER_MULTICAST_ADDRESS] [IP_ADAPTER_ADDRESSES -multicastaddresses $rec]] \ + -anycastaddresses [ntwine [IP_ADAPTER_ANYCAST_ADDRESS] [IP_ADAPTER_ADDRESSES -anycastaddresses $rec]] \ + -dnsservers [ntwine [IP_ADAPTER_DNS_SERVER_ADDRESS] [IP_ADAPTER_ADDRESSES -dnsservers $rec]]] + + lappend recs [IP_ADAPTER_ADDRESSES select $rec $fields] + } + return [list $fields $recs] +} + +# Get the list of local IP addresses +proc twapi::get_system_ipaddrs {args} { + array set opts [parseargs args { + {ipversion.arg 0} + {types.arg unicast} + adaptername.arg + } -maxleftover 0] + + # 0x20 -> SKIP_FRIENDLYNAME + # 0x08 -> SKIP_DNS_SERVER + set flags 0x2f + if {"all" in $opts(types)} { + set flags 0x20 + } else { + if {"unicast" in $opts(types)} {incr flags -1} + if {"anycast" in $opts(types)} {incr flags -2} + if {"multicast" in $opts(types)} {incr flags -4} + } + + set addrs {} + trap { + set entries [GetAdaptersAddresses [_ipversion_to_af $opts(ipversion)] $flags] + } onerror {TWAPI_WIN32 232} { + # Not installed, so no addresses + return {} + } + + foreach entry $entries { + if {[info exists opts(adaptername)] && + [string compare -nocase [IP_ADAPTER_ADDRESSES -adaptername $entry] $opts(adaptername)]} { + continue + } + + foreach rec [IP_ADAPTER_ADDRESSES -unicastaddresses $entry] { + lappend addrs [IP_ADAPTER_UNICAST_ADDRESS -address $rec] + } + foreach rec [IP_ADAPTER_ADDRESSES -anycastaddresses $entry] { + lappend addrs [IP_ADAPTER_ANYCAST_ADDRESS -address $rec] + } + foreach rec [IP_ADAPTER_ADDRESSES -multicastaddresses $entry] { + lappend addrs [IP_ADAPTER_MULTICAST_ADDRESS -address $rec] + } + } + + return [lsort -unique $addrs] +} + +# Get network related information +proc twapi::get_network_info {args} { + # Map options into the positions in result of GetNetworkParams + array set getnetworkparams_opts { + hostname 0 + domain 1 + dnsservers 2 + dhcpscopeid 4 + routingenabled 5 + arpproxyenabled 6 + dnsenabled 7 + } + + array set opts [parseargs args \ + [concat [list all] \ + [array names getnetworkparams_opts]]] + set result [list ] + foreach opt [array names getnetworkparams_opts] { + if {!$opts(all) && !$opts($opt)} continue + if {![info exists netparams]} { + set netparams [GetNetworkParams] + } + lappend result -$opt [lindex $netparams $getnetworkparams_opts($opt)] + } + + return $result +} + + +proc twapi::get_network_adapter_info {interface args} { + array set opts [parseargs args { + all + adaptername + anycastaddresses + description + dhcpenabled + dnsservers + dnssuffix + friendlyname + ipv4ifindex + ipv6ifindex + multicastaddresses + mtu + operstatus + physicaladdress + prefixes + type + unicastaddresses + zoneindices + + {ipversion.arg 0} + } -maxleftover 0 -hyphenated] + + set ipversion [_ipversion_to_af $opts(-ipversion)] + + set flags 0 + if {! $opts(-all)} { + # If not asked for some fields, don't bother getting them + if {! $opts(-unicastaddresses)} { incr flags 0x1 } + if {! $opts(-anycastaddresses)} { incr flags 0x2 } + if {! $opts(-multicastaddresses)} { incr flags 0x4 } + if {! $opts(-dnsservers)} { incr flags 0x8 } + if {! $opts(-friendlyname)} { incr flags 0x20 } + + if {$opts(-prefixes)} { incr flags 0x10 } + } else { + incr flags 0x10; # Want prefixes also + } + + set entries [GetAdaptersAddresses $ipversion $flags] + set nameindex [enum [IP_ADAPTER_ADDRESSES] -adaptername] + set entry [lsearch -nocase -exact -inline -index $nameindex $entries $interface] + if {[llength $entry] == 0} { + error "No interface matching '$interface'." + } + + array set result [IP_ADAPTER_ADDRESSES $entry] + if {$opts(-all) || $opts(-dhcpenabled)} { + set result(-dhcpenabled) [expr {($result(-flags) & 0x4) != 0}] + } + # Note even if -all is specified, we still loop through because + # the fields of IP_ADAPTER_ADDRESSES are a superset of options + foreach opt [IP_ADAPTER_ADDRESSES] { + # Select only those fields that have an option defined + # and that option is selected + if {!([info exists opts($opt)] && ($opts(-all) || $opts($opt)))} { + unset result($opt) + } + } + if {[info exists result(-physicaladdress)]} { + set result(-physicaladdress) [_hwaddr_binary_to_string $result(-physicaladdress)] + } + if {[info exists result(-unicastaddresses)]} { + set result(-unicastaddresses) [ntwine [IP_ADAPTER_UNICAST_ADDRESS] $result(-unicastaddresses)] + } + if {[info exists result(-multicastaddresses)]} { + set result(-multicastaddresses) [ntwine [IP_ADAPTER_MULTICAST_ADDRESS] $result(-multicastaddresses)] + } + if {[info exists result(-anycastaddresses)]} { + set result(-anycastaddresses) [ntwine [IP_ADAPTER_ANYCAST_ADDRESS] $result(-anycastaddresses)] + } + if {[info exists result(-dnsservers)]} { + set result(-dnsservers) [ntwine [IP_ADAPTER_DNS_SERVER_ADDRESS] $result(-dnsservers)] + } + + return [array get result] +} + +# Get the address->h/w address table +proc twapi::get_arp_table {args} { + array set opts [parseargs args { + sort + }] + + set arps [list ] + + foreach arp [GetIpNetTable $opts(sort)] { + lassign $arp ifindex hwaddr ipaddr type + # Token for enry 0 1 2 3 4 + set type [lindex {other other invalid dynamic static} $type] + if {$type == ""} { + set type other + } + lappend arps [list $ifindex [_hwaddr_binary_to_string $hwaddr] $ipaddr $type] + } + return [list [list ifindex hwaddr ipaddr type] $arps] +} + +# Return IP address for a hw address +proc twapi::ipaddr_to_hwaddr {ipaddr {varname ""}} { + if {![Twapi_IPAddressFamily $ipaddr]} { + error "$ipaddr is not a valid IP V4 address" + } + + foreach arp [GetIpNetTable 0] { + if {[lindex $arp 3] == 2} continue; # Invalid entry type + if {[string equal $ipaddr [lindex $arp 2]]} { + set result [_hwaddr_binary_to_string [lindex $arp 1]] + break + } + } + + # If could not get from ARP table, see if it is one of our own + # Ignore errors + if {![info exists result]} { + foreach ifc [get_network_adapters] { + catch { + array set netifinfo [get_network_adapter_info $ifc -unicastaddresses -physicaladdress] + if {$netifinfo(-physicaladdress) eq ""} continue + foreach elem $netifinfo(-unicastaddresses) { + if {[dict get $elem -address] eq $ipaddr} { + set result $netifinfo(-physicaladdress) + break + } + } + } + if {[info exists result]} { + break + } + } + } + + if {[info exists result]} { + if {$varname == ""} { + return $result + } + upvar $varname var + set var $result + return 1 + } else { + if {$varname == ""} { + error "Could not map IP address $ipaddr to a hardware address" + } + return 0 + } +} + +# Return hw address for a IP address +proc twapi::hwaddr_to_ipaddr {hwaddr {varname ""}} { + set hwaddr [string map {- "" : ""} $hwaddr] + foreach arp [GetIpNetTable 0] { + if {[lindex $arp 3] == 2} continue; # Invalid entry type + if {[string equal $hwaddr [_hwaddr_binary_to_string [lindex $arp 1] ""]]} { + set result [lindex $arp 2] + break + } + } + + # If could not get from ARP table, see if it is one of our own + # Ignore errors + if {![info exists result]} { + foreach ifc [get_network_adapters] { + catch { + array set netifinfo [get_network_adapter_info $ifc -unicastaddresses -physicaladdress] + if {$netifinfo(-physicaladdress) eq ""} continue + set ifhwaddr [string map {- ""} $netifinfo(-physicaladdress)] + if {[string equal -nocase $hwaddr $ifhwaddr]} { + foreach elem $netifinfo(-unicastaddresses) { + if {[dict get $elem -address] ne ""} { + set result [dict get $elem -address] + break + } + } + } + } + if {[info exists result]} { + break + } + } + } + + if {[info exists result]} { + if {$varname == ""} { + return $result + } + upvar $varname var + set var $result + return 1 + } else { + if {$varname == ""} { + error "Could not map hardware address $hwaddr to an IP address" + } + return 0 + } +} + +# Flush the arp table for a given interface +proc twapi::flush_arp_tables {args} { + if {[llength $args] == 0} { + set args [get_network_adapters] + } + foreach arg $args { + array set ifc [get_network_adapter_info $arg -type -ipv4ifindex] + if {$ifc(-type) != 24} { + trap { + FlushIpNetTable $ifc(-ipv4ifindex) + } onerror {} { + # Ignore - flush not supported for that interface type + } + } + } +} + +# Return the list of TCP connections +twapi::proc* twapi::get_tcp_connections {args} { + variable tcp_statenames + variable tcp_statevalues + + array set tcp_statevalues { + closed 1 + listen 2 + syn_sent 3 + syn_rcvd 4 + estab 5 + fin_wait1 6 + fin_wait2 7 + close_wait 8 + closing 9 + last_ack 10 + time_wait 11 + delete_tcb 12 + } + foreach {name val} [array get tcp_statevalues] { + set tcp_statenames($val) $name + } +} { + variable tcp_statenames + variable tcp_statevalues + + array set opts [parseargs args { + state + {ipversion.arg 0} + localaddr + remoteaddr + localport + remoteport + pid + modulename + modulepath + bindtime + all + matchstate.arg + matchlocaladdr.arg + matchremoteaddr.arg + matchlocalport.int + matchremoteport.int + matchpid.int + } -maxleftover 0] + + set opts(ipversion) [_ipversion_to_af $opts(ipversion)] + + if {! ($opts(state) || $opts(localaddr) || $opts(remoteaddr) || $opts(localport) || $opts(remoteport) || $opts(pid) || $opts(modulename) || $opts(modulepath) || $opts(bindtime))} { + set opts(all) 1 + } + + # Convert state to appropriate symbol if necessary + if {[info exists opts(matchstate)]} { + set matchstates [list ] + foreach stateval $opts(matchstate) { + if {[info exists tcp_statevalues($stateval)]} { + lappend matchstates $stateval + continue + } + if {[info exists tcp_statenames($stateval)]} { + lappend matchstates $tcp_statenames($stateval) + continue + } + error "Unrecognized connection state '$stateval' specified for option -matchstate" + } + } + + foreach opt {matchlocaladdr matchremoteaddr} { + if {[info exists opts($opt)]} { + # Note this also normalizes the address format + set $opt [_hosts_to_ip_addrs $opts($opt)] + if {[llength [set $opt]] == 0} { + return [list ]; # No addresses, so no connections will match + } + } + } + + # Get the complete list of connections + if {$opts(modulename) || $opts(modulepath) || $opts(bindtime) || $opts(all)} { + set level 8 + } else { + set level 5 + } + + # See if any matching needs to be done + if {[info exists opts(matchlocaladdr)] || [info exists opts(matchlocalport)] || + [info exist opts(matchremoteaddr)] || [info exists opts(matchremoteport)] || + [info exists opts(matchpid)] || [info exists opts(matchstate)]} { + set need_matching 1 + } else { + set need_matching 0 + } + + + set conns [list ] + foreach entry [_get_all_tcp 0 $level $opts(ipversion)] { + lassign $entry state localaddr localport remoteaddr remoteport pid bindtime modulename modulepath + + if {[string equal $remoteaddr 0.0.0.0]} { + # Socket not connected. WIndows passes some random value + # for remote port in this case. Set it to 0 + set remoteport 0 + } + + if {[info exists tcp_statenames($state)]} { + set state $tcp_statenames($state) + } + if {$need_matching} { + if {[info exists opts(matchpid)]} { + # See if this platform even returns the PID + if {$pid == ""} { + error "Connection process id not available on this system." + } + if {$pid != $opts(matchpid)} { + continue + } + } + if {[info exists matchlocaladdr] && + [lsearch -exact $matchlocaladdr $localaddr] < 0} { + # Not in match list + continue + } + if {[info exists matchremoteaddr] && + [lsearch -exact $matchremoteaddr $remoteaddr] < 0} { + # Not in match list + continue + } + if {[info exists opts(matchlocalport)] && + $opts(matchlocalport) != $localport} { + continue + } + if {[info exists opts(matchremoteport)] && + $opts(matchremoteport) != $remoteport} { + continue + } + if {[info exists matchstates] && [lsearch -exact $matchstates $state] < 0} { + continue + } + } + + # OK, now we have matched. Include specified fields in the result + set conn [list ] + foreach opt {localaddr localport remoteaddr remoteport state pid bindtime modulename modulepath} { + if {$opts(all) || $opts($opt)} { + lappend conn [set $opt] + } + } + lappend conns $conn + } + + # ORDER MUST MATCH ORDER ABOVE + set fields [list ] + foreach opt {localaddr localport remoteaddr remoteport state pid bindtime modulename modulepath} { + if {$opts(all) || $opts($opt)} { + lappend fields -$opt + } + } + + return [list $fields $conns] +} + + +# Return the list of UDP connections +proc twapi::get_udp_connections {args} { + array set opts [parseargs args { + {ipversion.arg 0} + localaddr + localport + pid + modulename + modulepath + bindtime + all + matchlocaladdr.arg + matchlocalport.int + matchpid.int + } -maxleftover 0] + + set opts(ipversion) [_ipversion_to_af $opts(ipversion)] + + if {! ($opts(localaddr) || $opts(localport) || $opts(pid) || $opts(modulename) || $opts(modulepath) || $opts(bindtime))} { + set opts(all) 1 + } + + if {[info exists opts(matchlocaladdr)]} { + # Note this also normalizes the address format + set matchlocaladdr [_hosts_to_ip_addrs $opts(matchlocaladdr)] + if {[llength $matchlocaladdr] == 0} { + return [list ]; # No addresses, so no connections will match + } + } + + # Get the complete list of connections + # Get the complete list of connections + if {$opts(modulename) || $opts(modulepath) || $opts(bindtime) || $opts(all)} { + set level 2 + } else { + set level 1 + } + set conns [list ] + foreach entry [_get_all_udp 0 $level $opts(ipversion)] { + foreach {localaddr localport pid bindtime modulename modulepath} $entry { + break + } + if {[info exists opts(matchpid)]} { + # See if this platform even returns the PID + if {$pid == ""} { + error "Connection process id not available on this system." + } + if {$pid != $opts(matchpid)} { + continue + } + } + if {[info exists matchlocaladdr] && + [lsearch -exact $matchlocaladdr $localaddr] < 0} { + continue + } + if {[info exists opts(matchlocalport)] && + $opts(matchlocalport) != $localport} { + continue + } + + # OK, now we have matched. Include specified fields in the result + set conn [list ] + foreach opt {localaddr localport pid bindtime modulename modulepath} { + if {$opts(all) || $opts($opt)} { + lappend conn [set $opt] + } + } + lappend conns $conn + } + + # ORDER MUST MATCH THAT ABOVE + set fields [list ] + foreach opt {localaddr localport pid bindtime modulename modulepath} { + if {$opts(all) || $opts($opt)} { + lappend fields -$opt + } + } + + return [list $fields $conns] +} + +# Terminates a TCP connection. Does not generate an error if connection +# does not exist +proc twapi::terminate_tcp_connections {args} { + array set opts [parseargs args { + matchstate.arg + matchlocaladdr.arg + matchremoteaddr.arg + matchlocalport.int + matchremoteport.int + matchpid.int + } -maxleftover 0] + + # TBD - ignore 'no such connection' errors + + # If local and remote endpoints fully specified, just directly call + # SetTcpEntry. Note pid must NOT be specified since we must then + # fall through and check for that pid + if {[info exists opts(matchlocaladdr)] && [info exists opts(matchlocalport)] && + [info exists opts(matchremoteaddr)] && [info exists opts(matchremoteport)] && + ! [info exists opts(matchpid)]} { + # 12 is "delete" code + catch { + SetTcpEntry [list 12 $opts(matchlocaladdr) $opts(matchlocalport) $opts(matchremoteaddr) $opts(matchremoteport)] + } + return + } + + # Get connection list and go through matching on each + # TBD - optimize by precalculating if *ANY* matching is to be done + # and if not, skip the whole matching sequence + foreach conn [twapi::recordarray getlist [get_tcp_connections {*}[_get_array_as_options opts]] -format dict] { + array set aconn $conn + # TBD - should we handle integer values of opts(state) ? + if {[info exists opts(matchstate)] && + $opts(matchstate) != $aconn(-state)} { + continue + } + if {[info exists opts(matchlocaladdr)] && + $opts(matchlocaladdr) != $aconn(-localaddr)} { + continue + } + if {[info exists opts(matchlocalport)] && + $opts(matchlocalport) != $aconn(-localport)} { + continue + } + if {[info exists opts(matchremoteaddr)] && + $opts(matchremoteaddr) != $aconn(-remoteaddr)} { + continue + } + if {[info exists opts(remoteport)] && + $opts(matchremoteport) != $aconn(-remoteport)} { + continue + } + if {[info exists opts(matchpid)] && + $opts(matchpid) != $aconn(-pid)} { + continue + } + # Matching conditions fulfilled + # 12 is "delete" code + catch { + SetTcpEntry [list 12 $aconn(-localaddr) $aconn(-localport) $aconn(-remoteaddr) $aconn(-remoteport)] + } + } + return +} + +# Flush cache of host names and ports. +# Backward compatibility - no op since we no longer have a cache +proc twapi::flush_network_name_cache {} {} + +# IP addr -> hostname +proc twapi::resolve_address {addr args} { + + # flushcache is ignored (for backward compatibility only) + array set opts [parseargs args { + flushcache + async.arg + } -maxleftover 0] + + # Note as a special case, we treat 0.0.0.0 explicitly since + # win32 getnameinfo translates this to the local host name which + # is completely bogus. + if {$addr eq "0.0.0.0"} { + if {[info exists opts(async)]} { + after idle [list after 0 $opts(async) [list $addr success $addr]] + return "" + } else { + return $addr + } + } + + # If async option, we will call back our internal function which + # will update the cache and then invoke the caller's script + if {[info exists opts(async)]} { + variable _address_handler_scripts + set id [Twapi_ResolveAddressAsync $addr] + set _address_handler_scripts($id) [list $addr $opts(async)] + return "" + } + + # Synchronous + set name [lindex [twapi::getnameinfo [list $addr] 8] 0] + if {$name eq $addr} { + # Could not resolve. + set name "" + } + + return $name +} + +# host name -> IP addresses +proc twapi::resolve_hostname {name args} { + set name [string tolower $name] + + # -flushcache option ignored (for backward compat only) + array set opts [parseargs args { + flushcache + async.arg + {ipversion.arg 0} + } -maxleftover 0] + + # NOTE: we do not pass the IP version to getaddrinfo but always + # give it 0 and then filter the results based on IP version ourselves + # if necessary. This is because of some confusion over behaviour + # with various combination of flags. + + set opts(ipversion) [_ipversion_to_af $opts(ipversion)] + set flags 0 + if {[min_os_version 6]} { + # 0x100 -> AI_ALL. By default, Windows enables the AI_ADDRCONFIG + # flat which will hide IPv6 addresses if the local system does + # not have an *global* IPv6 addr configured. We don't want that + # so set AI_ALL to override it and get back all addresses. + set flags 0x100; # AI_ALL + } + + # If async option, we will call back our internal function which + # will update the cache and then invoke the caller's script + if {[info exists opts(async)]} { + variable _hostname_handler_scripts + set id [Twapi_ResolveHostnameAsync $name 0 $flags] + set _hostname_handler_scripts($id) [list $opts(ipversion) $name $opts(async)] + return "" + } + + # Resolve address synchronously + set addrs [list ] + trap { + foreach endpt [twapi::getaddrinfo $name 0 0 0 0 $flags] { + # endpt is {family address port} + if {$opts(ipversion) == 0 || $opts(ipversion) == [lindex $endpt 0]} { + lappend addrs [lindex $endpt 1] + } + } + } onerror {TWAPI_WIN32 11001} { + # Ignore - 11001 -> no such host, so just return empty list + } onerror {TWAPI_WIN32 11002} { + # Ignore - 11002 -> no such host, non-authoritative + } onerror {TWAPI_WIN32 11003} { + # Ignore - 11001 -> no such host, non recoverable + } onerror {TWAPI_WIN32 11004} { + # Ignore - 11004 -> no such host, though valid syntax + } + + return $addrs +} + +# Look up a port name +proc twapi::port_to_service {port} { + set name "" + trap { + set name [lindex [twapi::getnameinfo [list 0.0.0.0 $port] 2] 1] + if {[string is integer $name] && $name == $port} { + # Some platforms return the port itself if no name exists + set name "" + } + } onerror {TWAPI_WIN32 11001} { + # Ignore - 11001 -> no such host, so just return empty list + } onerror {TWAPI_WIN32 11002} { + # Ignore - 11002 -> no such host, non-authoritative + } onerror {TWAPI_WIN32 11003} { + # Ignore - 11001 -> no such host, non recoverable + } onerror {TWAPI_WIN32 11004} { + # Ignore - 11004 -> no such host, though valid syntax + } + + # If we did not get a name back, check for some well known names + # that windows does not translate. Note some of these are names + # that windows does translate in the reverse direction! + if {$name eq ""} { + foreach {p n} { + 123 ntp + 137 netbios-ns + 138 netbios-dgm + 500 isakmp + 1900 ssdp + 4500 ipsec-nat-t + } { + if {$port == $p} { + set name $n + break + } + } + } + + return $name +} + + +# Port name -> number +proc twapi::service_to_port {name} { + + # TBD - add option for specifying protocol + set protocol 0 + + if {[string is integer $name]} { + return $name + } + + if {[catch { + # Return the first port + set port [lindex [lindex [twapi::getaddrinfo "" $name $protocol] 0] 2] + }]} { + set port "" + } + return $port +} + +# Get the routing table +proc twapi::get_routing_table {args} { + array set opts [parseargs args { + sort + } -maxleftover 0] + + set routes [list ] + foreach route [twapi::GetIpForwardTable $opts(sort)] { + lappend routes [_format_route $route] + } + + return $routes +} + +# Get the best route for given destination +proc twapi::get_route {args} { + array set opts [parseargs args { + {dest.arg 0.0.0.0} + {source.arg 0.0.0.0} + } -maxleftover 0] + return [_format_route [GetBestRoute $opts(dest) $opts(source)]] +} + +# Get the interface for a destination +proc twapi::get_outgoing_interface {{dest 0.0.0.0}} { + return [GetBestInterfaceEx $dest] +} + +proc twapi::get_ipaddr_version {addr} { + set af [Twapi_IPAddressFamily $addr] + if {$af == 2} { + return 4 + } elseif {$af == 23} { + return 6 + } else { + return 0 + } +} + +################################################################ +# Utility procs + +# Convert a route as returned by C code to Tcl format route +proc twapi::_format_route {route} { + foreach fld { + addr + mask + policy + nexthop + ifindex + type + protocol + age + nexthopas + metric1 + metric2 + metric3 + metric4 + metric5 + } val $route { + set r(-$fld) $val + } + + switch -exact -- $r(-type) { + 2 { set r(-type) invalid } + 3 { set r(-type) local } + 4 { set r(-type) remote } + 1 - + default { set r(-type) other } + } + + switch -exact -- $r(-protocol) { + 2 { set r(-protocol) local } + 3 { set r(-protocol) netmgmt } + 4 { set r(-protocol) icmp } + 5 { set r(-protocol) egp } + 6 { set r(-protocol) ggp } + 7 { set r(-protocol) hello } + 8 { set r(-protocol) rip } + 9 { set r(-protocol) is_is } + 10 { set r(-protocol) es_is } + 11 { set r(-protocol) cisco } + 12 { set r(-protocol) bbn } + 13 { set r(-protocol) ospf } + 14 { set r(-protocol) bgp } + 1 - + default { set r(-protocol) other } + } + + return [array get r] +} + + +# Convert binary hardware address to string format +proc twapi::_hwaddr_binary_to_string {b {joiner -}} { + if {[binary scan $b H* str]} { + set s "" + foreach {x y} [split $str ""] { + lappend s $x$y + } + return [join $s $joiner] + } else { + error "Could not convert binary hardware address" + } +} + +# Callback for address resolution +proc twapi::_address_resolve_handler {id status hostname} { + variable _address_handler_scripts + + if {![info exists _address_handler_scripts($id)]} { + # Queue a background error + after 0 [list error "Error: No entry found for id $id in address request table"] + return + } + lassign $_address_handler_scripts($id) addr script + unset _address_handler_scripts($id) + + # Before invoking the callback, store result if available + uplevel #0 [linsert $script end $addr $status $hostname] + return +} + +# Callback for hostname resolution +proc twapi::_hostname_resolve_handler {id status addrandports} { + variable _hostname_handler_scripts + + if {![info exists _hostname_handler_scripts($id)]} { + # Queue a background error + after 0 [list error "Error: No entry found for id $id in hostname request table"] + return + } + lassign $_hostname_handler_scripts($id) ipver name script + unset _hostname_handler_scripts($id) + + set addrs {} + if {$status eq "success"} { + foreach addr $addrandports { + lassign $addr ver addr + if {$ipver == 0 || $ipver == $ver} { + lappend addrs $addr + } + } + } elseif {$addrandports == 11001 || $addrandports == 11004} { + # For compatibility with the sync version and address resolution, + # We return an success if empty list if in fact the failure was + # that no name->address mapping exists + set status success + } + + uplevel #0 [linsert $script end $name $status $addrs] + return +} + +# Return list of all TCP connections +# Uses GetExtendedTcpTable if available, else AllocateAndGetTcpExTableFromStack +# $level is passed to GetExtendedTcpTable and dtermines format of returned +# data. Level 5 (default) matches what AllocateAndGetTcpExTableFromStack +# returns. Note level 6 and higher is two orders of magnitude more expensive +# to get for IPv4 and crashes in Windows for IPv6 (silently downgraded to +# level 5 for IPv6) +twapi::proc* twapi::_get_all_tcp {sort level address_family} { + variable _tcp_buf + set _tcp_buf(ptr) NULL + set _tcp_buf(size) 0 +} { + variable _tcp_buf + + if {$address_family == 0} { + return [concat [_get_all_tcp $sort $level 2] [_get_all_tcp $sort $level 23]] + } + + if {$address_family == 23 && $level > 5} { + set level 5; # IPv6 crashes for levels > 5 - Windows bug + } + + # Get required size of buffer. This also verifies that the + # GetExtendedTcpTable API exists on this system + # TBD - modify to do this check only once and not on every call + + if {[catch {twapi::GetExtendedTcpTable $_tcp_buf(ptr) $_tcp_buf(size) $sort $address_family $level} bufsz]} { + # No workee, try AllocateAndGetTcpExTableFromStack + # Note if GetExtendedTcpTable is not present, ipv6 is not + # available + if {$address_family == 2} { + return [AllocateAndGetTcpExTableFromStack $sort 0] + } else { + return {} + } + } + + # The required buffer size might change as connections + # are added or deleted. So we sit in a loop. + # Non-0 value indicates buffer was not large enough + # For safety, we only retry 10 times + set i 0 + while {$bufsz && [incr i] <= 10} { + if {! [pointer_null? $_tcp_buf(ptr)]} { + free $_tcp_buf(ptr) + set _tcp_buf(ptr) NULL + set _tcp_buf(size) 0 + } + + set _tcp_buf(ptr) [malloc $bufsz] + set _tcp_buf(size) $bufsz + + set bufsz [GetExtendedTcpTable $_tcp_buf(ptr) $_tcp_buf(size) $sort $address_family $level] + } + + if ($bufsz) { + # Repeated attempts failed + win32_error 122 + } + + return [Twapi_FormatExtendedTcpTable $_tcp_buf(ptr) $address_family $level] +} + +# See comments for _get_all_tcp above except this is for _get_all_udp +twapi::proc* twapi::_get_all_udp {sort level address_family} { + variable _udp_buf + set _udp_buf(ptr) NULL + set _udp_buf(size) 0 +} { + variable _udp_buf + + if {$address_family == 0} { + return [concat [_get_all_udp $sort $level 2] [_get_all_udp $sort $level 23]] + } + + if {$address_family == 23 && $level > 5} { + set level 5; # IPv6 crashes for levels > 5 - Windows bug + } + + # Get required size of buffer. This also verifies that the + # GetExtendedTcpTable API exists on this system + if {[catch {twapi::GetExtendedUdpTable $_udp_buf(ptr) $_udp_buf(size) $sort $address_family $level} bufsz]} { + # No workee, try AllocateAndGetUdpExTableFromStack + if {$address_family == 2} { + return [AllocateAndGetUdpExTableFromStack $sort 0] + } else { + return {} + } + } + + # The required buffer size might change as connections + # are added or deleted. So we sit in a loop. + # Non-0 value indicates buffer was not large enough + # For safety, we only retry 10 times + set i 0 + while {$bufsz && [incr i] <= 10} { + if {! [pointer_null? $_udp_buf(ptr)]} { + free $_udp_buf(ptr) + set _udp_buf(ptr) NULL + set _udp_buf(size) 0 + } + + set _udp_buf(ptr) [malloc $bufsz] + set _udp_buf(size) $bufsz + + set bufsz [GetExtendedUdpTable $_udp_buf(ptr) $_udp_buf(size) $sort $address_family $level] + } + + if ($bufsz) { + # Repeated attempts failed + win32_error 122 + } + + return [Twapi_FormatExtendedUdpTable $_udp_buf(ptr) $address_family $level] +} + + +# valid IP address +proc twapi::_valid_ipaddr_format {ipaddr} { + return [expr {[Twapi_IPAddressFamily $ipaddr] != 0}] +} + +# Given lists of IP addresses and DNS names, returns +# a list purely of IP addresses in normalized form +proc twapi::_hosts_to_ip_addrs hosts { + set addrs [list ] + foreach host $hosts { + if {[_valid_ipaddr_format $host]} { + lappend addrs [Twapi_NormalizeIPAddress $host] + } else { + # Not IP address. Try to resolve, ignoring errors + if {![catch {resolve_hostname $host} hostaddrs]} { + foreach addr $hostaddrs { + lappend addrs [Twapi_NormalizeIPAddress $addr] + } + } + } + } + return $addrs +} + +proc twapi::_ipversion_to_af {opt} { + if {[string is integer -strict $opt]} { + incr opt 0; # Normalize ints for switch + } + switch -exact -- [string tolower $opt] { + 4 - + inet { return 2 } + 6 - + inet6 { return 23 } + 0 - + any - + all { return 0 } + } + error "Invalid IP version '$opt'" +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/nls.tcl b/src/vendorlib_tcl8/twapi-5.0b1/nls.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/nls.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/nls.tcl index 66b51db9..e16b9c6e 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/nls.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/nls.tcl @@ -1,467 +1,467 @@ -# -# Copyright (c) 2003-2013, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi {} - -# Compatibility alias -interp alias {} twapi::get_user_default_langid {} twapi::get_user_langid -interp alias {} twapi::get_system_default_langid {} twapi::get_system_langid - -# -# Format a number -proc twapi::format_number {number lcid args} { - - set number [_verify_number_format $number] - - set lcid [_map_default_lcid_token $lcid] - - # If no options specified, format according to the passed locale - if {[llength $args] == 0} { - return [GetNumberFormat 1 $lcid 0 $number 0 0 0 . "" 0] - } - - array set opts [parseargs args { - idigits.int - ilzero.bool - sgrouping.int - sdecimal.arg - sthousand.arg - inegnumber.int - }] - - # Check the locale for unspecified options - foreach opt {idigits ilzero sgrouping sdecimal sthousand inegnumber} { - if {![info exists opts($opt)]} { - set opts($opt) [lindex [get_locale_info $lcid -$opt] 1] - } - } - - # If number of decimals is -1, see how many decimal places - # in passed string - if {$opts(idigits) == -1} { - lassign [split $number .] whole frac - set opts(idigits) [string length $frac] - } - - # Convert Locale format for grouping to integer calue - if {![string is integer $opts(sgrouping)]} { - # Format assumed to be of the form "N;M;....;0" - set grouping 0 - foreach n [split $opts(sgrouping) {;}] { - if {$n == 0} break - set grouping [expr {$n + 10*$grouping}] - } - set opts(sgrouping) $grouping - } - - set flags 0 - if {[info exists opts(nouseroverride)] && $opts(nouseroverride)} { - setbits flags 0x80000000 - } - return [GetNumberFormat 0 $lcid $flags $number $opts(idigits) \ - $opts(ilzero) $opts(sgrouping) $opts(sdecimal) \ - $opts(sthousand) $opts(inegnumber)] -} - - -# -# Format currency -proc twapi::format_currency {number lcid args} { - - set number [_verify_number_format $number] - - # Get semi-canonical form (get rid of preceding "+" etc.) - # Also verifies number syntax - set number [expr {$number+0}]; - - set lcid [_map_default_lcid_token $lcid] - - # If no options specified, format according to the passed locale - if {[llength $args] == 0} { - return [GetCurrencyFormat 1 $lcid 0 $number 0 0 0 . "" 0 0 ""] - } - - array set opts [parseargs args { - idigits.int - ilzero.bool - sgrouping.int - sdecimal.arg - sthousand.arg - inegcurr.int - icurrency.int - scurrency.arg - }] - - # Check the locale for unspecified options - foreach opt {idigits ilzero sgrouping sdecimal sthousand inegcurr icurrency scurrency} { - if {![info exists opts($opt)]} { - set opts($opt) [lindex [get_locale_info $lcid -$opt] 1] - } - } - - # If number of decimals is -1, see how many decimal places - # in passed string - if {$opts(idigits) == -1} { - lassign [split $number .] whole frac - set opts(idigits) [string length $frac] - } - - # Convert Locale format for grouping to integer calue - if {![string is integer $opts(sgrouping)]} { - # Format assumed to be of the form "N;M;....;0" - set grouping 0 - foreach n [split $opts(sgrouping) {;}] { - if {$n == 0} break - set grouping [expr {$n + 10*$grouping}] - } - set opts(sgrouping) $grouping - } - - set flags 0 - if {[info exists opts(nouseroverride)] && $opts(nouseroverride)} { - setbits flags 0x80000000 - } - - return [GetCurrencyFormat 0 $lcid $flags $number $opts(idigits) \ - $opts(ilzero) $opts(sgrouping) $opts(sdecimal) \ - $opts(sthousand) $opts(inegcurr) \ - $opts(icurrency) $opts(scurrency)] -} - - -# -# Get various info about a locale -proc twapi::get_locale_info {lcid args} { - - set lcid [_map_default_lcid_token $lcid] - - variable locale_info_class_map - if {![info exists locale_info_class_map]} { - # TBD - ilanguage not recommended for Vista. Remove it? - array set locale_info_class_map { - ilanguage 0x00000001 - slanguage 0x00000002 - senglanguage 0x00001001 - sabbrevlangname 0x00000003 - snativelangname 0x00000004 - icountry 0x00000005 - scountry 0x00000006 - sengcountry 0x00001002 - sabbrevctryname 0x00000007 - snativectryname 0x00000008 - idefaultlanguage 0x00000009 - idefaultcountry 0x0000000A - idefaultcodepage 0x0000000B - idefaultansicodepage 0x00001004 - idefaultmaccodepage 0x00001011 - slist 0x0000000C - imeasure 0x0000000D - sdecimal 0x0000000E - sthousand 0x0000000F - sgrouping 0x00000010 - idigits 0x00000011 - ilzero 0x00000012 - inegnumber 0x00001010 - snativedigits 0x00000013 - scurrency 0x00000014 - sintlsymbol 0x00000015 - smondecimalsep 0x00000016 - smonthousandsep 0x00000017 - smongrouping 0x00000018 - icurrdigits 0x00000019 - iintlcurrdigits 0x0000001A - icurrency 0x0000001B - inegcurr 0x0000001C - sdate 0x0000001D - stime 0x0000001E - sshortdate 0x0000001F - slongdate 0x00000020 - stimeformat 0x00001003 - idate 0x00000021 - ildate 0x00000022 - itime 0x00000023 - itimemarkposn 0x00001005 - icentury 0x00000024 - itlzero 0x00000025 - idaylzero 0x00000026 - imonlzero 0x00000027 - s1159 0x00000028 - s2359 0x00000029 - icalendartype 0x00001009 - ioptionalcalendar 0x0000100B - ifirstdayofweek 0x0000100C - ifirstweekofyear 0x0000100D - sdayname1 0x0000002A - sdayname2 0x0000002B - sdayname3 0x0000002C - sdayname4 0x0000002D - sdayname5 0x0000002E - sdayname6 0x0000002F - sdayname7 0x00000030 - sabbrevdayname1 0x00000031 - sabbrevdayname2 0x00000032 - sabbrevdayname3 0x00000033 - sabbrevdayname4 0x00000034 - sabbrevdayname5 0x00000035 - sabbrevdayname6 0x00000036 - sabbrevdayname7 0x00000037 - smonthname1 0x00000038 - smonthname2 0x00000039 - smonthname3 0x0000003A - smonthname4 0x0000003B - smonthname5 0x0000003C - smonthname6 0x0000003D - smonthname7 0x0000003E - smonthname8 0x0000003F - smonthname9 0x00000040 - smonthname10 0x00000041 - smonthname11 0x00000042 - smonthname12 0x00000043 - smonthname13 0x0000100E - sabbrevmonthname1 0x00000044 - sabbrevmonthname2 0x00000045 - sabbrevmonthname3 0x00000046 - sabbrevmonthname4 0x00000047 - sabbrevmonthname5 0x00000048 - sabbrevmonthname6 0x00000049 - sabbrevmonthname7 0x0000004A - sabbrevmonthname8 0x0000004B - sabbrevmonthname9 0x0000004C - sabbrevmonthname10 0x0000004D - sabbrevmonthname11 0x0000004E - sabbrevmonthname12 0x0000004F - sabbrevmonthname13 0x0000100F - spositivesign 0x00000050 - snegativesign 0x00000051 - ipossignposn 0x00000052 - inegsignposn 0x00000053 - ipossymprecedes 0x00000054 - ipossepbyspace 0x00000055 - inegsymprecedes 0x00000056 - inegsepbyspace 0x00000057 - fontsignature 0x00000058 - siso639langname 0x00000059 - siso3166ctryname 0x0000005A - idefaultebcdiccodepage 0x00001012 - ipapersize 0x0000100A - sengcurrname 0x00001007 - snativecurrname 0x00001008 - syearmonth 0x00001006 - ssortname 0x00001013 - idigitsubstitution 0x00001014 - } - } - - # array set opts [parseargs args [array names locale_info_class_map]] - - set result [list ] - foreach opt $args { - lappend result $opt [GetLocaleInfo $lcid $locale_info_class_map([string range $opt 1 end])] - } - return $result -} - - -proc twapi::map_code_page_to_name {cp} { - set code_page_names { - 0 "System ANSI default" - 1 "System OEM default" - 37 "IBM EBCDIC - U.S./Canada" - 437 "OEM - United States" - 500 "IBM EBCDIC - International" - 708 "Arabic - ASMO 708" - 709 "Arabic - ASMO 449+, BCON V4" - 710 "Arabic - Transparent Arabic" - 720 "Arabic - Transparent ASMO" - 737 "OEM - Greek (formerly 437G)" - 775 "OEM - Baltic" - 850 "OEM - Multilingual Latin I" - 852 "OEM - Latin II" - 855 "OEM - Cyrillic (primarily Russian)" - 857 "OEM - Turkish" - 858 "OEM - Multlingual Latin I + Euro symbol" - 860 "OEM - Portuguese" - 861 "OEM - Icelandic" - 862 "OEM - Hebrew" - 863 "OEM - Canadian-French" - 864 "OEM - Arabic" - 865 "OEM - Nordic" - 866 "OEM - Russian" - 869 "OEM - Modern Greek" - 870 "IBM EBCDIC - Multilingual/ROECE (Latin-2)" - 874 "ANSI/OEM - Thai (same as 28605, ISO 8859-15)" - 875 "IBM EBCDIC - Modern Greek" - 932 "ANSI/OEM - Japanese, Shift-JIS" - 936 "ANSI/OEM - Simplified Chinese (PRC, Singapore)" - 949 "ANSI/OEM - Korean (Unified Hangeul Code)" - 950 "ANSI/OEM - Traditional Chinese (Taiwan; Hong Kong SAR, PRC)" - 1026 "IBM EBCDIC - Turkish (Latin-5)" - 1047 "IBM EBCDIC - Latin 1/Open System" - 1140 "IBM EBCDIC - U.S./Canada (037 + Euro symbol)" - 1141 "IBM EBCDIC - Germany (20273 + Euro symbol)" - 1142 "IBM EBCDIC - Denmark/Norway (20277 + Euro symbol)" - 1143 "IBM EBCDIC - Finland/Sweden (20278 + Euro symbol)" - 1144 "IBM EBCDIC - Italy (20280 + Euro symbol)" - 1145 "IBM EBCDIC - Latin America/Spain (20284 + Euro symbol)" - 1146 "IBM EBCDIC - United Kingdom (20285 + Euro symbol)" - 1147 "IBM EBCDIC - France (20297 + Euro symbol)" - 1148 "IBM EBCDIC - International (500 + Euro symbol)" - 1149 "IBM EBCDIC - Icelandic (20871 + Euro symbol)" - 1200 "Unicode UCS-2 Little-Endian (BMP of ISO 10646)" - 1201 "Unicode UCS-2 Big-Endian" - 1250 "ANSI - Central European" - 1251 "ANSI - Cyrillic" - 1252 "ANSI - Latin I" - 1253 "ANSI - Greek" - 1254 "ANSI - Turkish" - 1255 "ANSI - Hebrew" - 1256 "ANSI - Arabic" - 1257 "ANSI - Baltic" - 1258 "ANSI/OEM - Vietnamese" - 1361 "Korean (Johab)" - 10000 "MAC - Roman" - 10001 "MAC - Japanese" - 10002 "MAC - Traditional Chinese (Big5)" - 10003 "MAC - Korean" - 10004 "MAC - Arabic" - 10005 "MAC - Hebrew" - 10006 "MAC - Greek I" - 10007 "MAC - Cyrillic" - 10008 "MAC - Simplified Chinese (GB 2312)" - 10010 "MAC - Romania" - 10017 "MAC - Ukraine" - 10021 "MAC - Thai" - 10029 "MAC - Latin II" - 10079 "MAC - Icelandic" - 10081 "MAC - Turkish" - 10082 "MAC - Croatia" - 12000 "Unicode UCS-4 Little-Endian" - 12001 "Unicode UCS-4 Big-Endian" - 20000 "CNS - Taiwan" - 20001 "TCA - Taiwan" - 20002 "Eten - Taiwan" - 20003 "IBM5550 - Taiwan" - 20004 "TeleText - Taiwan" - 20005 "Wang - Taiwan" - 20105 "IA5 IRV International Alphabet No. 5 (7-bit)" - 20106 "IA5 German (7-bit)" - 20107 "IA5 Swedish (7-bit)" - 20108 "IA5 Norwegian (7-bit)" - 20127 "US-ASCII (7-bit)" - 20261 "T.61" - 20269 "ISO 6937 Non-Spacing Accent" - 20273 "IBM EBCDIC - Germany" - 20277 "IBM EBCDIC - Denmark/Norway" - 20278 "IBM EBCDIC - Finland/Sweden" - 20280 "IBM EBCDIC - Italy" - 20284 "IBM EBCDIC - Latin America/Spain" - 20285 "IBM EBCDIC - United Kingdom" - 20290 "IBM EBCDIC - Japanese Katakana Extended" - 20297 "IBM EBCDIC - France" - 20420 "IBM EBCDIC - Arabic" - 20423 "IBM EBCDIC - Greek" - 20424 "IBM EBCDIC - Hebrew" - 20833 "IBM EBCDIC - Korean Extended" - 20838 "IBM EBCDIC - Thai" - 20866 "Russian - KOI8-R" - 20871 "IBM EBCDIC - Icelandic" - 20880 "IBM EBCDIC - Cyrillic (Russian)" - 20905 "IBM EBCDIC - Turkish" - 20924 "IBM EBCDIC - Latin-1/Open System (1047 + Euro symbol)" - 20932 "JIS X 0208-1990 & 0121-1990" - 20936 "Simplified Chinese (GB2312)" - 21025 "IBM EBCDIC - Cyrillic (Serbian, Bulgarian)" - 21027 "Extended Alpha Lowercase" - 21866 "Ukrainian (KOI8-U)" - 28591 "ISO 8859-1 Latin I" - 28592 "ISO 8859-2 Central Europe" - 28593 "ISO 8859-3 Latin 3" - 28594 "ISO 8859-4 Baltic" - 28595 "ISO 8859-5 Cyrillic" - 28596 "ISO 8859-6 Arabic" - 28597 "ISO 8859-7 Greek" - 28598 "ISO 8859-8 Hebrew" - 28599 "ISO 8859-9 Latin 5" - 28605 "ISO 8859-15 Latin 9" - 29001 "Europa 3" - 38598 "ISO 8859-8 Hebrew" - 50220 "ISO 2022 Japanese with no halfwidth Katakana" - 50221 "ISO 2022 Japanese with halfwidth Katakana" - 50222 "ISO 2022 Japanese JIS X 0201-1989" - 50225 "ISO 2022 Korean" - 50227 "ISO 2022 Simplified Chinese" - 50229 "ISO 2022 Traditional Chinese" - 50930 "Japanese (Katakana) Extended" - 50931 "US/Canada and Japanese" - 50933 "Korean Extended and Korean" - 50935 "Simplified Chinese Extended and Simplified Chinese" - 50936 "Simplified Chinese" - 50937 "US/Canada and Traditional Chinese" - 50939 "Japanese (Latin) Extended and Japanese" - 51932 "EUC - Japanese" - 51936 "EUC - Simplified Chinese" - 51949 "EUC - Korean" - 51950 "EUC - Traditional Chinese" - 52936 "HZ-GB2312 Simplified Chinese" - 54936 "Windows XP: GB18030 Simplified Chinese (4 Byte)" - 57002 "ISCII Devanagari" - 57003 "ISCII Bengali" - 57004 "ISCII Tamil" - 57005 "ISCII Telugu" - 57006 "ISCII Assamese" - 57007 "ISCII Oriya" - 57008 "ISCII Kannada" - 57009 "ISCII Malayalam" - 57010 "ISCII Gujarati" - 57011 "ISCII Punjabi" - 65000 "Unicode UTF-7" - 65001 "Unicode UTF-8" - } - - # TBD - isn't there a Win32 function to do this ? - set cp [expr {0+$cp}] - if {[dict exists $code_page_names $cp]} { - return [dict get $code_page_names $cp] - } else { - return "Code page $cp" - } -} - -# -# Get the name of a language -interp alias {} twapi::map_langid_to_name {} twapi::VerLanguageName - -# -# Extract language and sublanguage values -proc twapi::extract_primary_langid {langid} { - return [expr {$langid & 0x3ff}] -} -proc twapi::extract_sublanguage_langid {langid} { - return [expr {($langid >> 10) & 0x3f}] -} - -# -# Utility functions - -proc twapi::_map_default_lcid_token {lcid} { - if {$lcid == "systemdefault"} { - return 2048 - } elseif {$lcid == "userdefault"} { - return 1024 - } - return $lcid -} - -proc twapi::_verify_number_format {n} { - set n [string trimleft $n 0] - if {[regexp {^[+-]?[[:digit:]]*(\.)?[[:digit:]]*$} $n]} { - return $n - } else { - error "Invalid numeric format. Must be of a sequence of digits with an optional decimal point and leading plus/minus sign" - } -} - - +# +# Copyright (c) 2003-2013, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi {} + +# Compatibility alias +interp alias {} twapi::get_user_default_langid {} twapi::get_user_langid +interp alias {} twapi::get_system_default_langid {} twapi::get_system_langid + +# +# Format a number +proc twapi::format_number {number lcid args} { + + set number [_verify_number_format $number] + + set lcid [_map_default_lcid_token $lcid] + + # If no options specified, format according to the passed locale + if {[llength $args] == 0} { + return [GetNumberFormat 1 $lcid 0 $number 0 0 0 . "" 0] + } + + array set opts [parseargs args { + idigits.int + ilzero.bool + sgrouping.int + sdecimal.arg + sthousand.arg + inegnumber.int + }] + + # Check the locale for unspecified options + foreach opt {idigits ilzero sgrouping sdecimal sthousand inegnumber} { + if {![info exists opts($opt)]} { + set opts($opt) [lindex [get_locale_info $lcid -$opt] 1] + } + } + + # If number of decimals is -1, see how many decimal places + # in passed string + if {$opts(idigits) == -1} { + lassign [split $number .] whole frac + set opts(idigits) [string length $frac] + } + + # Convert Locale format for grouping to integer calue + if {![string is integer $opts(sgrouping)]} { + # Format assumed to be of the form "N;M;....;0" + set grouping 0 + foreach n [split $opts(sgrouping) {;}] { + if {$n == 0} break + set grouping [expr {$n + 10*$grouping}] + } + set opts(sgrouping) $grouping + } + + set flags 0 + if {[info exists opts(nouseroverride)] && $opts(nouseroverride)} { + setbits flags 0x80000000 + } + return [GetNumberFormat 0 $lcid $flags $number $opts(idigits) \ + $opts(ilzero) $opts(sgrouping) $opts(sdecimal) \ + $opts(sthousand) $opts(inegnumber)] +} + + +# +# Format currency +proc twapi::format_currency {number lcid args} { + + set number [_verify_number_format $number] + + # Get semi-canonical form (get rid of preceding "+" etc.) + # Also verifies number syntax + set number [expr {$number+0}]; + + set lcid [_map_default_lcid_token $lcid] + + # If no options specified, format according to the passed locale + if {[llength $args] == 0} { + return [GetCurrencyFormat 1 $lcid 0 $number 0 0 0 . "" 0 0 ""] + } + + array set opts [parseargs args { + idigits.int + ilzero.bool + sgrouping.int + sdecimal.arg + sthousand.arg + inegcurr.int + icurrency.int + scurrency.arg + }] + + # Check the locale for unspecified options + foreach opt {idigits ilzero sgrouping sdecimal sthousand inegcurr icurrency scurrency} { + if {![info exists opts($opt)]} { + set opts($opt) [lindex [get_locale_info $lcid -$opt] 1] + } + } + + # If number of decimals is -1, see how many decimal places + # in passed string + if {$opts(idigits) == -1} { + lassign [split $number .] whole frac + set opts(idigits) [string length $frac] + } + + # Convert Locale format for grouping to integer calue + if {![string is integer $opts(sgrouping)]} { + # Format assumed to be of the form "N;M;....;0" + set grouping 0 + foreach n [split $opts(sgrouping) {;}] { + if {$n == 0} break + set grouping [expr {$n + 10*$grouping}] + } + set opts(sgrouping) $grouping + } + + set flags 0 + if {[info exists opts(nouseroverride)] && $opts(nouseroverride)} { + setbits flags 0x80000000 + } + + return [GetCurrencyFormat 0 $lcid $flags $number $opts(idigits) \ + $opts(ilzero) $opts(sgrouping) $opts(sdecimal) \ + $opts(sthousand) $opts(inegcurr) \ + $opts(icurrency) $opts(scurrency)] +} + + +# +# Get various info about a locale +proc twapi::get_locale_info {lcid args} { + + set lcid [_map_default_lcid_token $lcid] + + variable locale_info_class_map + if {![info exists locale_info_class_map]} { + # TBD - ilanguage not recommended for Vista. Remove it? + array set locale_info_class_map { + ilanguage 0x00000001 + slanguage 0x00000002 + senglanguage 0x00001001 + sabbrevlangname 0x00000003 + snativelangname 0x00000004 + icountry 0x00000005 + scountry 0x00000006 + sengcountry 0x00001002 + sabbrevctryname 0x00000007 + snativectryname 0x00000008 + idefaultlanguage 0x00000009 + idefaultcountry 0x0000000A + idefaultcodepage 0x0000000B + idefaultansicodepage 0x00001004 + idefaultmaccodepage 0x00001011 + slist 0x0000000C + imeasure 0x0000000D + sdecimal 0x0000000E + sthousand 0x0000000F + sgrouping 0x00000010 + idigits 0x00000011 + ilzero 0x00000012 + inegnumber 0x00001010 + snativedigits 0x00000013 + scurrency 0x00000014 + sintlsymbol 0x00000015 + smondecimalsep 0x00000016 + smonthousandsep 0x00000017 + smongrouping 0x00000018 + icurrdigits 0x00000019 + iintlcurrdigits 0x0000001A + icurrency 0x0000001B + inegcurr 0x0000001C + sdate 0x0000001D + stime 0x0000001E + sshortdate 0x0000001F + slongdate 0x00000020 + stimeformat 0x00001003 + idate 0x00000021 + ildate 0x00000022 + itime 0x00000023 + itimemarkposn 0x00001005 + icentury 0x00000024 + itlzero 0x00000025 + idaylzero 0x00000026 + imonlzero 0x00000027 + s1159 0x00000028 + s2359 0x00000029 + icalendartype 0x00001009 + ioptionalcalendar 0x0000100B + ifirstdayofweek 0x0000100C + ifirstweekofyear 0x0000100D + sdayname1 0x0000002A + sdayname2 0x0000002B + sdayname3 0x0000002C + sdayname4 0x0000002D + sdayname5 0x0000002E + sdayname6 0x0000002F + sdayname7 0x00000030 + sabbrevdayname1 0x00000031 + sabbrevdayname2 0x00000032 + sabbrevdayname3 0x00000033 + sabbrevdayname4 0x00000034 + sabbrevdayname5 0x00000035 + sabbrevdayname6 0x00000036 + sabbrevdayname7 0x00000037 + smonthname1 0x00000038 + smonthname2 0x00000039 + smonthname3 0x0000003A + smonthname4 0x0000003B + smonthname5 0x0000003C + smonthname6 0x0000003D + smonthname7 0x0000003E + smonthname8 0x0000003F + smonthname9 0x00000040 + smonthname10 0x00000041 + smonthname11 0x00000042 + smonthname12 0x00000043 + smonthname13 0x0000100E + sabbrevmonthname1 0x00000044 + sabbrevmonthname2 0x00000045 + sabbrevmonthname3 0x00000046 + sabbrevmonthname4 0x00000047 + sabbrevmonthname5 0x00000048 + sabbrevmonthname6 0x00000049 + sabbrevmonthname7 0x0000004A + sabbrevmonthname8 0x0000004B + sabbrevmonthname9 0x0000004C + sabbrevmonthname10 0x0000004D + sabbrevmonthname11 0x0000004E + sabbrevmonthname12 0x0000004F + sabbrevmonthname13 0x0000100F + spositivesign 0x00000050 + snegativesign 0x00000051 + ipossignposn 0x00000052 + inegsignposn 0x00000053 + ipossymprecedes 0x00000054 + ipossepbyspace 0x00000055 + inegsymprecedes 0x00000056 + inegsepbyspace 0x00000057 + fontsignature 0x00000058 + siso639langname 0x00000059 + siso3166ctryname 0x0000005A + idefaultebcdiccodepage 0x00001012 + ipapersize 0x0000100A + sengcurrname 0x00001007 + snativecurrname 0x00001008 + syearmonth 0x00001006 + ssortname 0x00001013 + idigitsubstitution 0x00001014 + } + } + + # array set opts [parseargs args [array names locale_info_class_map]] + + set result [list ] + foreach opt $args { + lappend result $opt [GetLocaleInfo $lcid $locale_info_class_map([string range $opt 1 end])] + } + return $result +} + + +proc twapi::map_code_page_to_name {cp} { + set code_page_names { + 0 "System ANSI default" + 1 "System OEM default" + 37 "IBM EBCDIC - U.S./Canada" + 437 "OEM - United States" + 500 "IBM EBCDIC - International" + 708 "Arabic - ASMO 708" + 709 "Arabic - ASMO 449+, BCON V4" + 710 "Arabic - Transparent Arabic" + 720 "Arabic - Transparent ASMO" + 737 "OEM - Greek (formerly 437G)" + 775 "OEM - Baltic" + 850 "OEM - Multilingual Latin I" + 852 "OEM - Latin II" + 855 "OEM - Cyrillic (primarily Russian)" + 857 "OEM - Turkish" + 858 "OEM - Multlingual Latin I + Euro symbol" + 860 "OEM - Portuguese" + 861 "OEM - Icelandic" + 862 "OEM - Hebrew" + 863 "OEM - Canadian-French" + 864 "OEM - Arabic" + 865 "OEM - Nordic" + 866 "OEM - Russian" + 869 "OEM - Modern Greek" + 870 "IBM EBCDIC - Multilingual/ROECE (Latin-2)" + 874 "ANSI/OEM - Thai (same as 28605, ISO 8859-15)" + 875 "IBM EBCDIC - Modern Greek" + 932 "ANSI/OEM - Japanese, Shift-JIS" + 936 "ANSI/OEM - Simplified Chinese (PRC, Singapore)" + 949 "ANSI/OEM - Korean (Unified Hangeul Code)" + 950 "ANSI/OEM - Traditional Chinese (Taiwan; Hong Kong SAR, PRC)" + 1026 "IBM EBCDIC - Turkish (Latin-5)" + 1047 "IBM EBCDIC - Latin 1/Open System" + 1140 "IBM EBCDIC - U.S./Canada (037 + Euro symbol)" + 1141 "IBM EBCDIC - Germany (20273 + Euro symbol)" + 1142 "IBM EBCDIC - Denmark/Norway (20277 + Euro symbol)" + 1143 "IBM EBCDIC - Finland/Sweden (20278 + Euro symbol)" + 1144 "IBM EBCDIC - Italy (20280 + Euro symbol)" + 1145 "IBM EBCDIC - Latin America/Spain (20284 + Euro symbol)" + 1146 "IBM EBCDIC - United Kingdom (20285 + Euro symbol)" + 1147 "IBM EBCDIC - France (20297 + Euro symbol)" + 1148 "IBM EBCDIC - International (500 + Euro symbol)" + 1149 "IBM EBCDIC - Icelandic (20871 + Euro symbol)" + 1200 "Unicode UCS-2 Little-Endian (BMP of ISO 10646)" + 1201 "Unicode UCS-2 Big-Endian" + 1250 "ANSI - Central European" + 1251 "ANSI - Cyrillic" + 1252 "ANSI - Latin I" + 1253 "ANSI - Greek" + 1254 "ANSI - Turkish" + 1255 "ANSI - Hebrew" + 1256 "ANSI - Arabic" + 1257 "ANSI - Baltic" + 1258 "ANSI/OEM - Vietnamese" + 1361 "Korean (Johab)" + 10000 "MAC - Roman" + 10001 "MAC - Japanese" + 10002 "MAC - Traditional Chinese (Big5)" + 10003 "MAC - Korean" + 10004 "MAC - Arabic" + 10005 "MAC - Hebrew" + 10006 "MAC - Greek I" + 10007 "MAC - Cyrillic" + 10008 "MAC - Simplified Chinese (GB 2312)" + 10010 "MAC - Romania" + 10017 "MAC - Ukraine" + 10021 "MAC - Thai" + 10029 "MAC - Latin II" + 10079 "MAC - Icelandic" + 10081 "MAC - Turkish" + 10082 "MAC - Croatia" + 12000 "Unicode UCS-4 Little-Endian" + 12001 "Unicode UCS-4 Big-Endian" + 20000 "CNS - Taiwan" + 20001 "TCA - Taiwan" + 20002 "Eten - Taiwan" + 20003 "IBM5550 - Taiwan" + 20004 "TeleText - Taiwan" + 20005 "Wang - Taiwan" + 20105 "IA5 IRV International Alphabet No. 5 (7-bit)" + 20106 "IA5 German (7-bit)" + 20107 "IA5 Swedish (7-bit)" + 20108 "IA5 Norwegian (7-bit)" + 20127 "US-ASCII (7-bit)" + 20261 "T.61" + 20269 "ISO 6937 Non-Spacing Accent" + 20273 "IBM EBCDIC - Germany" + 20277 "IBM EBCDIC - Denmark/Norway" + 20278 "IBM EBCDIC - Finland/Sweden" + 20280 "IBM EBCDIC - Italy" + 20284 "IBM EBCDIC - Latin America/Spain" + 20285 "IBM EBCDIC - United Kingdom" + 20290 "IBM EBCDIC - Japanese Katakana Extended" + 20297 "IBM EBCDIC - France" + 20420 "IBM EBCDIC - Arabic" + 20423 "IBM EBCDIC - Greek" + 20424 "IBM EBCDIC - Hebrew" + 20833 "IBM EBCDIC - Korean Extended" + 20838 "IBM EBCDIC - Thai" + 20866 "Russian - KOI8-R" + 20871 "IBM EBCDIC - Icelandic" + 20880 "IBM EBCDIC - Cyrillic (Russian)" + 20905 "IBM EBCDIC - Turkish" + 20924 "IBM EBCDIC - Latin-1/Open System (1047 + Euro symbol)" + 20932 "JIS X 0208-1990 & 0121-1990" + 20936 "Simplified Chinese (GB2312)" + 21025 "IBM EBCDIC - Cyrillic (Serbian, Bulgarian)" + 21027 "Extended Alpha Lowercase" + 21866 "Ukrainian (KOI8-U)" + 28591 "ISO 8859-1 Latin I" + 28592 "ISO 8859-2 Central Europe" + 28593 "ISO 8859-3 Latin 3" + 28594 "ISO 8859-4 Baltic" + 28595 "ISO 8859-5 Cyrillic" + 28596 "ISO 8859-6 Arabic" + 28597 "ISO 8859-7 Greek" + 28598 "ISO 8859-8 Hebrew" + 28599 "ISO 8859-9 Latin 5" + 28605 "ISO 8859-15 Latin 9" + 29001 "Europa 3" + 38598 "ISO 8859-8 Hebrew" + 50220 "ISO 2022 Japanese with no halfwidth Katakana" + 50221 "ISO 2022 Japanese with halfwidth Katakana" + 50222 "ISO 2022 Japanese JIS X 0201-1989" + 50225 "ISO 2022 Korean" + 50227 "ISO 2022 Simplified Chinese" + 50229 "ISO 2022 Traditional Chinese" + 50930 "Japanese (Katakana) Extended" + 50931 "US/Canada and Japanese" + 50933 "Korean Extended and Korean" + 50935 "Simplified Chinese Extended and Simplified Chinese" + 50936 "Simplified Chinese" + 50937 "US/Canada and Traditional Chinese" + 50939 "Japanese (Latin) Extended and Japanese" + 51932 "EUC - Japanese" + 51936 "EUC - Simplified Chinese" + 51949 "EUC - Korean" + 51950 "EUC - Traditional Chinese" + 52936 "HZ-GB2312 Simplified Chinese" + 54936 "Windows XP: GB18030 Simplified Chinese (4 Byte)" + 57002 "ISCII Devanagari" + 57003 "ISCII Bengali" + 57004 "ISCII Tamil" + 57005 "ISCII Telugu" + 57006 "ISCII Assamese" + 57007 "ISCII Oriya" + 57008 "ISCII Kannada" + 57009 "ISCII Malayalam" + 57010 "ISCII Gujarati" + 57011 "ISCII Punjabi" + 65000 "Unicode UTF-7" + 65001 "Unicode UTF-8" + } + + # TBD - isn't there a Win32 function to do this ? + set cp [expr {0+$cp}] + if {[dict exists $code_page_names $cp]} { + return [dict get $code_page_names $cp] + } else { + return "Code page $cp" + } +} + +# +# Get the name of a language +interp alias {} twapi::map_langid_to_name {} twapi::VerLanguageName + +# +# Extract language and sublanguage values +proc twapi::extract_primary_langid {langid} { + return [expr {$langid & 0x3ff}] +} +proc twapi::extract_sublanguage_langid {langid} { + return [expr {($langid >> 10) & 0x3f}] +} + +# +# Utility functions + +proc twapi::_map_default_lcid_token {lcid} { + if {$lcid == "systemdefault"} { + return 2048 + } elseif {$lcid == "userdefault"} { + return 1024 + } + return $lcid +} + +proc twapi::_verify_number_format {n} { + set n [string trimleft $n 0] + if {[regexp {^[+-]?[[:digit:]]*(\.)?[[:digit:]]*$} $n]} { + return $n + } else { + error "Invalid numeric format. Must be of a sequence of digits with an optional decimal point and leading plus/minus sign" + } +} + + diff --git a/src/vendorlib_tcl8/twapi4.7.2/os.tcl b/src/vendorlib_tcl8/twapi-5.0b1/os.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/os.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/os.tcl index 87939756..beebf5fd 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/os.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/os.tcl @@ -1,1213 +1,1213 @@ -# -# Copyright (c) 2003-2012, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi {} - -# Returns an keyed list with the following elements: -# os_major_version -# os_minor_version -# os_build_number -# platform - currently always NT -# sp_major_version -# sp_minor_version -# suites - one or more from backoffice, blade, datacenter, enterprise, -# smallbusiness, smallbusiness_restricted, terminal, personal -# system_type - workstation, server -proc twapi::get_os_info {} { - variable _osinfo - - if {[info exists _osinfo]} { - return [array get _osinfo] - } - - array set verinfo [GetVersionEx] - set _osinfo(os_major_version) $verinfo(dwMajorVersion) - set _osinfo(os_minor_version) $verinfo(dwMinorVersion) - set _osinfo(os_build_number) $verinfo(dwBuildNumber) - set _osinfo(platform) "NT" - - set _osinfo(sp_major_version) $verinfo(wServicePackMajor) - set _osinfo(sp_minor_version) $verinfo(wServicePackMinor) - - set _osinfo(suites) [list ] - set suites $verinfo(wSuiteMask) - foreach {suite def} { - backoffice 0x4 blade 0x400 communications 0x8 compute_server 0x4000 - datacenter 0x80 embeddednt 0x40 embedded_restricted 0x800 - enterprise 0x2 personal 0x200 security_appliance 0x1000 - singleuserts 0x100 smallbusiness 0x1 - smallbusiness_restricted 0x20 storage_server 0x2000 - terminal 0x10 wh_server 0x8000 - } { - if {$suites & $def} { - lappend _osinfo(suites) $suite - } - } - - set system_type $verinfo(wProductType) - if {$system_type == 1} { - set _osinfo(system_type) "workstation"; # VER_NT_WORKSTATION - } elseif {$system_type == 3} { - set _osinfo(system_type) "server"; # VER_NT_SERVER - } elseif {$system_type == 2} { - set _osinfo(system_type) "domain_controller"; # VER_NT_DOMAIN_CONTROLLER - } else { - set _osinfo(system_type) "unknown" - } - - return [array get _osinfo] -} - -# Return a text string describing the OS version and options -# If specified, osinfo should be a keyed list containing -# data returned by get_os_info -proc twapi::get_os_description {} { - - array set osinfo [get_os_info] - - # Assume not terminal server - set tserver "" - - # Version - set osversion "$osinfo(os_major_version).$osinfo(os_minor_version)" - - set systype "" - - # Base OS name - switch -exact -- $osversion { - "5.0" { - set osname "Windows 2000" - if {[string equal $osinfo(system_type) "workstation"]} { - set systype "Professional" - } else { - if {"datacenter" in $osinfo(suites)} { - set systype "Datacenter Server" - } elseif {"enterprise" in $osinfo(suites)} { - set systype "Advanced Server" - } else { - set systype "Server" - } - } - } - "5.1" { - set osname "Windows XP" - if {"personal" in $osinfo(suites)} { - set systype "Home Edition" - } else { - set systype "Professional" - } - } - "5.2" { - set osname "Windows Server 2003" - if {[GetSystemMetrics 89]} { - append osname " R2" - } - if {"datacenter" in $osinfo(suites)} { - set systype "Datacenter Edition" - } elseif {"enterprise" in $osinfo(suites)} { - set systype "Enterprise Edition" - } elseif {"blade" in $osinfo(suites)} { - set systype "Web Edition" - } else { - set systype "Standard Edition" - } - } - "6.0" { - set prodtype [GetProductInfo] - if {$osinfo(system_type) eq "workstation"} { - set osname "Windows Vista" - } else { - set osname "Windows Server 2008" - } - } - "6.1" { - set prodtype [GetProductInfo] - if {$osinfo(system_type) eq "workstation"} { - set osname "Windows 7" - } else { - set osname "Windows Server 2008 R2" - } - } - "6.2" { - if {$osinfo(system_type) eq "workstation"} { - # Win8 does not follow the systype table below - switch -exact -- [format %x [GetProductInfo]] { - 3 {set systype ""} - 6 {set systype Pro} - default {set systype Enterprise} - } - set osname "Windows 8" - } else { - set prodtype [GetProductInfo] - - set osname "Windows Server 2012" - } - - } - "6.3" { - if {$osinfo(system_type) eq "workstation"} { - # Win8.1 probably (TBD) does not follow the systype table below - switch -exact -- [format %x [GetProductInfo]] { - 3 {set systype ""} - 6 {set systype Pro} - default {set systype Enterprise} - } - set osname "Windows 8.1" - } else { - set prodtype [GetProductInfo] - set osname "Windows Server 2012 R2" - } - } - default { - # Future release - can't really name, just make something up - catch {set prodtype [GetProductInfo]} - set osname "Windows" - } - } - - if {[info exists prodtype] && $prodtype} { - catch { - set systype [dict get { - 1 "Ultimate" - 2 "Home Basic" - 3 "Home Premium" - 4 "Enterprise" - 5 "Home Basic N" - 6 "Business" - 7 "Standard" - 8 "Datacenter" - 9 "Small Business Server" - a "Enterprise Server" - b "Starter" - c "Datacenter Server Core" - d "Standard Server Core" - e "Enterprise Server Core" - f "Enterprise Server Ia64" - 10 "Business N" - 11 "Web Server" - 12 "HPC Edition" - 13 "Home Server" - 14 "Storage Server Express" - 15 "Storage Server Standard" - 16 "Storage Server Workgroup" - 17 "Storage Server Enterprise" - 18 "Essential Server Solutions" - 19 "Small Business Server Premium" - 1a "Home Premium N" - 1b "Enterprise N" - 1c "Ultimate N" - 1d "Web Server Core" - 1e "Essential Business Server Management Server" - 1f "Essential Business Server Security Server" - 20 "Essential Business Server Messaging Server" - 21 "Server Foundation" - 22 "Home Premium Server" - 23 "Essential Server Solutions without Hyper-V" - 24 "Standard without Hyper-V" - 25 "Datacenter without Hyper-V" - 26 "Enterprise without Hyper-V" - 26 "Enterprise Server V" - 27 "Datacenter Server Core without Hyper-V" - 28 "Standard Core without Hyper-V" - 29 "Enterprise Server Core without Hyper-V" - 2a "Hyper-V Server" - 2b "Storage Express Server Core" - 2c "Storage Standard Server Core" - 2d "Storage Workgroup Server Core" - 2e "Storage Enterprise Server Core" - 2f "Starter N" - 30 "Professional" - 31 "Professional N" - 32 "Small Business Server 2011 Essentials" - 33 "Server For SB Solutions" - 34 "Standard Server Solutions" - 35 "Standard Server Solutions Core" - 36 "Server For SB Solutions EM" - 37 "Server For SB Solutions EM" - 38 "Windows MultiPoint Server" - 39 "Solution Embeddedserver Core" - 3a "Professional Embedded" - 3b "Windows Essential Server Solution Management" - 3c "Windows Essential Server Solution Additional" - 3d "Windows Essential Server Solution SVC" - 3e "Windows Essential Server Solution Additional SVC" - 3f "Small Business Premium Server Core" - 40 "Hyper Core V" - 41 "Embedded" - 42 "Starter E" - 43 "Home Basic E" - 44 "Home Premium E" - 45 "Professional E" - 46 "Enterprise E" - 47 "Ultimate E" - 48 "Enterprise Evaluation" - 4c "Multipoint Standard Server" - 4d "Multipoint Premium Server" - 4f "Standard Evaluation Server" - 50 "Datacenter Evaluation" - 54 "Enterprise N Evaluation" - 55 "Embedded Automotive" - 56 "Embedded Industry A" - 57 "Thin PC" - 58 "Embedded A" - 59 "Embedded Industry" - 5a "Embedded E" - 5b "Embedded Industry E" - 5c "Embedded Industry A E" - 5f "Storage Workgroup Evaluation Server" - 60 "Storage Standard Evaluation Server" - 61 "Core Arm" - 62 "N" - 63 "China" - 64 "Single Language" - 65 "" - 67 "Professional Wmc" - 68 "Mobile Core" - 69 "Embedded Industry Eval" - 6a "Embedded Industry E Eval" - 6b "Embedded Eval" - 6c "Embedded E Eval" - 6d "Core Server" - 6e "Cloud Storage Server" - abcdabcd "unlicensed" - } [format %x $prodtype]] - } - } - - if {"terminal" in $osinfo(suites)} { - set tserver " with Terminal Services" - } - - # Service pack - if {$osinfo(sp_major_version) != 0} { - set spver " Service Pack $osinfo(sp_major_version)" - } else { - set spver "" - } - - if {$systype ne ""} { - return "$osname $systype ${osversion} (Build $osinfo(os_build_number))${spver}${tserver}" - } else { - return "$osname ${osversion} (Build $osinfo(os_build_number))${spver}${tserver}" - } -} - -proc twapi::get_processor_group_config {} { - trap { - set info [GetLogicalProcessorInformationEx 4] - if {[llength $info]} { - set maxgroupcount [lindex $info 0 1 0] - set groups {} - set num -1 - foreach group [lindex $info 0 1 1] { - lappend groups [incr num] [twine {-maxprocessorcount -activeprocessorcount -processormask} $group] - } - } - return [list -maxgroupcount $maxgroupcount -activegroups $groups] - } onerror {TWAPI_WIN32 127} { - # Just try older APIs - set processor_count [lindex [GetSystemInfo] 5] - return [list -maxgroupcount 1 -activegroups [list 0 [list -maxprocessorcount $processor_count -activeprocessorcount $processor_count -processormask [expr {(1 << $processor_count) - 1}]]]] - } - -} - -proc twapi::get_numa_config {} { - trap { - set result {} - foreach rec [GetLogicalProcessorInformationEx 1] { - lappend result [lindex $rec 1 0] [twine {-processormask -group} [lindex $rec 1 1]] - } - return $result - } onerror {TWAPI_WIN32 127} { - # Use older APIs below - } - - # If GetLogicalProcessorInformation is available, records of type "1" - # indicate NUMA information. Use it. - trap { - set result {} - foreach rec [GetLogicalProcessorInformation] { - if {[lindex $rec 1] == 1} { - lappend result [lindex $rec 2] [list -processormask [lindex $rec 0] -group 0] - } - } - return $result - } onerror {TWAPI_WIN32 127} { - # API not present, fake it - } - - return $result -} - -# Returns proc information -# $processor should be processor number or "" for "total" -proc twapi::get_processor_info {processor args} { - - if {![string is integer $processor]} { - error "Invalid processor number \"$processor\". Should be a processor identifier or the empty string to signify all processors" - } - - if {![info exists ::twapi::get_processor_info_base_opts]} { - array set ::twapi::get_processor_info_base_opts { - idletime IdleTime - privilegedtime KernelTime - usertime UserTime - dpctime DpcTime - interrupttime InterruptTime - interrupts InterruptCount - } - } - - set sysinfo_opts { - arch - processorlevel - processorrev - processorname - processormodel - processorspeed - } - - array set opts [parseargs args \ - [concat all \ - [array names ::twapi::get_processor_info_base_opts] \ - $sysinfo_opts] -maxleftover 0] - - # Registry lookup for processor description - # If no processor specified, use 0 under the assumption all processors - # are the same - set reg_hwkey "HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\[expr {$processor == "" ? 0 : $processor}]" - - set results [list ] - - set processordata [Twapi_SystemProcessorTimes] - if {$processor ne ""} { - if {[llength $processordata] <= $processor} { - error "Invalid processor number '$processor'" - } - array set times [lindex $processordata $processor] - foreach {opt field} [array get ::twapi::get_processor_info_base_opts] { - if {$opts(all) || $opts($opt)} { - lappend results -$opt $times($field) - } - } - } else { - # Need information across all processors - foreach instancedata $processordata { - foreach {opt field} [array get ::twapi::get_processor_info_base_opts] { - incr times($field) [kl_get $instancedata $field] - } - foreach {opt field} [array get ::twapi::get_processor_info_base_opts] { - if {$opts(all) || $opts($opt)} { - lappend results -$opt $times($field) - } - } - } - } - - if {$opts(all) || $opts(arch) || $opts(processorlevel) || $opts(processorrev)} { - set sysinfo [GetSystemInfo] - if {$opts(all) || $opts(arch)} { - lappend results -arch [dict* { - 0 intel - 5 arm - 6 ia64 - 9 amd64 - 10 ia32_win64 - 65535 unknown - } [lindex $sysinfo 0]] - } - - if {$opts(all) || $opts(processorlevel)} { - lappend results -processorlevel [lindex $sysinfo 8] - } - - if {$opts(all) || $opts(processorrev)} { - lappend results -processorrev [format %x [lindex $sysinfo 9]] - } - } - - if {$opts(all) || $opts(processorname)} { - if {[catch {registry get $reg_hwkey "ProcessorNameString"} val]} { - set val "unknown" - } - lappend results -processorname $val - } - - if {$opts(all) || $opts(processormodel)} { - if {[catch {registry get $reg_hwkey "Identifier"} val]} { - set val "unknown" - } - lappend results -processormodel $val - } - - if {$opts(all) || $opts(processorspeed)} { - if {[catch {registry get $reg_hwkey "~MHz"} val]} { - set val "unknown" - } - lappend results -processorspeed $val - } - - return $results -} - -# Get mask of active processors -# TBD - handle processor groups -proc twapi::get_active_processor_mask {} { - return [format 0x%x [lindex [GetSystemInfo] 4]] -} - - -# Get number of active processors -proc twapi::get_processor_count {} { - trap { - set info [GetLogicalProcessorInformationEx 4] - if {[llength $info]} { - set count 0 - foreach group [lindex $info 0 1 1] { - incr count [lindex $group 1] - } - } - return $count - } onerror {TWAPI_WIN32 127} { - # GetLogicalProcessorInformationEx call does not exist - # so system does not support processor groups - return [lindex [GetSystemInfo] 5] - } -} - -# Get system memory information -proc twapi::get_memory_info {args} { - array set opts [parseargs args { - all - allocationgranularity - availcommit - availphysical - kernelpaged - kernelnonpaged - minappaddr - maxappaddr - pagesize - peakcommit - physicalmemoryload - processavailcommit - processcommitlimit - processtotalvirtual - processavailvirtual - swapfiles - swapfiledetail - systemcache - totalcommit - totalphysical - usedcommit - } -maxleftover 0] - - - set results [list ] - set mem [GlobalMemoryStatus] - foreach {opt fld} { - physicalmemoryload dwMemoryLoad - totalphysical ullTotalPhys - availphysical ullAvailPhys - processcommitlimit ullTotalPageFile - processavailcommit ullAvailPageFile - processtotalvirtual ullTotalVirtual - processavailvirtual ullAvailVirtual - } { - if {$opts(all) || $opts($opt)} { - lappend results -$opt [kl_get $mem $fld] - } - } - - if {$opts(all) || $opts(swapfiles) || $opts(swapfiledetail)} { - set swapfiles [list ] - set swapdetail [list ] - - foreach item [Twapi_SystemPagefileInformation] { - lassign $item current_size total_used peak_used path - set path [_normalize_path $path] - lappend swapfiles $path - lappend swapdetail $path [list $current_size $total_used $peak_used] - } - if {$opts(all) || $opts(swapfiles)} { - lappend results -swapfiles $swapfiles - } - if {$opts(all) || $opts(swapfiledetail)} { - lappend results -swapfiledetail $swapdetail - } - } - - if {$opts(all) || $opts(allocationgranularity) || - $opts(minappaddr) || $opts(maxappaddr) || $opts(pagesize)} { - set sysinfo [twapi::GetSystemInfo] - foreach {opt fmt index} { - pagesize %u 1 minappaddr 0x%lx 2 maxappaddr 0x%lx 3 allocationgranularity %u 7} { - if {$opts(all) || $opts($opt)} { - lappend results -$opt [format $fmt [lindex $sysinfo $index]] - } - } - } - - # This call is slightly expensive so check if it is really needed - if {$opts(all) || $opts(totalcommit) || $opts(usedcommit) || - $opts(availcommit) || - $opts(kernelpaged) || $opts(kernelnonpaged) - } { - set mem [GetPerformanceInformation] - set page_size [kl_get $mem PageSize] - foreach {opt fld} { - totalcommit CommitLimit - usedcommit CommitTotal - peakcommit CommitPeak - systemcache SystemCache - kernelpaged KernelPaged - kernelnonpaged KernelNonpaged - } { - if {$opts(all) || $opts($opt)} { - lappend results -$opt [expr {[kl_get $mem $fld] * $page_size}] - } - } - if {$opts(all) || $opts(availcommit)} { - lappend results -availcommit [expr {$page_size * ([kl_get $mem CommitLimit]-[kl_get $mem CommitTotal])}] - } - } - - return $results -} - -# Get the netbios name -proc twapi::get_computer_netbios_name {} { - return [GetComputerName] -} - -# Get the computer name -proc twapi::get_computer_name {{typename netbios}} { - if {[string is integer $typename]} { - set type $typename - } else { - set type [lsearch -exact {netbios dnshostname dnsdomain dnsfullyqualified physicalnetbios physicaldnshostname physicaldnsdomain physicaldnsfullyqualified} $typename] - if {$type < 0} { - error "Unknown computer name type '$typename' specified" - } - } - return [GetComputerNameEx $type] -} - -# Suspend system -proc twapi::suspend_system {args} { - array set opts [parseargs args { - {state.arg standby {standby hibernate}} - force.bool - disablewakeevents.bool - } -maxleftover 0 -nulldefault] - - eval_with_privileges { - SetSuspendState [expr {$opts(state) eq "hibernate"}] $opts(force) $opts(disablewakeevents) - } SeShutdownPrivilege -} - -# Shut down the system -proc twapi::shutdown_system {args} { - array set opts [parseargs args { - system.arg - {message.arg "System shutdown has been initiated"} - {timeout.int 60} - force - restart - } -nulldefault] - - eval_with_privileges { - InitiateSystemShutdown $opts(system) $opts(message) \ - $opts(timeout) $opts(force) $opts(restart) - } SeShutdownPrivilege -} - -# Abort a system shutdown -proc twapi::abort_system_shutdown {args} { - array set opts [parseargs args {system.arg} -nulldefault] - eval_with_privileges { - AbortSystemShutdown $opts(system) - } SeShutdownPrivilege -} - -twapi::proc* twapi::get_system_uptime {} { - package require twapi_pdh - variable _system_start_time - set ctr_path [pdh_counter_path System "System Up Time"] - set uptime [pdh_counter_path_value $ctr_path -format double] - set now [clock seconds] - set _system_start_time [expr {$now - round($uptime+0.5)}] -} { - variable _system_start_time - return [expr {[clock seconds] - $_system_start_time}] -} - -proc twapi::get_system_sid {} { - set lsah [get_lsa_policy_handle -access policy_view_local_information] - trap { - return [lindex [LsaQueryInformationPolicy $lsah 5] 1] - } finally { - close_lsa_policy_handle $lsah - } -} - -# Get the primary domain controller -proc twapi::get_primary_domain_controller {args} { - array set opts [parseargs args {system.arg domain.arg} -nulldefault -maxleftover 0] - return [NetGetDCName $opts(system) $opts(domain)] -} - -# Get a domain controller for a domain -proc twapi::find_domain_controller {args} { - array set opts [parseargs args { - system.arg - avoidself.bool - domain.arg - domainguid.arg - site.arg - rediscover.bool - allowstale.bool - require.arg - prefer.arg - justldap.bool - {inputnameformat.arg any {dns flat netbios any}} - {outputnameformat.arg any {dns flat netbios any}} - {outputaddrformat.arg any {ip netbios any}} - getdetails - } -maxleftover 0 -nulldefault] - - - set flags 0 - - if {$opts(outputaddrformat) eq "ip"} { - setbits flags 0x200 - } - - # Set required bits. - foreach req $opts(require) { - if {[string is integer $req]} { - setbits flags $req - } else { - switch -exact -- $req { - directoryservice { setbits flags 0x10 } - globalcatalog { setbits flags 0x40 } - pdc { setbits flags 0x80 } - kdc { setbits flags 0x400 } - timeserver { setbits flags 0x800 } - writable { setbits flags 0x1000 } - default { - error "Invalid token '$req' specified in value for option '-require'" - } - } - } - } - - # Set preferred bits. - foreach req $opts(prefer) { - if {[string is integer $req]} { - setbits flags $req - } else { - switch -exact -- $req { - directoryservice { - # If required flag is already set, don't set this - if {! ($flags & 0x10)} { - setbits flags 0x20 - } - } - timeserver { - # If required flag is already set, don't set this - if {! ($flags & 0x800)} { - setbits flags 0x2000 - } - } - default { - error "Invalid token '$req' specified in value for option '-prefer'" - } - } - } - } - - if {$opts(rediscover)} { - setbits flags 0x1 - } else { - # Only look at this option if rediscover is not set - if {$opts(allowstale)} { - setbits flags 0x100 - } - } - - if {$opts(avoidself)} { - setbits flags 0x4000 - } - - if {$opts(justldap)} { - setbits flags 0x8000 - } - - switch -exact -- $opts(inputnameformat) { - any { } - netbios - - flat { setbits flags 0x10000 } - dns { setbits flags 0x20000 } - default { - error "Invalid value '$opts(inputnameformat)' for option '-inputnameformat'" - } - } - - switch -exact -- $opts(outputnameformat) { - any { } - netbios - - flat { setbits flags 0x80000000 } - dns { setbits flags 0x40000000 } - default { - error "Invalid value '$opts(outputnameformat)' for option '-outputnameformat'" - } - } - - array set dcinfo [DsGetDcName $opts(system) $opts(domain) $opts(domainguid) $opts(site) $flags] - - if {! $opts(getdetails)} { - return $dcinfo(DomainControllerName) - } - - set result [list \ - -dcname $dcinfo(DomainControllerName) \ - -dcaddr [string trimleft $dcinfo(DomainControllerAddress) \\] \ - -domainguid $dcinfo(DomainGuid) \ - -domain $dcinfo(DomainName) \ - -dnsforest $dcinfo(DnsForestName) \ - -dcsite $dcinfo(DcSiteName) \ - -clientsite $dcinfo(ClientSiteName) \ - ] - - - if {$dcinfo(DomainControllerAddressType) == 1} { - lappend result -dcaddrformat ip - } else { - lappend result -dcaddrformat netbios - } - - if {$dcinfo(Flags) & 0x20000000} { - lappend result -dcnameformat dns - } else { - lappend result -dcnameformat netbios - } - - if {$dcinfo(Flags) & 0x40000000} { - lappend result -domainformat dns - } else { - lappend result -domainformat netbios - } - - if {$dcinfo(Flags) & 0x80000000} { - lappend result -dnsforestformat dns - } else { - lappend result -dnsforestformat netbios - } - - set features [list ] - foreach {flag feature} { - 0x1 pdc - 0x4 globalcatalog - 0x8 ldap - 0x10 directoryservice - 0x20 kdc - 0x40 timeserver - 0x80 closest - 0x100 writable - 0x200 goodtimeserver - } { - if {$dcinfo(Flags) & $flag} { - lappend features $feature - } - } - - lappend result -features $features - - return $result -} - -# Get the primary domain info -proc twapi::get_primary_domain_info {args} { - array set opts [parseargs args { - all - name - dnsdomainname - dnsforestname - domainguid - sid - type - } -maxleftover 0] - - set result [list ] - set lsah [get_lsa_policy_handle -access policy_view_local_information] - trap { - lassign [LsaQueryInformationPolicy $lsah 12] name dnsdomainname dnsforestname domainguid sid - if {[string length $sid] == 0} { - set type workgroup - set domainguid "" - } else { - set type domain - } - foreach opt {name dnsdomainname dnsforestname domainguid sid type} { - if {$opts(all) || $opts($opt)} { - lappend result -$opt [set $opt] - } - } - } finally { - close_lsa_policy_handle $lsah - } - - return $result -} - -# Get a element from SystemParametersInfo -proc twapi::get_system_parameters_info {uiaction} { - variable SystemParametersInfo_uiactions_get - # Format of an element is - # uiaction_indexvalue uiparam binaryscanstring malloc_size modifiers - # uiparam may be an int or "sz" in which case the malloc size - # is substituted for it. - # If modifiers contains "cbsize" the first dword is initialized - # with malloc_size - if {![info exists SystemParametersInfo_uiactions_get]} { - array set SystemParametersInfo_uiactions_get { - SPI_GETDESKWALLPAPER {0x0073 2048 unicode 4096} - SPI_GETBEEP {0x0001 0 i 4} - SPI_GETMOUSE {0x0003 0 i3 12} - SPI_GETBORDER {0x0005 0 i 4} - SPI_GETKEYBOARDSPEED {0x000A 0 i 4} - SPI_ICONHORIZONTALSPACING {0x000D 0 i 4} - SPI_GETSCREENSAVETIMEOUT {0x000E 0 i 4} - SPI_GETSCREENSAVEACTIVE {0x0010 0 i 4} - SPI_GETKEYBOARDDELAY {0x0016 0 i 4} - SPI_ICONVERTICALSPACING {0x0018 0 i 4} - SPI_GETICONTITLEWRAP {0x0019 0 i 4} - SPI_GETMENUDROPALIGNMENT {0x001B 0 i 4} - SPI_GETDRAGFULLWINDOWS {0x0026 0 i 4} - SPI_GETNONCLIENTMETRICS {0x0029 sz {i6 i5 cu8 A64 i2 i5 cu8 A64 i2 i5 cu8 A64 i5 cu8 A64 i5 cu8 A64} 500 cbsize} - SPI_GETMINIMIZEDMETRICS {0x002B sz i5 20 cbsize} - SPI_GETWORKAREA {0x0030 0 i4 16} - SPI_GETKEYBOARDPREF {0x0044 0 i 4 } - SPI_GETSCREENREADER {0x0046 0 i 4} - SPI_GETANIMATION {0x0048 sz i2 8 cbsize} - SPI_GETFONTSMOOTHING {0x004A 0 i 4} - SPI_GETLOWPOWERTIMEOUT {0x004F 0 i 4} - SPI_GETPOWEROFFTIMEOUT {0x0050 0 i 4} - SPI_GETLOWPOWERACTIVE {0x0053 0 i 4} - SPI_GETPOWEROFFACTIVE {0x0054 0 i 4} - SPI_GETMOUSETRAILS {0x005E 0 i 4} - SPI_GETSCREENSAVERRUNNING {0x0072 0 i 4} - SPI_GETFILTERKEYS {0x0032 sz i6 24 cbsize} - SPI_GETTOGGLEKEYS {0x0034 sz i2 8 cbsize} - SPI_GETMOUSEKEYS {0x0036 sz i7 28 cbsize} - SPI_GETSHOWSOUNDS {0x0038 0 i 4} - SPI_GETSTICKYKEYS {0x003A sz i2 8 cbsize} - SPI_GETACCESSTIMEOUT {0x003C 12 i3 12 cbsize} - SPI_GETSNAPTODEFBUTTON {0x005F 0 i 4} - SPI_GETMOUSEHOVERWIDTH {0x0062 0 i 4} - SPI_GETMOUSEHOVERHEIGHT {0x0064 0 i 4 } - SPI_GETMOUSEHOVERTIME {0x0066 0 i 4} - SPI_GETWHEELSCROLLLINES {0x0068 0 i 4} - SPI_GETMENUSHOWDELAY {0x006A 0 i 4} - SPI_GETSHOWIMEUI {0x006E 0 i 4} - SPI_GETMOUSESPEED {0x0070 0 i 4} - SPI_GETACTIVEWINDOWTRACKING {0x1000 0 i 4} - SPI_GETMENUANIMATION {0x1002 0 i 4} - SPI_GETCOMBOBOXANIMATION {0x1004 0 i 4} - SPI_GETLISTBOXSMOOTHSCROLLING {0x1006 0 i 4} - SPI_GETGRADIENTCAPTIONS {0x1008 0 i 4} - SPI_GETKEYBOARDCUES {0x100A 0 i 4} - SPI_GETMENUUNDERLINES {0x100A 0 i 4} - SPI_GETACTIVEWNDTRKZORDER {0x100C 0 i 4} - SPI_GETHOTTRACKING {0x100E 0 i 4} - SPI_GETMENUFADE {0x1012 0 i 4} - SPI_GETSELECTIONFADE {0x1014 0 i 4} - SPI_GETTOOLTIPANIMATION {0x1016 0 i 4} - SPI_GETTOOLTIPFADE {0x1018 0 i 4} - SPI_GETCURSORSHADOW {0x101A 0 i 4} - SPI_GETMOUSESONAR {0x101C 0 i 4 } - SPI_GETMOUSECLICKLOCK {0x101E 0 i 4} - SPI_GETMOUSEVANISH {0x1020 0 i 4} - SPI_GETFLATMENU {0x1022 0 i 4} - SPI_GETDROPSHADOW {0x1024 0 i 4} - SPI_GETBLOCKSENDINPUTRESETS {0x1026 0 i 4} - SPI_GETUIEFFECTS {0x103E 0 i 4} - SPI_GETFOREGROUNDLOCKTIMEOUT {0x2000 0 i 4} - SPI_GETACTIVEWNDTRKTIMEOUT {0x2002 0 i 4} - SPI_GETFOREGROUNDFLASHCOUNT {0x2004 0 i 4} - SPI_GETCARETWIDTH {0x2006 0 i 4} - SPI_GETMOUSECLICKLOCKTIME {0x2008 0 i 4} - SPI_GETFONTSMOOTHINGTYPE {0x200A 0 i 4} - SPI_GETFONTSMOOTHINGCONTRAST {0x200C 0 i 4} - SPI_GETFOCUSBORDERWIDTH {0x200E 0 i 4} - SPI_GETFOCUSBORDERHEIGHT {0x2010 0 i 4} - } - if {$::tcl_platform(pointerSize) == 4} { - set hc_struct_size 12 - set bfmt i3 - } else { - set hc_struct_size 16 - set bfmt i4 - } - set SystemParametersInfo_uiactions_get(SPI_GETHIGHCONTRAST) [list 0x0042 sz $bfmt $hc_struct_size cbsize] - } - - set key [string toupper $uiaction] - - # TBD - - # SPI_GETSOUNDSENTRY {0x0040 } - # SPI_GETICONMETRICS {0x002D } - # SPI_GETICONTITLELOGFONT {0x001F } - # SPI_GETDEFAULTINPUTLANG {0x0059 } - # SPI_GETFONTSMOOTHINGORIENTATION {0x2012} - - if {![info exists SystemParametersInfo_uiactions_get($key)]} { - set key SPI_$key - if {![info exists SystemParametersInfo_uiactions_get($key)]} { - error "Unknown SystemParametersInfo index symbol '$uiaction'" - } - } - - lassign $SystemParametersInfo_uiactions_get($key) index uiparam fmt sz modifiers - if {$uiparam eq "sz"} { - set uiparam $sz - } - set mem [malloc $sz] - trap { - if {[lsearch -exact $modifiers cbsize] >= 0} { - # A structure that needs first field set to its size - Twapi_WriteMemory 1 $mem 0 $sz [binary format i $sz] - } - SystemParametersInfo $index $uiparam $mem 0 - if {$fmt eq "unicode"} { - return [Twapi_ReadMemory 3 $mem 0 $sz 1] - } else { - set n [binary scan [Twapi_ReadMemory 1 $mem 0 $sz] $fmt {*}[lrange {val0 val1 val2 val3 val4 val5 val6 val7 val8 val9 val10 val11 val12 val13 val14 val15 val16 val17 val17} 0 [llength $fmt]-1]] - if {$n == 1} { - return $val0 - } else { - set result {} - for {set i 0} {$i < $n} {incr i} { - lappend result {*}[set val$i] - } - return $result - } - } - } finally { - free $mem - } -} - -proc twapi::set_system_parameters_info {uiaction val args} { - variable SystemParametersInfo_uiactions_set - - # Format of an element is - # uiaction_indexvalue uiparam binaryscanstring malloc_size modifiers - # uiparam may be an int or "sz" in which case the malloc size - # is substribnuted for it. - # If modifiers contains "cbsize" the first dword is initialized - # with malloc_size - if {![info exists SystemParametersInfo_uiactions_set]} { - array set SystemParametersInfo_uiactions_set { - SPI_SETBEEP {0x0002 bool} - SPI_SETMOUSE {0x0004 unsupported} - SPI_SETBORDER {0x0006 int} - SPI_SETKEYBOARDSPEED {0x000B int} - SPI_ICONHORIZONTALSPACING {0x000D int} - SPI_SETSCREENSAVETIMEOUT {0x000F int} - SPI_SETSCREENSAVEACTIVE {0x0011 bool} - SPI_SETDESKWALLPAPER {0x0014 unsupported} - SPI_SETDESKPATTERN {0x0015 int} - SPI_SETKEYBOARDDELAY {0x0017 int} - SPI_ICONVERTICALSPACING {0x0018 int} - SPI_SETICONTITLEWRAP {0x001A bool} - SPI_SETMENUDROPALIGNMENT {0x001C bool} - SPI_SETDOUBLECLKWIDTH {0x001D int} - SPI_SETDOUBLECLKHEIGHT {0x001E int} - SPI_SETDOUBLECLICKTIME {0x0020 int} - SPI_SETMOUSEBUTTONSWAP {0x0021 bool} - SPI_SETICONTITLELOGFONT {0x0022 LOGFONT} - SPI_SETDRAGFULLWINDOWS {0x0025 bool} - SPI_SETNONCLIENTMETRICS {0x002A NONCLIENTMETRICS} - SPI_SETMINIMIZEDMETRICS {0x002C MINIMIZEDMETRICS} - SPI_SETICONMETRICS {0x002E ICONMETRICS} - SPI_SETWORKAREA {0x002F RECT} - SPI_SETPENWINDOWS {0x0031} - SPI_SETHIGHCONTRAST {0x0043 HIGHCONTRAST} - SPI_SETKEYBOARDPREF {0x0045 bool} - SPI_SETSCREENREADER {0x0047 bool} - SPI_SETANIMATION {0x0049 ANIMATIONINFO} - SPI_SETFONTSMOOTHING {0x004B bool} - SPI_SETDRAGWIDTH {0x004C int} - SPI_SETDRAGHEIGHT {0x004D int} - SPI_SETHANDHELD {0x004E} - SPI_SETLOWPOWERTIMEOUT {0x0051 int} - SPI_SETPOWEROFFTIMEOUT {0x0052 int} - SPI_SETLOWPOWERACTIVE {0x0055 bool} - SPI_SETPOWEROFFACTIVE {0x0056 bool} - SPI_SETCURSORS {0x0057 int} - SPI_SETICONS {0x0058 int} - SPI_SETDEFAULTINPUTLANG {0x005A HKL} - SPI_SETLANGTOGGLE {0x005B int} - SPI_SETMOUSETRAILS {0x005D int} - SPI_SETFILTERKEYS {0x0033 FILTERKEYS} - SPI_SETTOGGLEKEYS {0x0035 TOGGLEKEYS} - SPI_SETMOUSEKEYS {0x0037 MOUSEKEYS} - SPI_SETSHOWSOUNDS {0x0039 bool} - SPI_SETSTICKYKEYS {0x003B STICKYKEYS} - SPI_SETACCESSTIMEOUT {0x003D ACCESSTIMEOUT} - SPI_SETSERIALKEYS {0x003F SERIALKEYS} - SPI_SETSOUNDSENTRY {0x0041 SOUNDSENTRY} - SPI_SETSNAPTODEFBUTTON {0x0060 bool} - SPI_SETMOUSEHOVERWIDTH {0x0063 int} - SPI_SETMOUSEHOVERHEIGHT {0x0065 int} - SPI_SETMOUSEHOVERTIME {0x0067 int} - SPI_SETWHEELSCROLLLINES {0x0069 int} - SPI_SETMENUSHOWDELAY {0x006B int} - SPI_SETSHOWIMEUI {0x006F bool} - SPI_SETMOUSESPEED {0x0071 castint} - SPI_SETACTIVEWINDOWTRACKING {0x1001 castbool} - SPI_SETMENUANIMATION {0x1003 castbool} - SPI_SETCOMBOBOXANIMATION {0x1005 castbool} - SPI_SETLISTBOXSMOOTHSCROLLING {0x1007 castbool} - SPI_SETGRADIENTCAPTIONS {0x1009 castbool} - SPI_SETKEYBOARDCUES {0x100B castbool} - SPI_SETMENUUNDERLINES {0x100B castbool} - SPI_SETACTIVEWNDTRKZORDER {0x100D castbool} - SPI_SETHOTTRACKING {0x100F castbool} - SPI_SETMENUFADE {0x1013 castbool} - SPI_SETSELECTIONFADE {0x1015 castbool} - SPI_SETTOOLTIPANIMATION {0x1017 castbool} - SPI_SETTOOLTIPFADE {0x1019 castbool} - SPI_SETCURSORSHADOW {0x101B castbool} - SPI_SETMOUSESONAR {0x101D castbool} - SPI_SETMOUSECLICKLOCK {0x101F bool} - SPI_SETMOUSEVANISH {0x1021 castbool} - SPI_SETFLATMENU {0x1023 castbool} - SPI_SETDROPSHADOW {0x1025 castbool} - SPI_SETBLOCKSENDINPUTRESETS {0x1027 bool} - SPI_SETUIEFFECTS {0x103F castbool} - SPI_SETFOREGROUNDLOCKTIMEOUT {0x2001 castint} - SPI_SETACTIVEWNDTRKTIMEOUT {0x2003 castint} - SPI_SETFOREGROUNDFLASHCOUNT {0x2005 castint} - SPI_SETCARETWIDTH {0x2007 castint} - SPI_SETMOUSECLICKLOCKTIME {0x2009 int} - SPI_SETFONTSMOOTHINGTYPE {0x200B castint} - SPI_SETFONTSMOOTHINGCONTRAST {0x200D unsupported} - SPI_SETFOCUSBORDERWIDTH {0x200F castint} - SPI_SETFOCUSBORDERHEIGHT {0x2011 castint} - } - } - - - array set opts [parseargs args { - persist - notify - } -nulldefault] - - set flags 0 - if {$opts(persist)} { - setbits flags 1 - } - - if {$opts(notify)} { - # Note that actually the notify flag has no effect if persist - # is not set. - setbits flags 2 - } - - set key [string toupper $uiaction] - - if {![info exists SystemParametersInfo_uiactions_set($key)]} { - set key SPI_$key - if {![info exists SystemParametersInfo_uiactions_set($key)]} { - error "Unknown SystemParametersInfo index symbol '$uiaction'" - } - } - - lassign $SystemParametersInfo_uiactions_set($key) index fmt - - switch -exact -- $fmt { - int { SystemParametersInfo $index $val NULL $flags } - bool { - set val [expr {$val ? 1 : 0}] - SystemParametersInfo $index $val NULL $flags - } - castint { - # We have to pass the value as a cast pointer - SystemParametersInfo $index 0 [Twapi_AddressToPointer $val] $flags - } - castbool { - # We have to pass the value as a cast pointer - set val [expr {$val ? 1 : 0}] - SystemParametersInfo $index 0 [Twapi_AddressToPointer $val] $flags - } - default { - error "The data format for $uiaction is not currently supported" - } - } - - return -} - -namespace eval twapi { - variable _wts_session_monitors - set _wts_session_monitors [dict create] -} - -proc twapi::start_wts_session_monitor {script args} { - variable _wts_session_monitors - - parseargs args { - all - } -maxleftover 0 -setvars - set script [lrange $script 0 end]; # Verify syntactically a list - - set id "wts#[TwapiId]" - if {[dict size $_wts_session_monitors] == 0} { - # No monitoring in progress. Start it - # 0x2B1 -> WM_WTSSESSION_CHANGE - Twapi_WTSRegisterSessionNotification $all - _register_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_change_handler] 0 - } - - dict set _wts_session_monitors $id $script - return $id -} - - -proc twapi::stop_wts_session_monitor {id} { - variable _wts_session_monitors - - if {![dict exists $_wts_session_monitors $id]} { - return - } - - dict unset _wts_session_monitors $id - if {[dict size $_wts_session_monitors] == 0} { - # 0x2B1 -> WM_WTSSESSION_CHANGE - _unregister_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_handler] - Twapi_WTSUnRegisterSessionNotification - } -} - -proc twapi::_wts_session_change_handler {msg change session_id msgpos ticks} { - variable _wts_session_monitors - - if {[dict size $_wts_session_monitors] == 0} { - return; # Not an error, could have deleted while already queued - } - - dict for {id script} $_wts_session_monitors { - set code [catch {uplevel #0 [linsert $script end $change $session_id]} msg] - if {$code == 1} { - # Error - put in background but we do not abort - after 0 [list error $msg $::errorInfo $::errorCode] - } - } - return -} +# +# Copyright (c) 2003-2012, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi {} + +# Returns an keyed list with the following elements: +# os_major_version +# os_minor_version +# os_build_number +# platform - currently always NT +# sp_major_version +# sp_minor_version +# suites - one or more from backoffice, blade, datacenter, enterprise, +# smallbusiness, smallbusiness_restricted, terminal, personal +# system_type - workstation, server +proc twapi::get_os_info {} { + variable _osinfo + + if {[info exists _osinfo]} { + return [array get _osinfo] + } + + array set verinfo [GetVersionEx] + set _osinfo(os_major_version) $verinfo(dwMajorVersion) + set _osinfo(os_minor_version) $verinfo(dwMinorVersion) + set _osinfo(os_build_number) $verinfo(dwBuildNumber) + set _osinfo(platform) "NT" + + set _osinfo(sp_major_version) $verinfo(wServicePackMajor) + set _osinfo(sp_minor_version) $verinfo(wServicePackMinor) + + set _osinfo(suites) [list ] + set suites $verinfo(wSuiteMask) + foreach {suite def} { + backoffice 0x4 blade 0x400 communications 0x8 compute_server 0x4000 + datacenter 0x80 embeddednt 0x40 embedded_restricted 0x800 + enterprise 0x2 personal 0x200 security_appliance 0x1000 + singleuserts 0x100 smallbusiness 0x1 + smallbusiness_restricted 0x20 storage_server 0x2000 + terminal 0x10 wh_server 0x8000 + } { + if {$suites & $def} { + lappend _osinfo(suites) $suite + } + } + + set system_type $verinfo(wProductType) + if {$system_type == 1} { + set _osinfo(system_type) "workstation"; # VER_NT_WORKSTATION + } elseif {$system_type == 3} { + set _osinfo(system_type) "server"; # VER_NT_SERVER + } elseif {$system_type == 2} { + set _osinfo(system_type) "domain_controller"; # VER_NT_DOMAIN_CONTROLLER + } else { + set _osinfo(system_type) "unknown" + } + + return [array get _osinfo] +} + +# Return a text string describing the OS version and options +# If specified, osinfo should be a keyed list containing +# data returned by get_os_info +proc twapi::get_os_description {} { + + array set osinfo [get_os_info] + + # Assume not terminal server + set tserver "" + + # Version + set osversion "$osinfo(os_major_version).$osinfo(os_minor_version)" + + set systype "" + + # Base OS name + switch -exact -- $osversion { + "5.0" { + set osname "Windows 2000" + if {[string equal $osinfo(system_type) "workstation"]} { + set systype "Professional" + } else { + if {"datacenter" in $osinfo(suites)} { + set systype "Datacenter Server" + } elseif {"enterprise" in $osinfo(suites)} { + set systype "Advanced Server" + } else { + set systype "Server" + } + } + } + "5.1" { + set osname "Windows XP" + if {"personal" in $osinfo(suites)} { + set systype "Home Edition" + } else { + set systype "Professional" + } + } + "5.2" { + set osname "Windows Server 2003" + if {[GetSystemMetrics 89]} { + append osname " R2" + } + if {"datacenter" in $osinfo(suites)} { + set systype "Datacenter Edition" + } elseif {"enterprise" in $osinfo(suites)} { + set systype "Enterprise Edition" + } elseif {"blade" in $osinfo(suites)} { + set systype "Web Edition" + } else { + set systype "Standard Edition" + } + } + "6.0" { + set prodtype [GetProductInfo] + if {$osinfo(system_type) eq "workstation"} { + set osname "Windows Vista" + } else { + set osname "Windows Server 2008" + } + } + "6.1" { + set prodtype [GetProductInfo] + if {$osinfo(system_type) eq "workstation"} { + set osname "Windows 7" + } else { + set osname "Windows Server 2008 R2" + } + } + "6.2" { + if {$osinfo(system_type) eq "workstation"} { + # Win8 does not follow the systype table below + switch -exact -- [format %x [GetProductInfo]] { + 3 {set systype ""} + 6 {set systype Pro} + default {set systype Enterprise} + } + set osname "Windows 8" + } else { + set prodtype [GetProductInfo] + + set osname "Windows Server 2012" + } + + } + "6.3" { + if {$osinfo(system_type) eq "workstation"} { + # Win8.1 probably (TBD) does not follow the systype table below + switch -exact -- [format %x [GetProductInfo]] { + 3 {set systype ""} + 6 {set systype Pro} + default {set systype Enterprise} + } + set osname "Windows 8.1" + } else { + set prodtype [GetProductInfo] + set osname "Windows Server 2012 R2" + } + } + default { + # Future release - can't really name, just make something up + catch {set prodtype [GetProductInfo]} + set osname "Windows" + } + } + + if {[info exists prodtype] && $prodtype} { + catch { + set systype [dict get { + 1 "Ultimate" + 2 "Home Basic" + 3 "Home Premium" + 4 "Enterprise" + 5 "Home Basic N" + 6 "Business" + 7 "Standard" + 8 "Datacenter" + 9 "Small Business Server" + a "Enterprise Server" + b "Starter" + c "Datacenter Server Core" + d "Standard Server Core" + e "Enterprise Server Core" + f "Enterprise Server Ia64" + 10 "Business N" + 11 "Web Server" + 12 "HPC Edition" + 13 "Home Server" + 14 "Storage Server Express" + 15 "Storage Server Standard" + 16 "Storage Server Workgroup" + 17 "Storage Server Enterprise" + 18 "Essential Server Solutions" + 19 "Small Business Server Premium" + 1a "Home Premium N" + 1b "Enterprise N" + 1c "Ultimate N" + 1d "Web Server Core" + 1e "Essential Business Server Management Server" + 1f "Essential Business Server Security Server" + 20 "Essential Business Server Messaging Server" + 21 "Server Foundation" + 22 "Home Premium Server" + 23 "Essential Server Solutions without Hyper-V" + 24 "Standard without Hyper-V" + 25 "Datacenter without Hyper-V" + 26 "Enterprise without Hyper-V" + 26 "Enterprise Server V" + 27 "Datacenter Server Core without Hyper-V" + 28 "Standard Core without Hyper-V" + 29 "Enterprise Server Core without Hyper-V" + 2a "Hyper-V Server" + 2b "Storage Express Server Core" + 2c "Storage Standard Server Core" + 2d "Storage Workgroup Server Core" + 2e "Storage Enterprise Server Core" + 2f "Starter N" + 30 "Professional" + 31 "Professional N" + 32 "Small Business Server 2011 Essentials" + 33 "Server For SB Solutions" + 34 "Standard Server Solutions" + 35 "Standard Server Solutions Core" + 36 "Server For SB Solutions EM" + 37 "Server For SB Solutions EM" + 38 "Windows MultiPoint Server" + 39 "Solution Embeddedserver Core" + 3a "Professional Embedded" + 3b "Windows Essential Server Solution Management" + 3c "Windows Essential Server Solution Additional" + 3d "Windows Essential Server Solution SVC" + 3e "Windows Essential Server Solution Additional SVC" + 3f "Small Business Premium Server Core" + 40 "Hyper Core V" + 41 "Embedded" + 42 "Starter E" + 43 "Home Basic E" + 44 "Home Premium E" + 45 "Professional E" + 46 "Enterprise E" + 47 "Ultimate E" + 48 "Enterprise Evaluation" + 4c "Multipoint Standard Server" + 4d "Multipoint Premium Server" + 4f "Standard Evaluation Server" + 50 "Datacenter Evaluation" + 54 "Enterprise N Evaluation" + 55 "Embedded Automotive" + 56 "Embedded Industry A" + 57 "Thin PC" + 58 "Embedded A" + 59 "Embedded Industry" + 5a "Embedded E" + 5b "Embedded Industry E" + 5c "Embedded Industry A E" + 5f "Storage Workgroup Evaluation Server" + 60 "Storage Standard Evaluation Server" + 61 "Core Arm" + 62 "N" + 63 "China" + 64 "Single Language" + 65 "" + 67 "Professional Wmc" + 68 "Mobile Core" + 69 "Embedded Industry Eval" + 6a "Embedded Industry E Eval" + 6b "Embedded Eval" + 6c "Embedded E Eval" + 6d "Core Server" + 6e "Cloud Storage Server" + abcdabcd "unlicensed" + } [format %x $prodtype]] + } + } + + if {"terminal" in $osinfo(suites)} { + set tserver " with Terminal Services" + } + + # Service pack + if {$osinfo(sp_major_version) != 0} { + set spver " Service Pack $osinfo(sp_major_version)" + } else { + set spver "" + } + + if {$systype ne ""} { + return "$osname $systype ${osversion} (Build $osinfo(os_build_number))${spver}${tserver}" + } else { + return "$osname ${osversion} (Build $osinfo(os_build_number))${spver}${tserver}" + } +} + +proc twapi::get_processor_group_config {} { + trap { + set info [GetLogicalProcessorInformationEx 4] + if {[llength $info]} { + set maxgroupcount [lindex $info 0 1 0] + set groups {} + set num -1 + foreach group [lindex $info 0 1 1] { + lappend groups [incr num] [twine {-maxprocessorcount -activeprocessorcount -processormask} $group] + } + } + return [list -maxgroupcount $maxgroupcount -activegroups $groups] + } onerror {TWAPI_WIN32 127} { + # Just try older APIs + set processor_count [lindex [GetSystemInfo] 5] + return [list -maxgroupcount 1 -activegroups [list 0 [list -maxprocessorcount $processor_count -activeprocessorcount $processor_count -processormask [expr {(1 << $processor_count) - 1}]]]] + } + +} + +proc twapi::get_numa_config {} { + trap { + set result {} + foreach rec [GetLogicalProcessorInformationEx 1] { + lappend result [lindex $rec 1 0] [twine {-processormask -group} [lindex $rec 1 1]] + } + return $result + } onerror {TWAPI_WIN32 127} { + # Use older APIs below + } + + # If GetLogicalProcessorInformation is available, records of type "1" + # indicate NUMA information. Use it. + trap { + set result {} + foreach rec [GetLogicalProcessorInformation] { + if {[lindex $rec 1] == 1} { + lappend result [lindex $rec 2] [list -processormask [lindex $rec 0] -group 0] + } + } + return $result + } onerror {TWAPI_WIN32 127} { + # API not present, fake it + } + + return $result +} + +# Returns proc information +# $processor should be processor number or "" for "total" +proc twapi::get_processor_info {processor args} { + + if {![string is integer $processor]} { + error "Invalid processor number \"$processor\". Should be a processor identifier or the empty string to signify all processors" + } + + if {![info exists ::twapi::get_processor_info_base_opts]} { + array set ::twapi::get_processor_info_base_opts { + idletime IdleTime + privilegedtime KernelTime + usertime UserTime + dpctime DpcTime + interrupttime InterruptTime + interrupts InterruptCount + } + } + + set sysinfo_opts { + arch + processorlevel + processorrev + processorname + processormodel + processorspeed + } + + array set opts [parseargs args \ + [concat all \ + [array names ::twapi::get_processor_info_base_opts] \ + $sysinfo_opts] -maxleftover 0] + + # Registry lookup for processor description + # If no processor specified, use 0 under the assumption all processors + # are the same + set reg_hwkey "HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\[expr {$processor == "" ? 0 : $processor}]" + + set results [list ] + + set processordata [Twapi_SystemProcessorTimes] + if {$processor ne ""} { + if {[llength $processordata] <= $processor} { + error "Invalid processor number '$processor'" + } + array set times [lindex $processordata $processor] + foreach {opt field} [array get ::twapi::get_processor_info_base_opts] { + if {$opts(all) || $opts($opt)} { + lappend results -$opt $times($field) + } + } + } else { + # Need information across all processors + foreach instancedata $processordata { + foreach {opt field} [array get ::twapi::get_processor_info_base_opts] { + incr times($field) [kl_get $instancedata $field] + } + foreach {opt field} [array get ::twapi::get_processor_info_base_opts] { + if {$opts(all) || $opts($opt)} { + lappend results -$opt $times($field) + } + } + } + } + + if {$opts(all) || $opts(arch) || $opts(processorlevel) || $opts(processorrev)} { + set sysinfo [GetSystemInfo] + if {$opts(all) || $opts(arch)} { + lappend results -arch [dict* { + 0 intel + 5 arm + 6 ia64 + 9 amd64 + 10 ia32_win64 + 65535 unknown + } [lindex $sysinfo 0]] + } + + if {$opts(all) || $opts(processorlevel)} { + lappend results -processorlevel [lindex $sysinfo 8] + } + + if {$opts(all) || $opts(processorrev)} { + lappend results -processorrev [format %x [lindex $sysinfo 9]] + } + } + + if {$opts(all) || $opts(processorname)} { + if {[catch {registry get $reg_hwkey "ProcessorNameString"} val]} { + set val "unknown" + } + lappend results -processorname $val + } + + if {$opts(all) || $opts(processormodel)} { + if {[catch {registry get $reg_hwkey "Identifier"} val]} { + set val "unknown" + } + lappend results -processormodel $val + } + + if {$opts(all) || $opts(processorspeed)} { + if {[catch {registry get $reg_hwkey "~MHz"} val]} { + set val "unknown" + } + lappend results -processorspeed $val + } + + return $results +} + +# Get mask of active processors +# TBD - handle processor groups +proc twapi::get_active_processor_mask {} { + return [format 0x%x [lindex [GetSystemInfo] 4]] +} + + +# Get number of active processors +proc twapi::get_processor_count {} { + trap { + set info [GetLogicalProcessorInformationEx 4] + if {[llength $info]} { + set count 0 + foreach group [lindex $info 0 1 1] { + incr count [lindex $group 1] + } + } + return $count + } onerror {TWAPI_WIN32 127} { + # GetLogicalProcessorInformationEx call does not exist + # so system does not support processor groups + return [lindex [GetSystemInfo] 5] + } +} + +# Get system memory information +proc twapi::get_memory_info {args} { + array set opts [parseargs args { + all + allocationgranularity + availcommit + availphysical + kernelpaged + kernelnonpaged + minappaddr + maxappaddr + pagesize + peakcommit + physicalmemoryload + processavailcommit + processcommitlimit + processtotalvirtual + processavailvirtual + swapfiles + swapfiledetail + systemcache + totalcommit + totalphysical + usedcommit + } -maxleftover 0] + + + set results [list ] + set mem [GlobalMemoryStatus] + foreach {opt fld} { + physicalmemoryload dwMemoryLoad + totalphysical ullTotalPhys + availphysical ullAvailPhys + processcommitlimit ullTotalPageFile + processavailcommit ullAvailPageFile + processtotalvirtual ullTotalVirtual + processavailvirtual ullAvailVirtual + } { + if {$opts(all) || $opts($opt)} { + lappend results -$opt [kl_get $mem $fld] + } + } + + if {$opts(all) || $opts(swapfiles) || $opts(swapfiledetail)} { + set swapfiles [list ] + set swapdetail [list ] + + foreach item [Twapi_SystemPagefileInformation] { + lassign $item current_size total_used peak_used path + set path [_normalize_path $path] + lappend swapfiles $path + lappend swapdetail $path [list $current_size $total_used $peak_used] + } + if {$opts(all) || $opts(swapfiles)} { + lappend results -swapfiles $swapfiles + } + if {$opts(all) || $opts(swapfiledetail)} { + lappend results -swapfiledetail $swapdetail + } + } + + if {$opts(all) || $opts(allocationgranularity) || + $opts(minappaddr) || $opts(maxappaddr) || $opts(pagesize)} { + set sysinfo [twapi::GetSystemInfo] + foreach {opt fmt index} { + pagesize %u 1 minappaddr 0x%lx 2 maxappaddr 0x%lx 3 allocationgranularity %u 7} { + if {$opts(all) || $opts($opt)} { + lappend results -$opt [format $fmt [lindex $sysinfo $index]] + } + } + } + + # This call is slightly expensive so check if it is really needed + if {$opts(all) || $opts(totalcommit) || $opts(usedcommit) || + $opts(availcommit) || + $opts(kernelpaged) || $opts(kernelnonpaged) + } { + set mem [GetPerformanceInformation] + set page_size [kl_get $mem PageSize] + foreach {opt fld} { + totalcommit CommitLimit + usedcommit CommitTotal + peakcommit CommitPeak + systemcache SystemCache + kernelpaged KernelPaged + kernelnonpaged KernelNonpaged + } { + if {$opts(all) || $opts($opt)} { + lappend results -$opt [expr {[kl_get $mem $fld] * $page_size}] + } + } + if {$opts(all) || $opts(availcommit)} { + lappend results -availcommit [expr {$page_size * ([kl_get $mem CommitLimit]-[kl_get $mem CommitTotal])}] + } + } + + return $results +} + +# Get the netbios name +proc twapi::get_computer_netbios_name {} { + return [GetComputerName] +} + +# Get the computer name +proc twapi::get_computer_name {{typename netbios}} { + if {[string is integer $typename]} { + set type $typename + } else { + set type [lsearch -exact {netbios dnshostname dnsdomain dnsfullyqualified physicalnetbios physicaldnshostname physicaldnsdomain physicaldnsfullyqualified} $typename] + if {$type < 0} { + error "Unknown computer name type '$typename' specified" + } + } + return [GetComputerNameEx $type] +} + +# Suspend system +proc twapi::suspend_system {args} { + array set opts [parseargs args { + {state.arg standby {standby hibernate}} + force.bool + disablewakeevents.bool + } -maxleftover 0 -nulldefault] + + eval_with_privileges { + SetSuspendState [expr {$opts(state) eq "hibernate"}] $opts(force) $opts(disablewakeevents) + } SeShutdownPrivilege +} + +# Shut down the system +proc twapi::shutdown_system {args} { + array set opts [parseargs args { + system.arg + {message.arg "System shutdown has been initiated"} + {timeout.int 60} + force + restart + } -nulldefault] + + eval_with_privileges { + InitiateSystemShutdown $opts(system) $opts(message) \ + $opts(timeout) $opts(force) $opts(restart) + } SeShutdownPrivilege +} + +# Abort a system shutdown +proc twapi::abort_system_shutdown {args} { + array set opts [parseargs args {system.arg} -nulldefault] + eval_with_privileges { + AbortSystemShutdown $opts(system) + } SeShutdownPrivilege +} + +twapi::proc* twapi::get_system_uptime {} { + package require twapi_pdh + variable _system_start_time + set ctr_path [pdh_counter_path System "System Up Time"] + set uptime [pdh_counter_path_value $ctr_path -format double] + set now [clock seconds] + set _system_start_time [expr {$now - round($uptime+0.5)}] +} { + variable _system_start_time + return [expr {[clock seconds] - $_system_start_time}] +} + +proc twapi::get_system_sid {} { + set lsah [get_lsa_policy_handle -access policy_view_local_information] + trap { + return [lindex [LsaQueryInformationPolicy $lsah 5] 1] + } finally { + close_lsa_policy_handle $lsah + } +} + +# Get the primary domain controller +proc twapi::get_primary_domain_controller {args} { + array set opts [parseargs args {system.arg domain.arg} -nulldefault -maxleftover 0] + return [NetGetDCName $opts(system) $opts(domain)] +} + +# Get a domain controller for a domain +proc twapi::find_domain_controller {args} { + array set opts [parseargs args { + system.arg + avoidself.bool + domain.arg + domainguid.arg + site.arg + rediscover.bool + allowstale.bool + require.arg + prefer.arg + justldap.bool + {inputnameformat.arg any {dns flat netbios any}} + {outputnameformat.arg any {dns flat netbios any}} + {outputaddrformat.arg any {ip netbios any}} + getdetails + } -maxleftover 0 -nulldefault] + + + set flags 0 + + if {$opts(outputaddrformat) eq "ip"} { + setbits flags 0x200 + } + + # Set required bits. + foreach req $opts(require) { + if {[string is integer $req]} { + setbits flags $req + } else { + switch -exact -- $req { + directoryservice { setbits flags 0x10 } + globalcatalog { setbits flags 0x40 } + pdc { setbits flags 0x80 } + kdc { setbits flags 0x400 } + timeserver { setbits flags 0x800 } + writable { setbits flags 0x1000 } + default { + error "Invalid token '$req' specified in value for option '-require'" + } + } + } + } + + # Set preferred bits. + foreach req $opts(prefer) { + if {[string is integer -strict $req]} { + setbits flags $req + } else { + switch -exact -- $req { + directoryservice { + # If required flag is already set, don't set this + if {! ($flags & 0x10)} { + setbits flags 0x20 + } + } + timeserver { + # If required flag is already set, don't set this + if {! ($flags & 0x800)} { + setbits flags 0x2000 + } + } + default { + error "Invalid token '$req' specified in value for option '-prefer'" + } + } + } + } + + if {$opts(rediscover)} { + setbits flags 0x1 + } else { + # Only look at this option if rediscover is not set + if {$opts(allowstale)} { + setbits flags 0x100 + } + } + + if {$opts(avoidself)} { + setbits flags 0x4000 + } + + if {$opts(justldap)} { + setbits flags 0x8000 + } + + switch -exact -- $opts(inputnameformat) { + any { } + netbios - + flat { setbits flags 0x10000 } + dns { setbits flags 0x20000 } + default { + error "Invalid value '$opts(inputnameformat)' for option '-inputnameformat'" + } + } + + switch -exact -- $opts(outputnameformat) { + any { } + netbios - + flat { setbits flags 0x80000000 } + dns { setbits flags 0x40000000 } + default { + error "Invalid value '$opts(outputnameformat)' for option '-outputnameformat'" + } + } + + array set dcinfo [DsGetDcName $opts(system) $opts(domain) $opts(domainguid) $opts(site) $flags] + + if {! $opts(getdetails)} { + return $dcinfo(DomainControllerName) + } + + set result [list \ + -dcname $dcinfo(DomainControllerName) \ + -dcaddr [string trimleft $dcinfo(DomainControllerAddress) \\] \ + -domainguid $dcinfo(DomainGuid) \ + -domain $dcinfo(DomainName) \ + -dnsforest $dcinfo(DnsForestName) \ + -dcsite $dcinfo(DcSiteName) \ + -clientsite $dcinfo(ClientSiteName) \ + ] + + + if {$dcinfo(DomainControllerAddressType) == 1} { + lappend result -dcaddrformat ip + } else { + lappend result -dcaddrformat netbios + } + + if {$dcinfo(Flags) & 0x20000000} { + lappend result -dcnameformat dns + } else { + lappend result -dcnameformat netbios + } + + if {$dcinfo(Flags) & 0x40000000} { + lappend result -domainformat dns + } else { + lappend result -domainformat netbios + } + + if {$dcinfo(Flags) & 0x80000000} { + lappend result -dnsforestformat dns + } else { + lappend result -dnsforestformat netbios + } + + set features [list ] + foreach {flag feature} { + 0x1 pdc + 0x4 globalcatalog + 0x8 ldap + 0x10 directoryservice + 0x20 kdc + 0x40 timeserver + 0x80 closest + 0x100 writable + 0x200 goodtimeserver + } { + if {$dcinfo(Flags) & $flag} { + lappend features $feature + } + } + + lappend result -features $features + + return $result +} + +# Get the primary domain info +proc twapi::get_primary_domain_info {args} { + array set opts [parseargs args { + all + name + dnsdomainname + dnsforestname + domainguid + sid + type + } -maxleftover 0] + + set result [list ] + set lsah [get_lsa_policy_handle -access policy_view_local_information] + trap { + lassign [LsaQueryInformationPolicy $lsah 12] name dnsdomainname dnsforestname domainguid sid + if {[string length $sid] == 0} { + set type workgroup + set domainguid "" + } else { + set type domain + } + foreach opt {name dnsdomainname dnsforestname domainguid sid type} { + if {$opts(all) || $opts($opt)} { + lappend result -$opt [set $opt] + } + } + } finally { + close_lsa_policy_handle $lsah + } + + return $result +} + +# Get a element from SystemParametersInfo +proc twapi::get_system_parameters_info {uiaction} { + variable SystemParametersInfo_uiactions_get + # Format of an element is + # uiaction_indexvalue uiparam binaryscanstring malloc_size modifiers + # uiparam may be an int or "sz" in which case the malloc size + # is substituted for it. + # If modifiers contains "cbsize" the first dword is initialized + # with malloc_size + if {![info exists SystemParametersInfo_uiactions_get]} { + array set SystemParametersInfo_uiactions_get { + SPI_GETDESKWALLPAPER {0x0073 2048 unicode 4096} + SPI_GETBEEP {0x0001 0 i 4} + SPI_GETMOUSE {0x0003 0 i3 12} + SPI_GETBORDER {0x0005 0 i 4} + SPI_GETKEYBOARDSPEED {0x000A 0 i 4} + SPI_ICONHORIZONTALSPACING {0x000D 0 i 4} + SPI_GETSCREENSAVETIMEOUT {0x000E 0 i 4} + SPI_GETSCREENSAVEACTIVE {0x0010 0 i 4} + SPI_GETKEYBOARDDELAY {0x0016 0 i 4} + SPI_ICONVERTICALSPACING {0x0018 0 i 4} + SPI_GETICONTITLEWRAP {0x0019 0 i 4} + SPI_GETMENUDROPALIGNMENT {0x001B 0 i 4} + SPI_GETDRAGFULLWINDOWS {0x0026 0 i 4} + SPI_GETNONCLIENTMETRICS {0x0029 sz {i6 i5 cu8 A64 i2 i5 cu8 A64 i2 i5 cu8 A64 i5 cu8 A64 i5 cu8 A64} 500 cbsize} + SPI_GETMINIMIZEDMETRICS {0x002B sz i5 20 cbsize} + SPI_GETWORKAREA {0x0030 0 i4 16} + SPI_GETKEYBOARDPREF {0x0044 0 i 4 } + SPI_GETSCREENREADER {0x0046 0 i 4} + SPI_GETANIMATION {0x0048 sz i2 8 cbsize} + SPI_GETFONTSMOOTHING {0x004A 0 i 4} + SPI_GETLOWPOWERTIMEOUT {0x004F 0 i 4} + SPI_GETPOWEROFFTIMEOUT {0x0050 0 i 4} + SPI_GETLOWPOWERACTIVE {0x0053 0 i 4} + SPI_GETPOWEROFFACTIVE {0x0054 0 i 4} + SPI_GETMOUSETRAILS {0x005E 0 i 4} + SPI_GETSCREENSAVERRUNNING {0x0072 0 i 4} + SPI_GETFILTERKEYS {0x0032 sz i6 24 cbsize} + SPI_GETTOGGLEKEYS {0x0034 sz i2 8 cbsize} + SPI_GETMOUSEKEYS {0x0036 sz i7 28 cbsize} + SPI_GETSHOWSOUNDS {0x0038 0 i 4} + SPI_GETSTICKYKEYS {0x003A sz i2 8 cbsize} + SPI_GETACCESSTIMEOUT {0x003C 12 i3 12 cbsize} + SPI_GETSNAPTODEFBUTTON {0x005F 0 i 4} + SPI_GETMOUSEHOVERWIDTH {0x0062 0 i 4} + SPI_GETMOUSEHOVERHEIGHT {0x0064 0 i 4 } + SPI_GETMOUSEHOVERTIME {0x0066 0 i 4} + SPI_GETWHEELSCROLLLINES {0x0068 0 i 4} + SPI_GETMENUSHOWDELAY {0x006A 0 i 4} + SPI_GETSHOWIMEUI {0x006E 0 i 4} + SPI_GETMOUSESPEED {0x0070 0 i 4} + SPI_GETACTIVEWINDOWTRACKING {0x1000 0 i 4} + SPI_GETMENUANIMATION {0x1002 0 i 4} + SPI_GETCOMBOBOXANIMATION {0x1004 0 i 4} + SPI_GETLISTBOXSMOOTHSCROLLING {0x1006 0 i 4} + SPI_GETGRADIENTCAPTIONS {0x1008 0 i 4} + SPI_GETKEYBOARDCUES {0x100A 0 i 4} + SPI_GETMENUUNDERLINES {0x100A 0 i 4} + SPI_GETACTIVEWNDTRKZORDER {0x100C 0 i 4} + SPI_GETHOTTRACKING {0x100E 0 i 4} + SPI_GETMENUFADE {0x1012 0 i 4} + SPI_GETSELECTIONFADE {0x1014 0 i 4} + SPI_GETTOOLTIPANIMATION {0x1016 0 i 4} + SPI_GETTOOLTIPFADE {0x1018 0 i 4} + SPI_GETCURSORSHADOW {0x101A 0 i 4} + SPI_GETMOUSESONAR {0x101C 0 i 4 } + SPI_GETMOUSECLICKLOCK {0x101E 0 i 4} + SPI_GETMOUSEVANISH {0x1020 0 i 4} + SPI_GETFLATMENU {0x1022 0 i 4} + SPI_GETDROPSHADOW {0x1024 0 i 4} + SPI_GETBLOCKSENDINPUTRESETS {0x1026 0 i 4} + SPI_GETUIEFFECTS {0x103E 0 i 4} + SPI_GETFOREGROUNDLOCKTIMEOUT {0x2000 0 i 4} + SPI_GETACTIVEWNDTRKTIMEOUT {0x2002 0 i 4} + SPI_GETFOREGROUNDFLASHCOUNT {0x2004 0 i 4} + SPI_GETCARETWIDTH {0x2006 0 i 4} + SPI_GETMOUSECLICKLOCKTIME {0x2008 0 i 4} + SPI_GETFONTSMOOTHINGTYPE {0x200A 0 i 4} + SPI_GETFONTSMOOTHINGCONTRAST {0x200C 0 i 4} + SPI_GETFOCUSBORDERWIDTH {0x200E 0 i 4} + SPI_GETFOCUSBORDERHEIGHT {0x2010 0 i 4} + } + if {$::tcl_platform(pointerSize) == 4} { + set hc_struct_size 12 + set bfmt i3 + } else { + set hc_struct_size 16 + set bfmt i4 + } + set SystemParametersInfo_uiactions_get(SPI_GETHIGHCONTRAST) [list 0x0042 sz $bfmt $hc_struct_size cbsize] + } + + set key [string toupper $uiaction] + + # TBD - + # SPI_GETSOUNDSENTRY {0x0040 } + # SPI_GETICONMETRICS {0x002D } + # SPI_GETICONTITLELOGFONT {0x001F } + # SPI_GETDEFAULTINPUTLANG {0x0059 } + # SPI_GETFONTSMOOTHINGORIENTATION {0x2012} + + if {![info exists SystemParametersInfo_uiactions_get($key)]} { + set key SPI_$key + if {![info exists SystemParametersInfo_uiactions_get($key)]} { + error "Unknown SystemParametersInfo index symbol '$uiaction'" + } + } + + lassign $SystemParametersInfo_uiactions_get($key) index uiparam fmt sz modifiers + if {$uiparam eq "sz"} { + set uiparam $sz + } + set mem [malloc $sz] + trap { + if {[lsearch -exact $modifiers cbsize] >= 0} { + # A structure that needs first field set to its size + Twapi_WriteMemory 1 $mem 0 $sz [binary format i $sz] + } + SystemParametersInfo $index $uiparam $mem 0 + if {$fmt eq "unicode"} { + return [Twapi_ReadMemory 3 $mem 0 $sz 1] + } else { + set n [binary scan [Twapi_ReadMemory 1 $mem 0 $sz] $fmt {*}[lrange {val0 val1 val2 val3 val4 val5 val6 val7 val8 val9 val10 val11 val12 val13 val14 val15 val16 val17 val17} 0 [llength $fmt]-1]] + if {$n == 1} { + return $val0 + } else { + set result {} + for {set i 0} {$i < $n} {incr i} { + lappend result {*}[set val$i] + } + return $result + } + } + } finally { + free $mem + } +} + +proc twapi::set_system_parameters_info {uiaction val args} { + variable SystemParametersInfo_uiactions_set + + # Format of an element is + # uiaction_indexvalue uiparam binaryscanstring malloc_size modifiers + # uiparam may be an int or "sz" in which case the malloc size + # is substribnuted for it. + # If modifiers contains "cbsize" the first dword is initialized + # with malloc_size + if {![info exists SystemParametersInfo_uiactions_set]} { + array set SystemParametersInfo_uiactions_set { + SPI_SETBEEP {0x0002 bool} + SPI_SETMOUSE {0x0004 unsupported} + SPI_SETBORDER {0x0006 int} + SPI_SETKEYBOARDSPEED {0x000B int} + SPI_ICONHORIZONTALSPACING {0x000D int} + SPI_SETSCREENSAVETIMEOUT {0x000F int} + SPI_SETSCREENSAVEACTIVE {0x0011 bool} + SPI_SETDESKWALLPAPER {0x0014 unsupported} + SPI_SETDESKPATTERN {0x0015 int} + SPI_SETKEYBOARDDELAY {0x0017 int} + SPI_ICONVERTICALSPACING {0x0018 int} + SPI_SETICONTITLEWRAP {0x001A bool} + SPI_SETMENUDROPALIGNMENT {0x001C bool} + SPI_SETDOUBLECLKWIDTH {0x001D int} + SPI_SETDOUBLECLKHEIGHT {0x001E int} + SPI_SETDOUBLECLICKTIME {0x0020 int} + SPI_SETMOUSEBUTTONSWAP {0x0021 bool} + SPI_SETICONTITLELOGFONT {0x0022 LOGFONT} + SPI_SETDRAGFULLWINDOWS {0x0025 bool} + SPI_SETNONCLIENTMETRICS {0x002A NONCLIENTMETRICS} + SPI_SETMINIMIZEDMETRICS {0x002C MINIMIZEDMETRICS} + SPI_SETICONMETRICS {0x002E ICONMETRICS} + SPI_SETWORKAREA {0x002F RECT} + SPI_SETPENWINDOWS {0x0031} + SPI_SETHIGHCONTRAST {0x0043 HIGHCONTRAST} + SPI_SETKEYBOARDPREF {0x0045 bool} + SPI_SETSCREENREADER {0x0047 bool} + SPI_SETANIMATION {0x0049 ANIMATIONINFO} + SPI_SETFONTSMOOTHING {0x004B bool} + SPI_SETDRAGWIDTH {0x004C int} + SPI_SETDRAGHEIGHT {0x004D int} + SPI_SETHANDHELD {0x004E} + SPI_SETLOWPOWERTIMEOUT {0x0051 int} + SPI_SETPOWEROFFTIMEOUT {0x0052 int} + SPI_SETLOWPOWERACTIVE {0x0055 bool} + SPI_SETPOWEROFFACTIVE {0x0056 bool} + SPI_SETCURSORS {0x0057 int} + SPI_SETICONS {0x0058 int} + SPI_SETDEFAULTINPUTLANG {0x005A HKL} + SPI_SETLANGTOGGLE {0x005B int} + SPI_SETMOUSETRAILS {0x005D int} + SPI_SETFILTERKEYS {0x0033 FILTERKEYS} + SPI_SETTOGGLEKEYS {0x0035 TOGGLEKEYS} + SPI_SETMOUSEKEYS {0x0037 MOUSEKEYS} + SPI_SETSHOWSOUNDS {0x0039 bool} + SPI_SETSTICKYKEYS {0x003B STICKYKEYS} + SPI_SETACCESSTIMEOUT {0x003D ACCESSTIMEOUT} + SPI_SETSERIALKEYS {0x003F SERIALKEYS} + SPI_SETSOUNDSENTRY {0x0041 SOUNDSENTRY} + SPI_SETSNAPTODEFBUTTON {0x0060 bool} + SPI_SETMOUSEHOVERWIDTH {0x0063 int} + SPI_SETMOUSEHOVERHEIGHT {0x0065 int} + SPI_SETMOUSEHOVERTIME {0x0067 int} + SPI_SETWHEELSCROLLLINES {0x0069 int} + SPI_SETMENUSHOWDELAY {0x006B int} + SPI_SETSHOWIMEUI {0x006F bool} + SPI_SETMOUSESPEED {0x0071 castint} + SPI_SETACTIVEWINDOWTRACKING {0x1001 castbool} + SPI_SETMENUANIMATION {0x1003 castbool} + SPI_SETCOMBOBOXANIMATION {0x1005 castbool} + SPI_SETLISTBOXSMOOTHSCROLLING {0x1007 castbool} + SPI_SETGRADIENTCAPTIONS {0x1009 castbool} + SPI_SETKEYBOARDCUES {0x100B castbool} + SPI_SETMENUUNDERLINES {0x100B castbool} + SPI_SETACTIVEWNDTRKZORDER {0x100D castbool} + SPI_SETHOTTRACKING {0x100F castbool} + SPI_SETMENUFADE {0x1013 castbool} + SPI_SETSELECTIONFADE {0x1015 castbool} + SPI_SETTOOLTIPANIMATION {0x1017 castbool} + SPI_SETTOOLTIPFADE {0x1019 castbool} + SPI_SETCURSORSHADOW {0x101B castbool} + SPI_SETMOUSESONAR {0x101D castbool} + SPI_SETMOUSECLICKLOCK {0x101F bool} + SPI_SETMOUSEVANISH {0x1021 castbool} + SPI_SETFLATMENU {0x1023 castbool} + SPI_SETDROPSHADOW {0x1025 castbool} + SPI_SETBLOCKSENDINPUTRESETS {0x1027 bool} + SPI_SETUIEFFECTS {0x103F castbool} + SPI_SETFOREGROUNDLOCKTIMEOUT {0x2001 castint} + SPI_SETACTIVEWNDTRKTIMEOUT {0x2003 castint} + SPI_SETFOREGROUNDFLASHCOUNT {0x2005 castint} + SPI_SETCARETWIDTH {0x2007 castint} + SPI_SETMOUSECLICKLOCKTIME {0x2009 int} + SPI_SETFONTSMOOTHINGTYPE {0x200B castint} + SPI_SETFONTSMOOTHINGCONTRAST {0x200D unsupported} + SPI_SETFOCUSBORDERWIDTH {0x200F castint} + SPI_SETFOCUSBORDERHEIGHT {0x2011 castint} + } + } + + + array set opts [parseargs args { + persist + notify + } -nulldefault] + + set flags 0 + if {$opts(persist)} { + setbits flags 1 + } + + if {$opts(notify)} { + # Note that actually the notify flag has no effect if persist + # is not set. + setbits flags 2 + } + + set key [string toupper $uiaction] + + if {![info exists SystemParametersInfo_uiactions_set($key)]} { + set key SPI_$key + if {![info exists SystemParametersInfo_uiactions_set($key)]} { + error "Unknown SystemParametersInfo index symbol '$uiaction'" + } + } + + lassign $SystemParametersInfo_uiactions_set($key) index fmt + + switch -exact -- $fmt { + int { SystemParametersInfo $index $val NULL $flags } + bool { + set val [expr {$val ? 1 : 0}] + SystemParametersInfo $index $val NULL $flags + } + castint { + # We have to pass the value as a cast pointer + SystemParametersInfo $index 0 [Twapi_AddressToPointer $val] $flags + } + castbool { + # We have to pass the value as a cast pointer + set val [expr {$val ? 1 : 0}] + SystemParametersInfo $index 0 [Twapi_AddressToPointer $val] $flags + } + default { + error "The data format for $uiaction is not currently supported" + } + } + + return +} + +namespace eval twapi { + variable _wts_session_monitors + set _wts_session_monitors [dict create] +} + +proc twapi::start_wts_session_monitor {script args} { + variable _wts_session_monitors + + parseargs args { + all + } -maxleftover 0 -setvars + set script [lrange $script 0 end]; # Verify syntactically a list + + set id "wts#[TwapiId]" + if {[dict size $_wts_session_monitors] == 0} { + # No monitoring in progress. Start it + # 0x2B1 -> WM_WTSSESSION_CHANGE + Twapi_WTSRegisterSessionNotification $all + _register_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_change_handler] 0 + } + + dict set _wts_session_monitors $id $script + return $id +} + + +proc twapi::stop_wts_session_monitor {id} { + variable _wts_session_monitors + + if {![dict exists $_wts_session_monitors $id]} { + return + } + + dict unset _wts_session_monitors $id + if {[dict size $_wts_session_monitors] == 0} { + # 0x2B1 -> WM_WTSSESSION_CHANGE + _unregister_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_handler] + Twapi_WTSUnRegisterSessionNotification + } +} + +proc twapi::_wts_session_change_handler {msg change session_id msgpos ticks} { + variable _wts_session_monitors + + if {[dict size $_wts_session_monitors] == 0} { + return; # Not an error, could have deleted while already queued + } + + dict for {id script} $_wts_session_monitors { + set code [catch {uplevel #0 [linsert $script end $change $session_id]} msg] + if {$code == 1} { + # Error - put in background but we do not abort + after 0 [list error $msg $::errorInfo $::errorCode] + } + } + return +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/pdh.tcl b/src/vendorlib_tcl8/twapi-5.0b1/pdh.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/pdh.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/pdh.tcl index fadf8817..1870ad3c 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/pdh.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/pdh.tcl @@ -1,984 +1,984 @@ -# -# Copyright (c) 2003-2014, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { -} - -# -# Return list of toplevel performance objects -proc twapi::pdh_enumerate_objects {args} { - - array set opts [parseargs args { - datasource.arg - machine.arg - {detail.arg wizard} - refresh - } -nulldefault] - - # TBD - PdhEnumObjects enables the SeDebugPrivilege the first time it - # is called. Should we reset it if it was not already enabled? - # This seems to only happen on the first call - - return [PdhEnumObjects $opts(datasource) $opts(machine) \ - [_perf_detail_sym_to_val $opts(detail)] \ - $opts(refresh)] -} - -proc twapi::_pdh_enumerate_object_items_helper {selector objname args} { - array set opts [parseargs args { - datasource.arg - machine.arg - {detail.arg wizard} - refresh - } -nulldefault] - - if {$opts(refresh)} { - _refresh_perf_objects $opts(machine) $opts(datasource) - } - - return [PdhEnumObjectItems $opts(datasource) $opts(machine) \ - $objname \ - [_perf_detail_sym_to_val $opts(detail)] \ - $selector] -} - -interp alias {} twapi::pdh_enumerate_object_items {} twapi::_pdh_enumerate_object_items_helper 0 -interp alias {} twapi::pdh_enumerate_object_counters {} twapi::_pdh_enumerate_object_items_helper 1 -interp alias {} twapi::pdh_enumerate_object_instances {} twapi::_pdh_enumerate_object_items_helper 2 - - -# -# Construct a counter path -proc twapi::pdh_counter_path {object counter args} { - array set opts [parseargs args { - machine.arg - instance.arg - parent.arg - {instanceindex.int -1} - {localized.bool false} - } -nulldefault] - - if {$opts(instanceindex) == 0} { - # For XP. For first instance (index 0), the path should not contain - # "#0" but on XP it does. Reset it to -1 for Vista+ consistency - set opts(instanceindex) -1 - } - - - if {! $opts(localized)} { - # Need to localize the counter names - set object [_pdh_localize $object] - set counter [_pdh_localize $counter] - # TBD - not sure we need to localize parent - set opts(parent) [_pdh_localize $opts(parent)] - } - - # TBD - add options PDH_PATH_WBEM as documented in PdhMakeCounterPath - return [PdhMakeCounterPath $opts(machine) $object $opts(instance) \ - $opts(parent) $opts(instanceindex) $counter 0] - -} - -# -# Parse a counter path and return the individual elements -proc twapi::pdh_parse_counter_path {counter_path} { - return [twine {machine object instance parent instanceindex counter} [PdhParseCounterPath $counter_path 0]] -} - - -interp alias {} twapi::pdh_get_scalar {} twapi::_pdh_get 1 -interp alias {} twapi::pdh_get_array {} twapi::_pdh_get 0 - -proc twapi::_pdh_get {scalar hcounter args} { - - array set opts [parseargs args { - {format.arg large {long large double}} - {scale.arg {} {{} none x1000 nocap100}} - var.arg - } -ignoreunknown -nulldefault] - - set flags [_pdh_fmt_sym_to_val $opts(format)] - - if {$opts(scale) ne ""} { - set flags [expr {$flags | [_pdh_fmt_sym_to_val $opts(scale)]}] - } - - set status 1 - set result "" - trap { - if {$scalar} { - set result [PdhGetFormattedCounterValue $hcounter $flags] - } else { - set result [PdhGetFormattedCounterArray $hcounter $flags] - } - } onerror {TWAPI_WIN32 0x800007d1} { - # Error is that no such instance exists. - # If result is being returned in a variable, then - # we will not generate an error but pass back a return value - # of 0 - if {[string length $opts(var)] == 0} { - rethrow - } - set status 0 - } - - if {[string length $opts(var)]} { - uplevel [list set $opts(var) $result] - return $status - } else { - return $result - } -} - -# -# Get the value of a counter identified by the path. -# Should not be used to collect -# rate based options. -# TBD - document -proc twapi::pdh_counter_path_value {counter_path args} { - - array set opts [parseargs args { - {format.arg long} - scale.arg - datasource.arg - var.arg - full.bool - } -nulldefault] - - # Open the query - set hquery [pdh_query_open -datasource $opts(datasource)] - trap { - set hcounter [pdh_add_counter $hquery $counter_path] - pdh_query_refresh $hquery - if {[string length $opts(var)]} { - # Need to pass up value in a variable if so requested - upvar $opts(var) myvar - set opts(var) myvar - } - set value [pdh_get_scalar $hcounter -format $opts(format) \ - -scale $opts(scale) -full $opts(full) \ - -var $opts(var)] - } finally { - pdh_query_close $hquery - } - - return $value -} - - -# -# Constructs one or more counter paths for getting process information. -# Returned as a list of sublists. Each sublist corresponds to a counter path -# and has the form {counteroptionname datatype counterpath rate} -# datatype is the recommended format when retrieving counter value (eg. double) -# rate is 0 or 1 depending on whether the counter is a rate based counter or -# not (requires at least two readings when getting the value) -proc twapi::get_perf_process_counter_paths {pids args} { - variable _process_counter_opt_map - - if {![info exists _counter_opt_map]} { - # "descriptive string" format rate - array set _process_counter_opt_map { - privilegedutilization {"% Privileged Time" double 1} - processorutilization {"% Processor Time" double 1} - userutilization {"% User Time" double 1} - parent {"Creating Process ID" long 0} - elapsedtime {"Elapsed Time" large 0} - handlecount {"Handle Count" long 0} - pid {"ID Process" long 0} - iodatabytesrate {"IO Data Bytes/sec" large 1} - iodataopsrate {"IO Data Operations/sec" large 1} - iootherbytesrate {"IO Other Bytes/sec" large 1} - iootheropsrate {"IO Other Operations/sec" large 1} - ioreadbytesrate {"IO Read Bytes/sec" large 1} - ioreadopsrate {"IO Read Operations/sec" large 1} - iowritebytesrate {"IO Write Bytes/sec" large 1} - iowriteopsrate {"IO Write Operations/sec" large 1} - pagefaultrate {"Page Faults/sec" large 1} - pagefilebytes {"Page File Bytes" large 0} - pagefilebytespeak {"Page File Bytes Peak" large 0} - poolnonpagedbytes {"Pool Nonpaged Bytes" large 0} - poolpagedbytes {"Pool Paged Bytes" large 1} - basepriority {"Priority Base" large 1} - privatebytes {"Private Bytes" large 1} - threadcount {"Thread Count" large 1} - virtualbytes {"Virtual Bytes" large 1} - virtualbytespeak {"Virtual Bytes Peak" large 1} - workingset {"Working Set" large 1} - workingsetpeak {"Working Set Peak" large 1} - } - } - - set optdefs { - machine.arg - datasource.arg - all - refresh - } - - # Add counter names to option list - foreach cntr [array names _process_counter_opt_map] { - lappend optdefs $cntr - } - - # Parse options - array set opts [parseargs args $optdefs -nulldefault] - - # Force a refresh of object items - if {$opts(refresh)} { - # Silently ignore. The above counters are predefined and refreshing - # is just a time-consuming no-op. Keep the option for backward - # compatibility - if {0} { - _refresh_perf_objects $opts(machine) $opts(datasource) - } - } - - # TBD - could we not use get_perf_instance_counter_paths instead of rest of this code - - # Get the path to the process. - set pid_paths [get_perf_counter_paths \ - [_pdh_localize "Process"] \ - [list [_pdh_localize "ID Process"]] \ - $pids \ - -machine $opts(machine) -datasource $opts(datasource) \ - -all] - - if {[llength $pid_paths] == 0} { - # No thread - return [list ] - } - - # Construct the requested counter paths - set counter_paths [list ] - foreach {pid pid_path} $pid_paths { - - # We have to filter out an entry for _Total which might be present - # if pid includes "0" - # TBD - does _Total need to be localized? - if {$pid == 0 && [string match -nocase *_Total\#0* $pid_path]} { - continue - } - - # Break it down into components and store in array - array set path_components [pdh_parse_counter_path $pid_path] - - # Construct counter paths for this pid - foreach {opt counter_info} [array get _process_counter_opt_map] { - if {$opts(all) || $opts($opt)} { - lappend counter_paths \ - [list -$opt $pid [lindex $counter_info 1] \ - [pdh_counter_path $path_components(object) \ - [_pdh_localize [lindex $counter_info 0]] \ - -localized true \ - -machine $path_components(machine) \ - -parent $path_components(parent) \ - -instance $path_components(instance) \ - -instanceindex $path_components(instanceindex)] \ - [lindex $counter_info 2] \ - ] - } - } - } - - return $counter_paths -} - - -# Returns the counter path for the process with the given pid. This includes -# the pid counter path element -proc twapi::get_perf_process_id_path {pid args} { - return [get_unique_counter_path \ - [_pdh_localize "Process"] \ - [_pdh_localize "ID Process"] $pid] -} - - -# -# Constructs one or more counter paths for getting thread information. -# Returned as a list of sublists. Each sublist corresponds to a counter path -# and has the form {counteroptionname datatype counterpath rate} -# datatype is the recommended format when retrieving counter value (eg. double) -# rate is 0 or 1 depending on whether the counter is a rate based counter or -# not (requires at least two readings when getting the value) -proc twapi::get_perf_thread_counter_paths {tids args} { - variable _thread_counter_opt_map - - if {![info exists _thread_counter_opt_map]} { - array set _thread_counter_opt_map { - privilegedutilization {"% Privileged Time" double 1} - processorutilization {"% Processor Time" double 1} - userutilization {"% User Time" double 1} - contextswitchrate {"Context Switches/sec" long 1} - elapsedtime {"Elapsed Time" large 0} - pid {"ID Process" long 0} - tid {"ID Thread" long 0} - basepriority {"Priority Base" long 0} - priority {"Priority Current" long 0} - startaddress {"Start Address" large 0} - state {"Thread State" long 0} - waitreason {"Thread Wait Reason" long 0} - } - } - - set optdefs { - machine.arg - datasource.arg - all - refresh - } - - # Add counter names to option list - foreach cntr [array names _thread_counter_opt_map] { - lappend optdefs $cntr - } - - # Parse options - array set opts [parseargs args $optdefs -nulldefault] - - # Force a refresh of object items - if {$opts(refresh)} { - # Silently ignore. The above counters are predefined and refreshing - # is just a time-consuming no-op. Keep the option for backward - # compatibility - if {0} { - _refresh_perf_objects $opts(machine) $opts(datasource) - } - } - - # TBD - could we not use get_perf_instance_counter_paths instead of rest of this code - - # Get the path to the thread - set tid_paths [get_perf_counter_paths \ - [_pdh_localize "Thread"] \ - [list [_pdh_localize "ID Thread"]] \ - $tids \ - -machine $opts(machine) -datasource $opts(datasource) \ - -all] - - if {[llength $tid_paths] == 0} { - # No thread - return [list ] - } - - # Now construct the requested counter paths - set counter_paths [list ] - foreach {tid tid_path} $tid_paths { - # Break it down into components and store in array - array set path_components [pdh_parse_counter_path $tid_path] - foreach {opt counter_info} [array get _thread_counter_opt_map] { - if {$opts(all) || $opts($opt)} { - lappend counter_paths \ - [list -$opt $tid [lindex $counter_info 1] \ - [pdh_counter_path $path_components(object) \ - [_pdh_localize [lindex $counter_info 0]] \ - -localized true \ - -machine $path_components(machine) \ - -parent $path_components(parent) \ - -instance $path_components(instance) \ - -instanceindex $path_components(instanceindex)] \ - [lindex $counter_info 2] - ] - } - } - } - - return $counter_paths -} - - -# Returns the counter path for the thread with the given tid. This includes -# the tid counter path element -proc twapi::get_perf_thread_id_path {tid args} { - - return [get_unique_counter_path [_pdh_localize"Thread"] [_pdh_localize "ID Thread"] $tid] -} - - -# -# Constructs one or more counter paths for getting processor information. -# Returned as a list of sublists. Each sublist corresponds to a counter path -# and has the form {counteroptionname datatype counterpath rate} -# datatype is the recommended format when retrieving counter value (eg. double) -# rate is 0 or 1 depending on whether the counter is a rate based counter or -# not (requires at least two readings when getting the value) -# $processor should be the processor number or "" to get total -proc twapi::get_perf_processor_counter_paths {processor args} { - variable _processor_counter_opt_map - - if {![string is integer -strict $processor]} { - if {[string length $processor]} { - error "Processor id must be an integer or null to retrieve information for all processors" - } - set processor "_Total" - } - - if {![info exists _processor_counter_opt_map]} { - array set _processor_counter_opt_map { - dpcutilization {"% DPC Time" double 1} - interruptutilization {"% Interrupt Time" double 1} - privilegedutilization {"% Privileged Time" double 1} - processorutilization {"% Processor Time" double 1} - userutilization {"% User Time" double 1} - dpcrate {"DPC Rate" double 1} - dpcqueuerate {"DPCs Queued/sec" double 1} - interruptrate {"Interrupts/sec" double 1} - } - } - - set optdefs { - machine.arg - datasource.arg - all - refresh - } - - # Add counter names to option list - foreach cntr [array names _processor_counter_opt_map] { - lappend optdefs $cntr - } - - # Parse options - array set opts [parseargs args $optdefs -nulldefault -maxleftover 0] - - # Force a refresh of object items - if {$opts(refresh)} { - # Silently ignore. The above counters are predefined and refreshing - # is just a time-consuming no-op. Keep the option for backward - # compatibility - if {0} { - _refresh_perf_objects $opts(machine) $opts(datasource) - } - } - - # Now construct the requested counter paths - set counter_paths [list ] - foreach {opt counter_info} [array get _processor_counter_opt_map] { - if {$opts(all) || $opts($opt)} { - lappend counter_paths \ - [list $opt $processor [lindex $counter_info 1] \ - [pdh_counter_path \ - [_pdh_localize "Processor"] \ - [_pdh_localize [lindex $counter_info 0]] \ - -localized true \ - -machine $opts(machine) \ - -instance $processor] \ - [lindex $counter_info 2] \ - ] - } - } - - return $counter_paths -} - - - -# -# Returns a list comprising of the counter paths for counters with -# names in the list $counters from those instance(s) whose counter -# $key_counter matches the specified $key_counter_value -proc twapi::get_perf_instance_counter_paths {object counters - key_counter key_counter_values - args} { - # Parse options - array set opts [parseargs args { - machine.arg - datasource.arg - {matchop.arg "exact"} - skiptotal.bool - refresh - } -nulldefault] - - # Force a refresh of object items - if {$opts(refresh)} { - _refresh_perf_objects $opts(machine) $opts(datasource) - } - - # Get the list of instances that have the specified value for the - # key counter - set instance_paths [get_perf_counter_paths $object \ - [list $key_counter] $key_counter_values \ - -machine $opts(machine) \ - -datasource $opts(datasource) \ - -matchop $opts(matchop) \ - -skiptotal $opts(skiptotal) \ - -all] - - # Loop through all instance paths, and all counters to generate - # We store in an array to get rid of duplicates - array set counter_paths {} - foreach {key_counter_value instance_path} $instance_paths { - # Break it down into components and store in array - array set path_components [pdh_parse_counter_path $instance_path] - - # Now construct the requested counter paths - # TBD - what should -localized be here ? - foreach counter $counters { - set counter_path \ - [pdh_counter_path $path_components(object) \ - $counter \ - -localized true \ - -machine $path_components(machine) \ - -parent $path_components(parent) \ - -instance $path_components(instance) \ - -instanceindex $path_components(instanceindex)] - set counter_paths($counter_path) "" - } - } - - return [array names counter_paths] - - -} - - -# -# Returns a list comprising of the counter paths for all counters -# whose values match the specified criteria -proc twapi::get_perf_counter_paths {object counters counter_values args} { - array set opts [parseargs args { - machine.arg - datasource.arg - {matchop.arg "exact"} - skiptotal.bool - all - refresh - } -nulldefault] - - if {$opts(refresh)} { - _refresh_perf_objects $opts(machine) $opts(datasource) - } - - set items [pdh_enum_object_items $object \ - -machine $opts(machine) \ - -datasource $opts(datasource)] - lassign $items object_counters object_instances - - if {[llength $counters]} { - set object_counters $counters - } - set paths [_make_counter_path_list \ - $object $object_instances $object_counters \ - -skiptotal $opts(skiptotal) -machine $opts(machine)] - set result_paths [list ] - trap { - # Set up the query with the process id for all processes - set hquery [pdh_query_open -datasource $opts(datasource)] - foreach path $paths { - set hcounter [pdh_add_counter $hquery $path] - set lookup($hcounter) $path - } - - # Now collect the info - pdh_query_refresh $hquery - - # Now lookup each counter value to find a matching one - foreach hcounter [array names lookup] { - if {! [pdh_get_scalar $hcounter -var value]} { - # Counter or instance no longer exists - continue - } - - set match_pos [lsearch -$opts(matchop) $counter_values $value] - if {$match_pos >= 0} { - lappend result_paths \ - [lindex $counter_values $match_pos] $lookup($hcounter) - if {! $opts(all)} { - break - } - } - } - } finally { - # TBD - should we have a catch to throw errors? - pdh_query_close $hquery - } - - return $result_paths -} - - -# -# Returns the counter path for counter $counter with a value $value -# for object $object. Returns "" on no matches but exception if more than one -proc twapi::get_unique_counter_path {object counter value args} { - set matches [get_perf_counter_paths $object [list $counter ] [list $value] {*}$args -all] - if {[llength $matches] > 1} { - error "Multiple counter paths found matching criteria object='$object' counter='$counter' value='$value" - } - return [lindex $matches 0] -} - - - -# -# Utilities -# -proc twapi::_refresh_perf_objects {machine datasource} { - pdh_enumerate_objects -refresh - return -} - - -# -# Return the localized form of a counter name -# TBD - assumes machine is local machine! -proc twapi::_pdh_localize {name} { - variable _perf_counter_ids - variable _localized_perf_counter_names - - set name_index [string tolower $name] - - # If we already have a translation, return it - if {[info exists _localized_perf_counter_names($name_index)]} { - return $_localized_perf_counter_names($name_index) - } - - # Didn't already have it. Go generate the mappings - - # Get the list of counter names in English if we don't already have it - if {![info exists _perf_counter_ids]} { - foreach {id label} [registry get {HKEY_PERFORMANCE_DATA} {Counter 009}] { - set _perf_counter_ids([string tolower $label]) $id - } - } - - # If we have do not have id for the given name, we will just use - # the passed name as the localized version - if {! [info exists _perf_counter_ids($name_index)]} { - # Does not seem to exist. Just set localized name to itself - return [set _localized_perf_counter_names($name_index) $name] - } - - # We do have an id. THen try to get a translated name - if {[catch {PdhLookupPerfNameByIndex "" $_perf_counter_ids($name_index)} xname]} { - set _localized_perf_counter_names($name_index) $name - } else { - set _localized_perf_counter_names($name_index) $xname - } - - return $_localized_perf_counter_names($name_index) -} - - -# Given a list of instances and counters, return a cross product of the -# corresponding counter paths. -# The list is expected to be already localized -# Example: _make_counter_path_list "Process" (instance list) {{ID Process} {...}} -# TBD - bug - does not handle -parent in counter path -proc twapi::_make_counter_path_list {object instance_list counter_list args} { - array set opts [parseargs args { - machine.arg - skiptotal.bool - } -nulldefault] - - array set instances {} - foreach instance $instance_list { - if {![info exists instances($instance)]} { - set instances($instance) 1 - } else { - incr instances($instance) - } - } - - if {$opts(skiptotal)} { - catch {array unset instances "*_Total"} - } - - set counter_paths [list ] - foreach {instance count} [array get instances] { - while {$count} { - incr count -1 - foreach counter $counter_list { - lappend counter_paths [pdh_counter_path \ - $object $counter \ - -localized true \ - -machine $opts(machine) \ - -instance $instance \ - -instanceindex $count] - } - } - } - - return $counter_paths -} - - -# -# Given a set of counter paths in the format returned by -# get_perf_thread_counter_paths, get_perf_processor_counter_paths etc. -# return the counter information as a flat list of field value pairs -proc twapi::get_perf_values_from_metacounter_info {metacounters args} { - array set opts [parseargs args {{interval.int 100}}] - - set result [list ] - set counters [list ] - if {[llength $metacounters]} { - set hquery [pdh_query_open] - trap { - set counter_info [list ] - set need_wait 0 - foreach counter_elem $metacounters { - lassign $counter_elem pdh_opt key data_type counter_path wait - incr need_wait $wait - set hcounter [pdh_add_counter $hquery $counter_path] - lappend counters $hcounter - lappend counter_info $pdh_opt $key $counter_path $data_type $hcounter - } - - pdh_query_refresh $hquery - if {$need_wait} { - after $opts(interval) - pdh_query_refresh $hquery - } - - foreach {pdh_opt key counter_path data_type hcounter} $counter_info { - if {[pdh_get_scalar $hcounter -format $data_type -var value]} { - lappend result $pdh_opt $key $value - } - } - } onerror {} { - #puts "Error: $msg" - } finally { - pdh_query_close $hquery - } - } - - return $result - -} - -proc twapi::pdh_query_open {args} { - variable _pdh_queries - - array set opts [parseargs args { - datasource.arg - cookie.int - } -nulldefault] - - set qh [PdhOpenQuery $opts(datasource) $opts(cookie)] - set id pdh[TwapiId] - dict set _pdh_queries($id) Qh $qh - dict set _pdh_queries($id) Counters {} - dict set _pdh_queries($id) Meta {} - return $id -} - -proc twapi::pdh_query_refresh {qid args} { - variable _pdh_queries - _pdh_query_check $qid - PdhCollectQueryData [dict get $_pdh_queries($qid) Qh] - return -} - -proc twapi::pdh_query_close {qid} { - variable _pdh_queries - _pdh_query_check $qid - - dict for {ctrh -} [dict get $_pdh_queries($qid) Counters] { - PdhRemoveCounter $ctrh - } - - PdhCloseQuery [dict get $_pdh_queries($qid) Qh] - unset _pdh_queries($qid) -} - -proc twapi::pdh_add_counter {qid ctr_path args} { - variable _pdh_queries - - _pdh_query_check $qid - - parseargs args { - {format.arg large {long large double}} - {scale.arg {} {{} none x1000 nocap100}} - name.arg - cookie.int - array.bool - } -nulldefault -maxleftover 0 -setvars - - if {$name eq ""} { - set name $ctr_path - } - - if {[dict exists $_pdh_queries($qid) Meta $name]} { - error "A counter with name \"$name\" already present in the query." - } - - set flags [_pdh_fmt_sym_to_val $format] - - if {$scale ne ""} { - set flags [expr {$flags | [_pdh_fmt_sym_to_val $scale]}] - } - - set hctr [PdhAddCounter [dict get $_pdh_queries($qid) Qh] $ctr_path $flags] - dict set _pdh_queries($qid) Counters $hctr 1 - dict set _pdh_queries($qid) Meta $name [list Counter $hctr FmtFlags $flags Array $array] - - return $hctr -} - -proc twapi::pdh_remove_counter {qid ctrname} { - variable _pdh_queries - _pdh_query_check $qid - if {![dict exists $_pdh_queries($qid) Meta $ctrname]} { - badargs! "Counter \"$ctrname\" not present in query." - } - set hctr [dict get $_pdh_queries($qid) Meta $ctrname Counter] - dict unset _pdh_queries($qid) Counters $hctr - dict unset _pdh_queries($qid) Meta $ctrname - PdhRemoveCounter $hctr - return -} - -proc twapi::pdh_query_get {qid args} { - variable _pdh_queries - - _pdh_query_check $qid - - # Refresh the data - PdhCollectQueryData [dict get $_pdh_queries($qid) Qh] - - set meta [dict get $_pdh_queries($qid) Meta] - - if {[llength $args] != 0} { - set names $args - } else { - set names [dict keys $meta] - } - - set result {} - foreach name $names { - if {[dict get $meta $name Array]} { - lappend result $name [PdhGetFormattedCounterArray [dict get $meta $name Counter] [dict get $meta $name FmtFlags]] - } else { - lappend result $name [PdhGetFormattedCounterValue [dict get $meta $name Counter] [dict get $meta $name FmtFlags]] - } - } - - return $result -} - -twapi::proc* twapi::pdh_system_performance_query args { - variable _sysperf_defs - - set _sysperf_defs { - event_count { {Objects Events} {} } - mutex_count { {Objects Mutexes} {} } - process_count { {Objects Processes} {} } - section_count { {Objects Sections} {} } - semaphore_count { {Objects Semaphores} {} } - thread_count { {Objects Threads} {} } - handle_count { {Process "Handle Count" -instance _Total} {-format long} } - commit_limit { {Memory "Commit Limit"} {} } - committed_bytes { {Memory "Committed Bytes"} {} } - committed_percent { {Memory "% Committed Bytes In Use"} {-format double} } - memory_free_mb { {Memory "Available MBytes"} {} } - memory_free_kb { {Memory "Available KBytes"} {} } - page_fault_rate { {Memory "Page Faults/sec"} {} } - page_input_rate { {Memory "Pages Input/sec"} {} } - page_output_rate { {Memory "Pages Output/sec"} {} } - - disk_bytes_rate { {PhysicalDisk "Disk Bytes/sec" -instance _Total} {} } - disk_readbytes_rate { {PhysicalDisk "Disk Read Bytes/sec" -instance _Total} {} } - disk_writebytes_rate { {PhysicalDisk "Disk Write Bytes/sec" -instance _Total} {} } - disk_transfer_rate { {PhysicalDisk "Disk Transfers/sec" -instance _Total} {} } - disk_read_rate { {PhysicalDisk "Disk Reads/sec" -instance _Total} {} } - disk_write_rate { {PhysicalDisk "Disk Writes/sec" -instance _Total} {} } - disk_idle_percent { {PhysicalDisk "% Idle Time" -instance _Total} {-format double} } - } - - # Per-processor counters are based on above but the object name depends - # on the system in order to support > 64 processors - set obj_name [expr {[min_os_version 6 1] ? "Processor Information" : "Processor"}] - dict for {key ctr_name} { - interrupt_utilization "% Interrupt Time" - privileged_utilization "% Privileged Time" - processor_utilization "% Processor Time" - user_utilization "% User Time" - idle_utilization "% Idle Time" - } { - lappend _sysperf_defs $key \ - [list \ - [list $obj_name $ctr_name -instance _Total] \ - [list -format double]] - - lappend _sysperf_defs ${key}_per_cpu \ - [list \ - [list $obj_name $ctr_name -instance *] \ - [list -format double -array 1]] - } -} { - variable _sysperf_defs - - if {[llength $args] == 0} { - return [lsort -dictionary [dict keys $_sysperf_defs]] - } - - set qid [pdh_query_open] - trap { - foreach arg $args { - set def [dict! $_sysperf_defs $arg] - set ctr_path [pdh_counter_path {*}[lindex $def 0]] - pdh_add_counter $qid $ctr_path -name $arg {*}[lindex $def 1] - } - pdh_query_refresh $qid - } onerror {} { - pdh_query_close $qid - rethrow - } - - return $qid -} - -# -# Internal utility procedures -proc twapi::_pdh_query_check {qid} { - variable _pdh_queries - - if {![info exists _pdh_queries($qid)]} { - error "Invalid query id $qid" - } -} - -proc twapi::_perf_detail_sym_to_val {sym} { - # PERF_DETAIL_NOVICE 100 - # PERF_DETAIL_ADVANCED 200 - # PERF_DETAIL_EXPERT 300 - # PERF_DETAIL_WIZARD 400 - # PERF_DETAIL_COSTLY 0x00010000 - # PERF_DETAIL_STANDARD 0x0000FFFF - - return [dict get {novice 100 advanced 200 expert 300 wizard 400 costly 0x00010000 standard 0x0000ffff } $sym] -} - - -proc twapi::_pdh_fmt_sym_to_val {sym} { - # PDH_FMT_RAW 0x00000010 - # PDH_FMT_ANSI 0x00000020 - # PDH_FMT_UNICODE 0x00000040 - # PDH_FMT_LONG 0x00000100 - # PDH_FMT_DOUBLE 0x00000200 - # PDH_FMT_LARGE 0x00000400 - # PDH_FMT_NOSCALE 0x00001000 - # PDH_FMT_1000 0x00002000 - # PDH_FMT_NODATA 0x00004000 - # PDH_FMT_NOCAP100 0x00008000 - - return [dict get { - raw 0x00000010 - ansi 0x00000020 - unicode 0x00000040 - long 0x00000100 - double 0x00000200 - large 0x00000400 - noscale 0x00001000 - none 0x00001000 - 1000 0x00002000 - x1000 0x00002000 - nodata 0x00004000 - nocap100 0x00008000 - nocap 0x00008000 - } $sym] -} +# +# Copyright (c) 2003-2014, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { +} + +# +# Return list of toplevel performance objects +proc twapi::pdh_enumerate_objects {args} { + + array set opts [parseargs args { + datasource.arg + machine.arg + {detail.arg wizard} + refresh + } -nulldefault] + + # TBD - PdhEnumObjects enables the SeDebugPrivilege the first time it + # is called. Should we reset it if it was not already enabled? + # This seems to only happen on the first call + + return [PdhEnumObjects $opts(datasource) $opts(machine) \ + [_perf_detail_sym_to_val $opts(detail)] \ + $opts(refresh)] +} + +proc twapi::_pdh_enumerate_object_items_helper {selector objname args} { + array set opts [parseargs args { + datasource.arg + machine.arg + {detail.arg wizard} + refresh + } -nulldefault] + + if {$opts(refresh)} { + _refresh_perf_objects $opts(machine) $opts(datasource) + } + + return [PdhEnumObjectItems $opts(datasource) $opts(machine) \ + $objname \ + [_perf_detail_sym_to_val $opts(detail)] \ + $selector] +} + +interp alias {} twapi::pdh_enumerate_object_items {} twapi::_pdh_enumerate_object_items_helper 0 +interp alias {} twapi::pdh_enumerate_object_counters {} twapi::_pdh_enumerate_object_items_helper 1 +interp alias {} twapi::pdh_enumerate_object_instances {} twapi::_pdh_enumerate_object_items_helper 2 + + +# +# Construct a counter path +proc twapi::pdh_counter_path {object counter args} { + array set opts [parseargs args { + machine.arg + instance.arg + parent.arg + {instanceindex.int -1} + {localized.bool false} + } -nulldefault] + + if {$opts(instanceindex) == 0} { + # For XP. For first instance (index 0), the path should not contain + # "#0" but on XP it does. Reset it to -1 for Vista+ consistency + set opts(instanceindex) -1 + } + + + if {! $opts(localized)} { + # Need to localize the counter names + set object [_pdh_localize $object] + set counter [_pdh_localize $counter] + # TBD - not sure we need to localize parent + set opts(parent) [_pdh_localize $opts(parent)] + } + + # TBD - add options PDH_PATH_WBEM as documented in PdhMakeCounterPath + return [PdhMakeCounterPath $opts(machine) $object $opts(instance) \ + $opts(parent) $opts(instanceindex) $counter 0] + +} + +# +# Parse a counter path and return the individual elements +proc twapi::pdh_parse_counter_path {counter_path} { + return [twine {machine object instance parent instanceindex counter} [PdhParseCounterPath $counter_path 0]] +} + + +interp alias {} twapi::pdh_get_scalar {} twapi::_pdh_get 1 +interp alias {} twapi::pdh_get_array {} twapi::_pdh_get 0 + +proc twapi::_pdh_get {scalar hcounter args} { + + array set opts [parseargs args { + {format.arg large {long large double}} + {scale.arg {} {{} none x1000 nocap100}} + var.arg + } -ignoreunknown -nulldefault] + + set flags [_pdh_fmt_sym_to_val $opts(format)] + + if {$opts(scale) ne ""} { + set flags [expr {$flags | [_pdh_fmt_sym_to_val $opts(scale)]}] + } + + set status 1 + set result "" + trap { + if {$scalar} { + set result [PdhGetFormattedCounterValue $hcounter $flags] + } else { + set result [PdhGetFormattedCounterArray $hcounter $flags] + } + } onerror {TWAPI_WIN32 0x800007d1} { + # Error is that no such instance exists. + # If result is being returned in a variable, then + # we will not generate an error but pass back a return value + # of 0 + if {[string length $opts(var)] == 0} { + rethrow + } + set status 0 + } + + if {[string length $opts(var)]} { + uplevel [list set $opts(var) $result] + return $status + } else { + return $result + } +} + +# +# Get the value of a counter identified by the path. +# Should not be used to collect +# rate based options. +# TBD - document +proc twapi::pdh_counter_path_value {counter_path args} { + + array set opts [parseargs args { + {format.arg long} + scale.arg + datasource.arg + var.arg + full.bool + } -nulldefault] + + # Open the query + set hquery [pdh_query_open -datasource $opts(datasource)] + trap { + set hcounter [pdh_add_counter $hquery $counter_path] + pdh_query_refresh $hquery + if {[string length $opts(var)]} { + # Need to pass up value in a variable if so requested + upvar $opts(var) myvar + set opts(var) myvar + } + set value [pdh_get_scalar $hcounter -format $opts(format) \ + -scale $opts(scale) -full $opts(full) \ + -var $opts(var)] + } finally { + pdh_query_close $hquery + } + + return $value +} + + +# +# Constructs one or more counter paths for getting process information. +# Returned as a list of sublists. Each sublist corresponds to a counter path +# and has the form {counteroptionname datatype counterpath rate} +# datatype is the recommended format when retrieving counter value (eg. double) +# rate is 0 or 1 depending on whether the counter is a rate based counter or +# not (requires at least two readings when getting the value) +proc twapi::get_perf_process_counter_paths {pids args} { + variable _process_counter_opt_map + + if {![info exists _counter_opt_map]} { + # "descriptive string" format rate + array set _process_counter_opt_map { + privilegedutilization {"% Privileged Time" double 1} + processorutilization {"% Processor Time" double 1} + userutilization {"% User Time" double 1} + parent {"Creating Process ID" long 0} + elapsedtime {"Elapsed Time" large 0} + handlecount {"Handle Count" long 0} + pid {"ID Process" long 0} + iodatabytesrate {"IO Data Bytes/sec" large 1} + iodataopsrate {"IO Data Operations/sec" large 1} + iootherbytesrate {"IO Other Bytes/sec" large 1} + iootheropsrate {"IO Other Operations/sec" large 1} + ioreadbytesrate {"IO Read Bytes/sec" large 1} + ioreadopsrate {"IO Read Operations/sec" large 1} + iowritebytesrate {"IO Write Bytes/sec" large 1} + iowriteopsrate {"IO Write Operations/sec" large 1} + pagefaultrate {"Page Faults/sec" large 1} + pagefilebytes {"Page File Bytes" large 0} + pagefilebytespeak {"Page File Bytes Peak" large 0} + poolnonpagedbytes {"Pool Nonpaged Bytes" large 0} + poolpagedbytes {"Pool Paged Bytes" large 1} + basepriority {"Priority Base" large 1} + privatebytes {"Private Bytes" large 1} + threadcount {"Thread Count" large 1} + virtualbytes {"Virtual Bytes" large 1} + virtualbytespeak {"Virtual Bytes Peak" large 1} + workingset {"Working Set" large 1} + workingsetpeak {"Working Set Peak" large 1} + } + } + + set optdefs { + machine.arg + datasource.arg + all + refresh + } + + # Add counter names to option list + foreach cntr [array names _process_counter_opt_map] { + lappend optdefs $cntr + } + + # Parse options + array set opts [parseargs args $optdefs -nulldefault] + + # Force a refresh of object items + if {$opts(refresh)} { + # Silently ignore. The above counters are predefined and refreshing + # is just a time-consuming no-op. Keep the option for backward + # compatibility + if {0} { + _refresh_perf_objects $opts(machine) $opts(datasource) + } + } + + # TBD - could we not use get_perf_instance_counter_paths instead of rest of this code + + # Get the path to the process. + set pid_paths [get_perf_counter_paths \ + [_pdh_localize "Process"] \ + [list [_pdh_localize "ID Process"]] \ + $pids \ + -machine $opts(machine) -datasource $opts(datasource) \ + -all] + + if {[llength $pid_paths] == 0} { + # No thread + return [list ] + } + + # Construct the requested counter paths + set counter_paths [list ] + foreach {pid pid_path} $pid_paths { + + # We have to filter out an entry for _Total which might be present + # if pid includes "0" + # TBD - does _Total need to be localized? + if {$pid == 0 && [string match -nocase *_Total\#0* $pid_path]} { + continue + } + + # Break it down into components and store in array + array set path_components [pdh_parse_counter_path $pid_path] + + # Construct counter paths for this pid + foreach {opt counter_info} [array get _process_counter_opt_map] { + if {$opts(all) || $opts($opt)} { + lappend counter_paths \ + [list -$opt $pid [lindex $counter_info 1] \ + [pdh_counter_path $path_components(object) \ + [_pdh_localize [lindex $counter_info 0]] \ + -localized true \ + -machine $path_components(machine) \ + -parent $path_components(parent) \ + -instance $path_components(instance) \ + -instanceindex $path_components(instanceindex)] \ + [lindex $counter_info 2] \ + ] + } + } + } + + return $counter_paths +} + + +# Returns the counter path for the process with the given pid. This includes +# the pid counter path element +proc twapi::get_perf_process_id_path {pid args} { + return [get_unique_counter_path \ + [_pdh_localize "Process"] \ + [_pdh_localize "ID Process"] $pid] +} + + +# +# Constructs one or more counter paths for getting thread information. +# Returned as a list of sublists. Each sublist corresponds to a counter path +# and has the form {counteroptionname datatype counterpath rate} +# datatype is the recommended format when retrieving counter value (eg. double) +# rate is 0 or 1 depending on whether the counter is a rate based counter or +# not (requires at least two readings when getting the value) +proc twapi::get_perf_thread_counter_paths {tids args} { + variable _thread_counter_opt_map + + if {![info exists _thread_counter_opt_map]} { + array set _thread_counter_opt_map { + privilegedutilization {"% Privileged Time" double 1} + processorutilization {"% Processor Time" double 1} + userutilization {"% User Time" double 1} + contextswitchrate {"Context Switches/sec" long 1} + elapsedtime {"Elapsed Time" large 0} + pid {"ID Process" long 0} + tid {"ID Thread" long 0} + basepriority {"Priority Base" long 0} + priority {"Priority Current" long 0} + startaddress {"Start Address" large 0} + state {"Thread State" long 0} + waitreason {"Thread Wait Reason" long 0} + } + } + + set optdefs { + machine.arg + datasource.arg + all + refresh + } + + # Add counter names to option list + foreach cntr [array names _thread_counter_opt_map] { + lappend optdefs $cntr + } + + # Parse options + array set opts [parseargs args $optdefs -nulldefault] + + # Force a refresh of object items + if {$opts(refresh)} { + # Silently ignore. The above counters are predefined and refreshing + # is just a time-consuming no-op. Keep the option for backward + # compatibility + if {0} { + _refresh_perf_objects $opts(machine) $opts(datasource) + } + } + + # TBD - could we not use get_perf_instance_counter_paths instead of rest of this code + + # Get the path to the thread + set tid_paths [get_perf_counter_paths \ + [_pdh_localize "Thread"] \ + [list [_pdh_localize "ID Thread"]] \ + $tids \ + -machine $opts(machine) -datasource $opts(datasource) \ + -all] + + if {[llength $tid_paths] == 0} { + # No thread + return [list ] + } + + # Now construct the requested counter paths + set counter_paths [list ] + foreach {tid tid_path} $tid_paths { + # Break it down into components and store in array + array set path_components [pdh_parse_counter_path $tid_path] + foreach {opt counter_info} [array get _thread_counter_opt_map] { + if {$opts(all) || $opts($opt)} { + lappend counter_paths \ + [list -$opt $tid [lindex $counter_info 1] \ + [pdh_counter_path $path_components(object) \ + [_pdh_localize [lindex $counter_info 0]] \ + -localized true \ + -machine $path_components(machine) \ + -parent $path_components(parent) \ + -instance $path_components(instance) \ + -instanceindex $path_components(instanceindex)] \ + [lindex $counter_info 2] + ] + } + } + } + + return $counter_paths +} + + +# Returns the counter path for the thread with the given tid. This includes +# the tid counter path element +proc twapi::get_perf_thread_id_path {tid args} { + + return [get_unique_counter_path [_pdh_localize"Thread"] [_pdh_localize "ID Thread"] $tid] +} + + +# +# Constructs one or more counter paths for getting processor information. +# Returned as a list of sublists. Each sublist corresponds to a counter path +# and has the form {counteroptionname datatype counterpath rate} +# datatype is the recommended format when retrieving counter value (eg. double) +# rate is 0 or 1 depending on whether the counter is a rate based counter or +# not (requires at least two readings when getting the value) +# $processor should be the processor number or "" to get total +proc twapi::get_perf_processor_counter_paths {processor args} { + variable _processor_counter_opt_map + + if {![string is integer -strict $processor]} { + if {[string length $processor]} { + error "Processor id must be an integer or null to retrieve information for all processors" + } + set processor "_Total" + } + + if {![info exists _processor_counter_opt_map]} { + array set _processor_counter_opt_map { + dpcutilization {"% DPC Time" double 1} + interruptutilization {"% Interrupt Time" double 1} + privilegedutilization {"% Privileged Time" double 1} + processorutilization {"% Processor Time" double 1} + userutilization {"% User Time" double 1} + dpcrate {"DPC Rate" double 1} + dpcqueuerate {"DPCs Queued/sec" double 1} + interruptrate {"Interrupts/sec" double 1} + } + } + + set optdefs { + machine.arg + datasource.arg + all + refresh + } + + # Add counter names to option list + foreach cntr [array names _processor_counter_opt_map] { + lappend optdefs $cntr + } + + # Parse options + array set opts [parseargs args $optdefs -nulldefault -maxleftover 0] + + # Force a refresh of object items + if {$opts(refresh)} { + # Silently ignore. The above counters are predefined and refreshing + # is just a time-consuming no-op. Keep the option for backward + # compatibility + if {0} { + _refresh_perf_objects $opts(machine) $opts(datasource) + } + } + + # Now construct the requested counter paths + set counter_paths [list ] + foreach {opt counter_info} [array get _processor_counter_opt_map] { + if {$opts(all) || $opts($opt)} { + lappend counter_paths \ + [list $opt $processor [lindex $counter_info 1] \ + [pdh_counter_path \ + [_pdh_localize "Processor"] \ + [_pdh_localize [lindex $counter_info 0]] \ + -localized true \ + -machine $opts(machine) \ + -instance $processor] \ + [lindex $counter_info 2] \ + ] + } + } + + return $counter_paths +} + + + +# +# Returns a list comprising of the counter paths for counters with +# names in the list $counters from those instance(s) whose counter +# $key_counter matches the specified $key_counter_value +proc twapi::get_perf_instance_counter_paths {object counters + key_counter key_counter_values + args} { + # Parse options + array set opts [parseargs args { + machine.arg + datasource.arg + {matchop.arg "exact"} + skiptotal.bool + refresh + } -nulldefault] + + # Force a refresh of object items + if {$opts(refresh)} { + _refresh_perf_objects $opts(machine) $opts(datasource) + } + + # Get the list of instances that have the specified value for the + # key counter + set instance_paths [get_perf_counter_paths $object \ + [list $key_counter] $key_counter_values \ + -machine $opts(machine) \ + -datasource $opts(datasource) \ + -matchop $opts(matchop) \ + -skiptotal $opts(skiptotal) \ + -all] + + # Loop through all instance paths, and all counters to generate + # We store in an array to get rid of duplicates + array set counter_paths {} + foreach {key_counter_value instance_path} $instance_paths { + # Break it down into components and store in array + array set path_components [pdh_parse_counter_path $instance_path] + + # Now construct the requested counter paths + # TBD - what should -localized be here ? + foreach counter $counters { + set counter_path \ + [pdh_counter_path $path_components(object) \ + $counter \ + -localized true \ + -machine $path_components(machine) \ + -parent $path_components(parent) \ + -instance $path_components(instance) \ + -instanceindex $path_components(instanceindex)] + set counter_paths($counter_path) "" + } + } + + return [array names counter_paths] + + +} + + +# +# Returns a list comprising of the counter paths for all counters +# whose values match the specified criteria +proc twapi::get_perf_counter_paths {object counters counter_values args} { + array set opts [parseargs args { + machine.arg + datasource.arg + {matchop.arg "exact"} + skiptotal.bool + all + refresh + } -nulldefault] + + if {$opts(refresh)} { + _refresh_perf_objects $opts(machine) $opts(datasource) + } + + set items [pdh_enum_object_items $object \ + -machine $opts(machine) \ + -datasource $opts(datasource)] + lassign $items object_counters object_instances + + if {[llength $counters]} { + set object_counters $counters + } + set paths [_make_counter_path_list \ + $object $object_instances $object_counters \ + -skiptotal $opts(skiptotal) -machine $opts(machine)] + set result_paths [list ] + trap { + # Set up the query with the process id for all processes + set hquery [pdh_query_open -datasource $opts(datasource)] + foreach path $paths { + set hcounter [pdh_add_counter $hquery $path] + set lookup($hcounter) $path + } + + # Now collect the info + pdh_query_refresh $hquery + + # Now lookup each counter value to find a matching one + foreach hcounter [array names lookup] { + if {! [pdh_get_scalar $hcounter -var value]} { + # Counter or instance no longer exists + continue + } + + set match_pos [lsearch -$opts(matchop) $counter_values $value] + if {$match_pos >= 0} { + lappend result_paths \ + [lindex $counter_values $match_pos] $lookup($hcounter) + if {! $opts(all)} { + break + } + } + } + } finally { + # TBD - should we have a catch to throw errors? + pdh_query_close $hquery + } + + return $result_paths +} + + +# +# Returns the counter path for counter $counter with a value $value +# for object $object. Returns "" on no matches but exception if more than one +proc twapi::get_unique_counter_path {object counter value args} { + set matches [get_perf_counter_paths $object [list $counter ] [list $value] {*}$args -all] + if {[llength $matches] > 1} { + error "Multiple counter paths found matching criteria object='$object' counter='$counter' value='$value" + } + return [lindex $matches 0] +} + + + +# +# Utilities +# +proc twapi::_refresh_perf_objects {machine datasource} { + pdh_enumerate_objects -refresh + return +} + + +# +# Return the localized form of a counter name +# TBD - assumes machine is local machine! +proc twapi::_pdh_localize {name} { + variable _perf_counter_ids + variable _localized_perf_counter_names + + set name_index [string tolower $name] + + # If we already have a translation, return it + if {[info exists _localized_perf_counter_names($name_index)]} { + return $_localized_perf_counter_names($name_index) + } + + # Didn't already have it. Go generate the mappings + + # Get the list of counter names in English if we don't already have it + if {![info exists _perf_counter_ids]} { + foreach {id label} [registry get {HKEY_PERFORMANCE_DATA} {Counter 009}] { + set _perf_counter_ids([string tolower $label]) $id + } + } + + # If we have do not have id for the given name, we will just use + # the passed name as the localized version + if {! [info exists _perf_counter_ids($name_index)]} { + # Does not seem to exist. Just set localized name to itself + return [set _localized_perf_counter_names($name_index) $name] + } + + # We do have an id. THen try to get a translated name + if {[catch {PdhLookupPerfNameByIndex "" $_perf_counter_ids($name_index)} xname]} { + set _localized_perf_counter_names($name_index) $name + } else { + set _localized_perf_counter_names($name_index) $xname + } + + return $_localized_perf_counter_names($name_index) +} + + +# Given a list of instances and counters, return a cross product of the +# corresponding counter paths. +# The list is expected to be already localized +# Example: _make_counter_path_list "Process" (instance list) {{ID Process} {...}} +# TBD - bug - does not handle -parent in counter path +proc twapi::_make_counter_path_list {object instance_list counter_list args} { + array set opts [parseargs args { + machine.arg + skiptotal.bool + } -nulldefault] + + array set instances {} + foreach instance $instance_list { + if {![info exists instances($instance)]} { + set instances($instance) 1 + } else { + incr instances($instance) + } + } + + if {$opts(skiptotal)} { + catch {array unset instances "*_Total"} + } + + set counter_paths [list ] + foreach {instance count} [array get instances] { + while {$count} { + incr count -1 + foreach counter $counter_list { + lappend counter_paths [pdh_counter_path \ + $object $counter \ + -localized true \ + -machine $opts(machine) \ + -instance $instance \ + -instanceindex $count] + } + } + } + + return $counter_paths +} + + +# +# Given a set of counter paths in the format returned by +# get_perf_thread_counter_paths, get_perf_processor_counter_paths etc. +# return the counter information as a flat list of field value pairs +proc twapi::get_perf_values_from_metacounter_info {metacounters args} { + array set opts [parseargs args {{interval.int 100}}] + + set result [list ] + set counters [list ] + if {[llength $metacounters]} { + set hquery [pdh_query_open] + trap { + set counter_info [list ] + set need_wait 0 + foreach counter_elem $metacounters { + lassign $counter_elem pdh_opt key data_type counter_path wait + incr need_wait $wait + set hcounter [pdh_add_counter $hquery $counter_path] + lappend counters $hcounter + lappend counter_info $pdh_opt $key $counter_path $data_type $hcounter + } + + pdh_query_refresh $hquery + if {$need_wait} { + after $opts(interval) + pdh_query_refresh $hquery + } + + foreach {pdh_opt key counter_path data_type hcounter} $counter_info { + if {[pdh_get_scalar $hcounter -format $data_type -var value]} { + lappend result $pdh_opt $key $value + } + } + } onerror {} { + #puts "Error: $msg" + } finally { + pdh_query_close $hquery + } + } + + return $result + +} + +proc twapi::pdh_query_open {args} { + variable _pdh_queries + + array set opts [parseargs args { + datasource.arg + cookie.int + } -nulldefault] + + set qh [PdhOpenQuery $opts(datasource) $opts(cookie)] + set id pdh[TwapiId] + dict set _pdh_queries($id) Qh $qh + dict set _pdh_queries($id) Counters {} + dict set _pdh_queries($id) Meta {} + return $id +} + +proc twapi::pdh_query_refresh {qid args} { + variable _pdh_queries + _pdh_query_check $qid + PdhCollectQueryData [dict get $_pdh_queries($qid) Qh] + return +} + +proc twapi::pdh_query_close {qid} { + variable _pdh_queries + _pdh_query_check $qid + + dict for {ctrh -} [dict get $_pdh_queries($qid) Counters] { + PdhRemoveCounter $ctrh + } + + PdhCloseQuery [dict get $_pdh_queries($qid) Qh] + unset _pdh_queries($qid) +} + +proc twapi::pdh_add_counter {qid ctr_path args} { + variable _pdh_queries + + _pdh_query_check $qid + + parseargs args { + {format.arg large {long large double}} + {scale.arg {} {{} none x1000 nocap100}} + name.arg + cookie.int + array.bool + } -nulldefault -maxleftover 0 -setvars + + if {$name eq ""} { + set name $ctr_path + } + + if {[dict exists $_pdh_queries($qid) Meta $name]} { + error "A counter with name \"$name\" already present in the query." + } + + set flags [_pdh_fmt_sym_to_val $format] + + if {$scale ne ""} { + set flags [expr {$flags | [_pdh_fmt_sym_to_val $scale]}] + } + + set hctr [PdhAddCounter [dict get $_pdh_queries($qid) Qh] $ctr_path $flags] + dict set _pdh_queries($qid) Counters $hctr 1 + dict set _pdh_queries($qid) Meta $name [list Counter $hctr FmtFlags $flags Array $array] + + return $hctr +} + +proc twapi::pdh_remove_counter {qid ctrname} { + variable _pdh_queries + _pdh_query_check $qid + if {![dict exists $_pdh_queries($qid) Meta $ctrname]} { + badargs! "Counter \"$ctrname\" not present in query." + } + set hctr [dict get $_pdh_queries($qid) Meta $ctrname Counter] + dict unset _pdh_queries($qid) Counters $hctr + dict unset _pdh_queries($qid) Meta $ctrname + PdhRemoveCounter $hctr + return +} + +proc twapi::pdh_query_get {qid args} { + variable _pdh_queries + + _pdh_query_check $qid + + # Refresh the data + PdhCollectQueryData [dict get $_pdh_queries($qid) Qh] + + set meta [dict get $_pdh_queries($qid) Meta] + + if {[llength $args] != 0} { + set names $args + } else { + set names [dict keys $meta] + } + + set result {} + foreach name $names { + if {[dict get $meta $name Array]} { + lappend result $name [PdhGetFormattedCounterArray [dict get $meta $name Counter] [dict get $meta $name FmtFlags]] + } else { + lappend result $name [PdhGetFormattedCounterValue [dict get $meta $name Counter] [dict get $meta $name FmtFlags]] + } + } + + return $result +} + +twapi::proc* twapi::pdh_system_performance_query args { + variable _sysperf_defs + + set _sysperf_defs { + event_count { {Objects Events} {} } + mutex_count { {Objects Mutexes} {} } + process_count { {Objects Processes} {} } + section_count { {Objects Sections} {} } + semaphore_count { {Objects Semaphores} {} } + thread_count { {Objects Threads} {} } + handle_count { {Process "Handle Count" -instance _Total} {-format long} } + commit_limit { {Memory "Commit Limit"} {} } + committed_bytes { {Memory "Committed Bytes"} {} } + committed_percent { {Memory "% Committed Bytes In Use"} {-format double} } + memory_free_mb { {Memory "Available MBytes"} {} } + memory_free_kb { {Memory "Available KBytes"} {} } + page_fault_rate { {Memory "Page Faults/sec"} {} } + page_input_rate { {Memory "Pages Input/sec"} {} } + page_output_rate { {Memory "Pages Output/sec"} {} } + + disk_bytes_rate { {PhysicalDisk "Disk Bytes/sec" -instance _Total} {} } + disk_readbytes_rate { {PhysicalDisk "Disk Read Bytes/sec" -instance _Total} {} } + disk_writebytes_rate { {PhysicalDisk "Disk Write Bytes/sec" -instance _Total} {} } + disk_transfer_rate { {PhysicalDisk "Disk Transfers/sec" -instance _Total} {} } + disk_read_rate { {PhysicalDisk "Disk Reads/sec" -instance _Total} {} } + disk_write_rate { {PhysicalDisk "Disk Writes/sec" -instance _Total} {} } + disk_idle_percent { {PhysicalDisk "% Idle Time" -instance _Total} {-format double} } + } + + # Per-processor counters are based on above but the object name depends + # on the system in order to support > 64 processors + set obj_name [expr {[min_os_version 6 1] ? "Processor Information" : "Processor"}] + dict for {key ctr_name} { + interrupt_utilization "% Interrupt Time" + privileged_utilization "% Privileged Time" + processor_utilization "% Processor Time" + user_utilization "% User Time" + idle_utilization "% Idle Time" + } { + lappend _sysperf_defs $key \ + [list \ + [list $obj_name $ctr_name -instance _Total] \ + [list -format double]] + + lappend _sysperf_defs ${key}_per_cpu \ + [list \ + [list $obj_name $ctr_name -instance *] \ + [list -format double -array 1]] + } +} { + variable _sysperf_defs + + if {[llength $args] == 0} { + return [lsort -dictionary [dict keys $_sysperf_defs]] + } + + set qid [pdh_query_open] + trap { + foreach arg $args { + set def [dict! $_sysperf_defs $arg] + set ctr_path [pdh_counter_path {*}[lindex $def 0]] + pdh_add_counter $qid $ctr_path -name $arg {*}[lindex $def 1] + } + pdh_query_refresh $qid + } onerror {} { + pdh_query_close $qid + rethrow + } + + return $qid +} + +# +# Internal utility procedures +proc twapi::_pdh_query_check {qid} { + variable _pdh_queries + + if {![info exists _pdh_queries($qid)]} { + error "Invalid query id $qid" + } +} + +proc twapi::_perf_detail_sym_to_val {sym} { + # PERF_DETAIL_NOVICE 100 + # PERF_DETAIL_ADVANCED 200 + # PERF_DETAIL_EXPERT 300 + # PERF_DETAIL_WIZARD 400 + # PERF_DETAIL_COSTLY 0x00010000 + # PERF_DETAIL_STANDARD 0x0000FFFF + + return [dict get {novice 100 advanced 200 expert 300 wizard 400 costly 0x00010000 standard 0x0000ffff } $sym] +} + + +proc twapi::_pdh_fmt_sym_to_val {sym} { + # PDH_FMT_RAW 0x00000010 + # PDH_FMT_ANSI 0x00000020 + # PDH_FMT_UNICODE 0x00000040 + # PDH_FMT_LONG 0x00000100 + # PDH_FMT_DOUBLE 0x00000200 + # PDH_FMT_LARGE 0x00000400 + # PDH_FMT_NOSCALE 0x00001000 + # PDH_FMT_1000 0x00002000 + # PDH_FMT_NODATA 0x00004000 + # PDH_FMT_NOCAP100 0x00008000 + + return [dict get { + raw 0x00000010 + ansi 0x00000020 + unicode 0x00000040 + long 0x00000100 + double 0x00000200 + large 0x00000400 + noscale 0x00001000 + none 0x00001000 + 1000 0x00002000 + x1000 0x00002000 + nodata 0x00004000 + nocap100 0x00008000 + nocap 0x00008000 + } $sym] +} diff --git a/src/vendorlib_tcl8/twapi-5.0b1/pkgIndex.tcl b/src/vendorlib_tcl8/twapi-5.0b1/pkgIndex.tcl new file mode 100644 index 00000000..b67ac401 --- /dev/null +++ b/src/vendorlib_tcl8/twapi-5.0b1/pkgIndex.tcl @@ -0,0 +1,100 @@ +if {$::tcl_platform(platform) ne "windows"} { + return +} + +package ifneeded twapi_base 5.0b1 \ + [list apply [list {dir} { + package require platform + set packageVer [string map {. {}} 5.0b1] + if {[package vsatisfies [package require Tcl] 9]} { + set baseDllName "tcl9twapi50b1.dll" + } else { + set baseDllName "twapi50b1t.dll" + } + set package "twapi" + set package_ns ::$package + namespace eval $package_ns {} + set package_init_name [string totitle $package] + + # Try to load from current directory and if that fails try from + # platform-specific directories. Note on failure to load when the DLL + # exists, we do not try to load from other locations as twapi modules + # may have been partially set up. + + set dllFound false + foreach platform [linsert [::platform::patterns [platform::identify]] 0 .] { + if {$platform eq "tcl"} continue + set path [file join $dir $platform $baseDllName] + if {[file exists $path]} { + uplevel #0 [list load $path $package_init_name] + set dllFound true + break + } + } + + if {!$dllFound} { + error "Could not locate TWAPI dll." + } + + # Load was successful + set ${package_ns}::dllPath [file normalize $path] + set ${package_ns}::packageDir $dir + source [file join $dir twapi.tcl] + package provide twapi_base 5.0b1 + }] $dir] + +set __twapimods { + com + msi + power + printer + synch + security + account + apputil + clipboard + console + crypto + device + etw + eventlog + mstask + multimedia + namedpipe + network + nls + os + pdh + process + rds + registry + resource + service + share + shell + storage + ui + input + winsta + wmi +} +foreach __twapimod $__twapimods { + package ifneeded twapi_$__twapimod 5.0b1 \ + [list apply [list {dir mod} { + package require twapi_base 5.0b1 + source [file join $dir $mod.tcl] + package provide twapi_$mod 5.0b1 + }] $dir $__twapimod] +} + +package ifneeded twapi 5.0b1 \ + [list apply [list {dir mods} { + package require twapi_base 5.0b1 + foreach mod $mods { + package require twapi_$mod 5.0b1 + } + package provide twapi 5.0b1 + }] $dir $__twapimods] + +unset __twapimod +unset __twapimods diff --git a/src/vendorlib_tcl8/twapi4.7.2/power.tcl b/src/vendorlib_tcl8/twapi-5.0b1/power.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/power.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/power.tcl index f8a793c1..5bc39138 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/power.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/power.tcl @@ -1,136 +1,136 @@ -# -# Copyright (c) 2003-2012 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - variable _power_monitors - set _power_monitors [dict create] -} - -# Get the power status of the system -proc twapi::get_power_status {} { - lassign [GetSystemPowerStatus] ac battery lifepercent reserved lifetime fulllifetime - - set acstatus unknown - if {$ac == 0} { - set acstatus off - } elseif {$ac == 1} { - # Note only value 1 is "on", not just any non-0 value - set acstatus on - } - - set batterycharging unknown - if {$battery == -1} { - set batterystate unknown - } elseif {$battery & 128} { - set batterystate notpresent; # No battery - } else { - if {$battery & 8} { - set batterycharging true - } else { - set batterycharging false - } - if {$battery & 4} { - set batterystate critical - } elseif {$battery & 2} { - set batterystate low - } else { - set batterystate high - } - } - - set batterylifepercent unknown - if {$lifepercent >= 0 && $lifepercent <= 100} { - set batterylifepercent $lifepercent - } - - set batterylifetime $lifetime - if {$lifetime == -1} { - set batterylifetime unknown - } - - set batteryfulllifetime $fulllifetime - if {$fulllifetime == -1} { - set batteryfulllifetime unknown - } - - return [kl_create2 { - -acstatus - -batterystate - -batterycharging - -batterylifepercent - -batterylifetime - -batteryfulllifetime - } [list $acstatus $batterystate $batterycharging $batterylifepercent $batterylifetime $batteryfulllifetime]] -} - - -# Power notification callback -proc twapi::_power_handler {msg power_event lparam msgpos ticks} { - variable _power_monitors - - if {[dict size $_power_monitors] == 0} { - return; # Not an error, could have deleted while already queued - } - - if {![kl_vget { - 0 apmquerysuspend - 2 apmquerysuspendfailed - 4 apmsuspend - 6 apmresumecritical - 7 apmresumesuspend - 9 apmbatterylow - 10 apmpowerstatuschange - 11 apmoemevent - 18 apmresumeautomatic - } $power_event power_event]} { - return; # Do not support this event - } - - dict for {id script} $_power_monitors { - set code [catch {uplevel #0 [linsert $script end $power_event $lparam]} msg] - if {$code == 1} { - # Error - put in background but we do not abort - after 0 [list error $msg $::errorInfo $::errorCode] - } - } - return -} - -proc twapi::start_power_monitor {script} { - variable _power_monitors - - set script [lrange $script 0 end]; # Verify syntactically a list - - set id "power#[TwapiId]" - if {[dict size $_power_monitors] == 0} { - # No power monitoring in progress. Start it - # 0x218 -> WM_POWERBROADCAST - _register_script_wm_handler 0x218 [list [namespace current]::_power_handler] 1 - } - - dict set _power_monitors $id $script - return $id -} - - -# Stop monitoring of the power -proc twapi::stop_power_monitor {id} { - variable _power_monitors - - if {![dict exists $_power_monitors $id]} { - return - } - - dict unset _power_monitors $id - if {[dict size $_power_monitors] == 0} { - _unregister_script_wm_handler 0x218 [list [namespace current]::_power_handler] - } -} - -# Hack to work with the various build configuration. -if {[info commands ::twapi::get_version] ne ""} { - package provide twapi_power [::twapi::get_version -patchlevel] -} +# +# Copyright (c) 2003-2012 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + variable _power_monitors + set _power_monitors [dict create] +} + +# Get the power status of the system +proc twapi::get_power_status {} { + lassign [GetSystemPowerStatus] ac battery lifepercent reserved lifetime fulllifetime + + set acstatus unknown + if {$ac == 0} { + set acstatus off + } elseif {$ac == 1} { + # Note only value 1 is "on", not just any non-0 value + set acstatus on + } + + set batterycharging unknown + if {$battery == -1} { + set batterystate unknown + } elseif {$battery & 128} { + set batterystate notpresent; # No battery + } else { + if {$battery & 8} { + set batterycharging true + } else { + set batterycharging false + } + if {$battery & 4} { + set batterystate critical + } elseif {$battery & 2} { + set batterystate low + } else { + set batterystate high + } + } + + set batterylifepercent unknown + if {$lifepercent >= 0 && $lifepercent <= 100} { + set batterylifepercent $lifepercent + } + + set batterylifetime $lifetime + if {$lifetime == -1} { + set batterylifetime unknown + } + + set batteryfulllifetime $fulllifetime + if {$fulllifetime == -1} { + set batteryfulllifetime unknown + } + + return [kl_create2 { + -acstatus + -batterystate + -batterycharging + -batterylifepercent + -batterylifetime + -batteryfulllifetime + } [list $acstatus $batterystate $batterycharging $batterylifepercent $batterylifetime $batteryfulllifetime]] +} + + +# Power notification callback +proc twapi::_power_handler {msg power_event lparam msgpos ticks} { + variable _power_monitors + + if {[dict size $_power_monitors] == 0} { + return; # Not an error, could have deleted while already queued + } + + if {![kl_vget { + 0 apmquerysuspend + 2 apmquerysuspendfailed + 4 apmsuspend + 6 apmresumecritical + 7 apmresumesuspend + 9 apmbatterylow + 10 apmpowerstatuschange + 11 apmoemevent + 18 apmresumeautomatic + } $power_event power_event]} { + return; # Do not support this event + } + + dict for {id script} $_power_monitors { + set code [catch {uplevel #0 [linsert $script end $power_event $lparam]} msg] + if {$code == 1} { + # Error - put in background but we do not abort + after 0 [list error $msg $::errorInfo $::errorCode] + } + } + return +} + +proc twapi::start_power_monitor {script} { + variable _power_monitors + + set script [lrange $script 0 end]; # Verify syntactically a list + + set id "power#[TwapiId]" + if {[dict size $_power_monitors] == 0} { + # No power monitoring in progress. Start it + # 0x218 -> WM_POWERBROADCAST + _register_script_wm_handler 0x218 [list [namespace current]::_power_handler] 1 + } + + dict set _power_monitors $id $script + return $id +} + + +# Stop monitoring of the power +proc twapi::stop_power_monitor {id} { + variable _power_monitors + + if {![dict exists $_power_monitors $id]} { + return + } + + dict unset _power_monitors $id + if {[dict size $_power_monitors] == 0} { + _unregister_script_wm_handler 0x218 [list [namespace current]::_power_handler] + } +} + +# Hack to work with the various build configuration. +if {[info commands ::twapi::get_version] ne ""} { + package provide twapi_power [::twapi::get_version -patchlevel] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/printer.tcl b/src/vendorlib_tcl8/twapi-5.0b1/printer.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/printer.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/printer.tcl index d73af00d..c46ba564 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/printer.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/printer.tcl @@ -1,58 +1,58 @@ -# -# Copyright (c) 2004-2006 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi {} - -proc twapi::enumerate_printers {args} { - array set opts [parseargs args { - {proximity.arg all {local remote all any}} - } -maxleftover 0] - - set result [list ] - foreach elem [Twapi_EnumPrinters_Level4 \ - [string map {all 6 any 6 local 2 remote 4} $opts(proximity)] \ - ] { - lappend result [list [lindex $elem 0] [lindex $elem 1] \ - [_symbolize_printer_attributes [lindex $elem 2]]] - } - return [list {-name -server -attrs} $result] -} - - -# Utilities -# -proc twapi::_symbolize_printer_attributes {attr} { - return [_make_symbolic_bitmask $attr { - queued 0x00000001 - direct 0x00000002 - default 0x00000004 - shared 0x00000008 - network 0x00000010 - hidden 0x00000020 - local 0x00000040 - enabledevq 0x00000080 - keepprintedjobs 0x00000100 - docompletefirst 0x00000200 - workoffline 0x00000400 - enablebidi 0x00000800 - rawonly 0x00001000 - published 0x00002000 - fax 0x00004000 - ts 0x00008000 - pusheduser 0x00020000 - pushedmachine 0x00040000 - machine 0x00080000 - friendlyname 0x00100000 - tsgenericdriver 0x00200000 - peruser 0x00400000 - enterprisecloud 0x00800000 - }] -} - -# Hack to work with the various build configuration. -if {[info commands ::twapi::get_version] ne ""} { - package provide twapi_printer [::twapi::get_version -patchlevel] -} +# +# Copyright (c) 2004-2006 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi {} + +proc twapi::enumerate_printers {args} { + array set opts [parseargs args { + {proximity.arg all {local remote all any}} + } -maxleftover 0] + + set result [list ] + foreach elem [Twapi_EnumPrinters_Level4 \ + [string map {all 6 any 6 local 2 remote 4} $opts(proximity)] \ + ] { + lappend result [list [lindex $elem 0] [lindex $elem 1] \ + [_symbolize_printer_attributes [lindex $elem 2]]] + } + return [list {-name -server -attrs} $result] +} + + +# Utilities +# +proc twapi::_symbolize_printer_attributes {attr} { + return [_make_symbolic_bitmask $attr { + queued 0x00000001 + direct 0x00000002 + default 0x00000004 + shared 0x00000008 + network 0x00000010 + hidden 0x00000020 + local 0x00000040 + enabledevq 0x00000080 + keepprintedjobs 0x00000100 + docompletefirst 0x00000200 + workoffline 0x00000400 + enablebidi 0x00000800 + rawonly 0x00001000 + published 0x00002000 + fax 0x00004000 + ts 0x00008000 + pusheduser 0x00020000 + pushedmachine 0x00040000 + machine 0x00080000 + friendlyname 0x00100000 + tsgenericdriver 0x00200000 + peruser 0x00400000 + enterprisecloud 0x00800000 + }] +} + +# Hack to work with the various build configuration. +if {[info commands ::twapi::get_version] ne ""} { + package provide twapi_printer [::twapi::get_version -patchlevel] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/process.tcl b/src/vendorlib_tcl8/twapi-5.0b1/process.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/process.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/process.tcl index 5f37800b..2c1057ad 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/process.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/process.tcl @@ -1,2028 +1,2028 @@ -# -# Copyright (c) 2003-2020, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi {} - - -# Create a process -# TBD Use https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ -# to construct -cmdline value -proc twapi::create_process {path args} { - array set opts [parseargs args { - {debugchildtree.bool 0 0x1} - {debugchild.bool 0 0x2} - {createsuspended.bool 0 0x4} - {detached.bool 0 0x8} - {newconsole.bool 0 0x10} - {newprocessgroup.bool 0 0x200} - {separatevdm.bool 0 0x800} - {sharedvdm.bool 0 0x1000} - {inheriterrormode.bool 1 0x04000000} - {noconsole.bool 0 0x08000000} - {priority.arg normal {normal abovenormal belownormal high realtime idle}} - - {feedbackcursoron.bool 0 0x40} - {feedbackcursoroff.bool 0 0x80} - {fullscreen.bool 0 0x20} - - {cmdline.arg ""} - {inheritablechildprocess.bool 0} - {inheritablechildthread.bool 0} - {childprocesssecd.arg ""} - {childthreadsecd.arg ""} - {inherithandles.bool 0} - {env.arg ""} - {startdir.arg ""} - {desktop.arg __null__} - {title.arg ""} - windowpos.arg - windowsize.arg - screenbuffersize.arg - background.arg - foreground.arg - {showwindow.arg ""} - {stdhandles.arg ""} - {stdchannels.arg ""} - {returnhandles.bool 0} - - token.arg - } -maxleftover 0] - - set process_sec_attr [_make_secattr $opts(childprocesssecd) $opts(inheritablechildprocess)] - set thread_sec_attr [_make_secattr $opts(childthreadsecd) $opts(inheritablechildthread)] - - # Check incompatible options - if {$opts(newconsole) && $opts(detached)} { - error "Options -newconsole and -detached cannot be specified together" - } - if {$opts(sharedvdm) && $opts(separatevdm)} { - error "Options -sharedvdm and -separatevdm cannot be specified together" - } - - # Create the start up info structure - set si_flags 0 - if {[info exists opts(windowpos)]} { - lassign [_parse_integer_pair $opts(windowpos)] xpos ypos - setbits si_flags 0x4 - } else { - set xpos 0 - set ypos 0 - } - if {[info exists opts(windowsize)]} { - lassign [_parse_integer_pair $opts(windowsize)] xsize ysize - setbits si_flags 0x2 - } else { - set xsize 0 - set ysize 0 - } - if {[info exists opts(screenbuffersize)]} { - lassign [_parse_integer_pair $opts(screenbuffersize)] xscreen yscreen - setbits si_flags 0x8 - } else { - set xscreen 0 - set yscreen 0 - } - - set fg 7; # Default to white - set bg 0; # Default to black - if {[info exists opts(foreground)]} { - set fg [_map_console_color $opts(foreground) 0] - setbits si_flags 0x10 - } - if {[info exists opts(background)]} { - set bg [_map_console_color $opts(background) 1] - setbits si_flags 0x10 - } - - set si_flags [expr {$si_flags | - $opts(feedbackcursoron) | $opts(feedbackcursoroff) | - $opts(fullscreen)}] - - switch -exact -- $opts(showwindow) { - "" {set opts(showwindow) 1 } - hidden {set opts(showwindow) 0} - normal {set opts(showwindow) 1} - minimized {set opts(showwindow) 2} - maximized {set opts(showwindow) 3} - default {error "Invalid value '$opts(showwindow)' for -showwindow option"} - } - if {[string length $opts(showwindow)]} { - setbits si_flags 0x1 - } - - if {[llength $opts(stdhandles)] && [llength $opts(stdchannels)]} { - error "Options -stdhandles and -stdchannels cannot be used together" - } - - if {[llength $opts(stdhandles)]} { - if {! $opts(inherithandles)} { - error "Cannot specify -stdhandles option if option -inherithandles is specified as 0" - } - - setbits si_flags 0x100 - } - - # Figure out process creation flags - # 0x400 -> CREATE_UNICODE_ENVIRONMENT - set flags [expr {0x00000400 | - $opts(createsuspended) | $opts(debugchildtree) | - $opts(debugchild) | $opts(detached) | $opts(newconsole) | - $opts(newprocessgroup) | $opts(separatevdm) | - $opts(sharedvdm) | $opts(inheriterrormode) | - $opts(noconsole) }] - - switch -exact -- $opts(priority) { - normal {set priority 0x00000020} - abovenormal {set priority 0x00008000} - belownormal {set priority 0x00004000} - "" {set priority 0} - high {set priority 0x00000080} - realtime {set priority 0x00000100} - idle {set priority 0x00000040} - default {error "Unknown priority '$priority'"} - } - set flags [expr {$flags | $priority}] - - # Create the environment strings - if {[llength $opts(env)]} { - set child_env [list ] - foreach {envvar envval} $opts(env) { - lappend child_env "$envvar=$envval" - } - } else { - set child_env "__null__" - } - - trap { - # This is inside the trap because duplicated handles have - # to be closed. - if {[llength $opts(stdchannels)]} { - if {! $opts(inherithandles)} { - error "Cannot specify -stdhandles option if option -inherithandles is specified as 0" - } - if {[llength $opts(stdchannels)] != 3} { - error "Must specify 3 channels for -stdchannels option corresponding stdin, stdout and stderr" - } - - setbits si_flags 0x100 - - # Convert the channels to handles - lappend opts(stdhandles) [duplicate_handle [get_tcl_channel_handle [lindex $opts(stdchannels) 0] read] -inherit] - lappend opts(stdhandles) [duplicate_handle [get_tcl_channel_handle [lindex $opts(stdchannels) 1] write] -inherit] - lappend opts(stdhandles) [duplicate_handle [get_tcl_channel_handle [lindex $opts(stdchannels) 2] write] -inherit] - } - - set startup [list $opts(desktop) $opts(title) $xpos $ypos \ - $xsize $ysize $xscreen $yscreen \ - [expr {$fg|$bg}] $si_flags $opts(showwindow) \ - $opts(stdhandles)] - - if {[info exists opts(token)]} { - lassign [CreateProcessAsUser $opts(token) [file nativename $path] \ - $opts(cmdline) \ - $process_sec_attr $thread_sec_attr \ - $opts(inherithandles) $flags $child_env \ - [file normalize $opts(startdir)] $startup \ - ] ph th pid tid - - } else { - lassign [CreateProcess [file nativename $path] \ - $opts(cmdline) \ - $process_sec_attr $thread_sec_attr \ - $opts(inherithandles) $flags $child_env \ - [file normalize $opts(startdir)] $startup \ - ] ph th pid tid - } - } finally { - # If opts(stdchannels) is not an empty list, we duplicated the handles - # into opts(stdhandles) ourselves so free them - if {[llength $opts(stdchannels)]} { - # Free corresponding handles in opts(stdhandles) - close_handles $opts(stdhandles) - } - } - - # From the Tcl source code - (tclWinPipe.c) - # /* - # * "When an application spawns a process repeatedly, a new thread - # * instance will be created for each process but the previous - # * instances may not be cleaned up. This results in a significant - # * virtual memory loss each time the process is spawned. If there - # * is a WaitForInputIdle() call between CreateProcess() and - # * CloseHandle(), the problem does not occur." PSS ID Number: Q124121 - # */ - # WaitForInputIdle $ph 5000 -- Apparently this is only needed for NT 3.5 - - - if {$opts(returnhandles)} { - return [list $pid $tid $ph $th] - } else { - CloseHandle $th - CloseHandle $ph - return [list $pid $tid] - } -} - -# Wait until the process is ready -proc twapi::process_waiting_for_input {pid args} { - array set opts [parseargs args { - {wait.int 0} - } -maxleftover 0] - - if {$pid == [pid]} { - variable my_process_handle - return [WaitForInputIdle $my_process_handle $opts(wait)] - } - - set hpid [get_process_handle $pid] - trap { - return [WaitForInputIdle $hpid $opts(wait)] - } finally { - CloseHandle $hpid - } -} - - - -# Get a handle to a process -proc twapi::get_process_handle {pid args} { - # OpenProcess masks off the bottom two bits thereby converting - # an invalid pid to a real one. - if {(![string is integer -strict $pid]) || ($pid & 3)} { - win32_error 87 "Invalid PID '$pid'."; # "The parameter is incorrect" - } - array set opts [parseargs args { - {access.arg process_query_information} - {inherit.bool 0} - } -maxleftover 0] - return [OpenProcess [_access_rights_to_mask $opts(access)] $opts(inherit) $pid] -} - -# Return true if passed pid is system -proc twapi::is_system_pid {pid} { - # Note Windows 2000 System PID was 8 but we no longer support it. - return [expr {$pid == 4}] -} - -# Return true if passed pid is of idle process -proc twapi::is_idle_pid {pid} { - return [expr {$pid == 0}] -} - -# Get my process id -proc twapi::get_current_process_id {} { - return [::pid] -} - -# Get my thread id -proc twapi::get_current_thread_id {} { - return [GetCurrentThreadId] -} - -# Get the exit code for a process. Returns "" if still running. -proc twapi::get_process_exit_code {hpid} { - set code [GetExitCodeProcess $hpid] - return [expr {$code == 259 ? "" : $code}] -} - -# Return list of process ids -# Note if -path or -name is specified, then processes for which this -# information cannot be obtained are skipped -proc twapi::get_process_ids {args} { - - set save_args $args; # Need to pass to process_exists - array set opts [parseargs args { - user.arg - path.arg - name.arg - logonsession.arg - glob} -maxleftover 0] - - if {[info exists opts(path)] && [info exists opts(name)]} { - error "Options -path and -name are mutually exclusive" - } - - if {$opts(glob)} { - set match_op ~ - } else { - set match_op eq - } - - # If we do not care about user or path, Twapi_GetProcessList - # is faster than EnumProcesses or the WTS functions - if {[info exists opts(user)] == 0 && - [info exists opts(logonsession)] == 0 && - [info exists opts(path)] == 0} { - if {[info exists opts(name)] == 0} { - return [Twapi_GetProcessList -1 0] - } - # We need to match against the name - return [recordarray column [Twapi_GetProcessList -1 2] -pid \ - -filter [list [list "-name" $match_op $opts(name) -nocase]]] - } - - # Only want pids with a specific user or path or logon session - - # If is the name we are looking for, try using the faster WTS - # API's first. If they are not available, we try a slower method - # If we need to match paths or logon sessions, we don't try this - # at all as the wts api's don't provide that info - if {[info exists opts(path)] == 0 && - [info exists opts(logonsession)] == 0} { - if {![info exists opts(user)]} { - # How did we get here? - error "Internal error - option -user not specified where expected" - } - if {[catch {map_account_to_sid $opts(user)} sid]} { - # No such user. Return empty list (no processes) - return [list ] - } - - if {[info exists opts(name)]} { - set filter_expr [list [list pUserSid eq $sid -nocase] [list pProcessName $match_op $opts(name) -nocase]] - } else { - set filter_expr [list [list pUserSid eq $sid -nocase]] - } - - # Catch failures so we can try other means - if {! [catch {recordarray column [WTSEnumerateProcesses NULL] \ - ProcessId -filter $filter_expr} wtslist]} { - return $wtslist - } - } - - set process_pids [list ] - - - # Either we are matching on path/logonsession, or the WTS call failed - # Try yet another way. - - # Note that in the code below, we use "file join" with a single arg - # to convert \ to /. Do not use file normalize as that will also - # land up converting relative paths to full paths - if {[info exists opts(path)]} { - set opts(path) [file join $opts(path)] - } - - set process_pids [list ] - if {[info exists opts(name)]} { - # Note we may reach here if the WTS call above failed - set all_pids [recordarray column [Twapi_GetProcessList -1 2] ProcessId -filter [list [list ProcessName $match_op $opts(name) -nocase]]] - } else { - set all_pids [Twapi_GetProcessList -1 0] - } - - set filter_expr {} - set popts [list ] - if {[info exists opts(path)]} { - lappend popts -path - lappend filter_expr [list -path $match_op $opts(path) -nocase] - } - - if {[info exists opts(user)]} { - lappend popts -user - lappend filter_expr [list -user eq $opts(user) -nocase] - } - if {[info exists opts(logonsession)]} { - lappend popts -logonsession - lappend filter_expr [list -logonsession eq $opts(logonsession) -nocase] - } - - - set matches [recordarray get [get_multiple_process_info -matchpids $all_pids {*}$popts] -filter $filter_expr] - return [recordarray column $matches -pid] -} - -proc twapi::get_process_memory_info {{pid {}}} { - variable my_process_handle - - if {$pid eq "" || $pid == [pid]} { - set hpid $my_process_handle - } else { - set hpid [get_process_handle $pid -access {process_query_information process_vm_read}] - } - - try { - # Note: -pagefileusage and -privateusage are same according to SDK. - # However for Win7 and earlier, -pagefileusage is always set to 0. - # We return what was given and not try to fix it up. - return [twine { - -pagefaults -workingsetpeak -workingset - -poolpagedbytespeak -poolpagedbytes - -poolnonpagedbytespeak -poolnonpagedbytes - -pagefilebytes -pagefilebytespeak -privatebytes - } [GetProcessMemoryInfo $hpid]] - } finally { - if {$hpid != $my_process_handle} { - CloseHandle $hpid - } - } -} - -# Return list of modules handles for a process -proc twapi::get_process_modules {pid args} { - variable my_process_handle - - array set opts [parseargs args {handle name path base size entry all}] - - if {$opts(all)} { - foreach opt {handle name path base size entry} { - set opts($opt) 1 - } - } - set noopts [expr {($opts(name) || $opts(path) || $opts(base) || $opts(size) || $opts(entry) || $opts(handle)) == 0}] - - if {! $noopts} { - # Returning a record array - set fields {} - # ORDER MUST be same a value order below - foreach opt {handle name path base size entry} { - if {$opts($opt)} { - lappend fields -$opt - } - } - - } - - if {$pid == [pid]} { - set hpid $my_process_handle - } else { - set hpid [get_process_handle $pid -access {process_query_information process_vm_read}] - } - - set results [list ] - trap { - foreach module [EnumProcessModules $hpid] { - if {$noopts} { - lappend results $module - continue - } - set rec {} - if {$opts(handle)} { - lappend rec $module - } - if {$opts(name)} { - if {[catch {GetModuleBaseName $hpid $module} name]} { - set name "" - } - lappend rec $name - } - if {$opts(path)} { - if {[catch {GetModuleFileNameEx $hpid $module} path]} { - set path "" - } - lappend rec [_normalize_path $path] - } - if {$opts(base) || $opts(size) || $opts(entry)} { - if {[catch {GetModuleInformation $hpid $module} imagedata]} { - set base "" - set size "" - set entry "" - } else { - lassign $imagedata base size entry - } - foreach opt {base size entry} { - if {$opts($opt)} { - lappend rec [set $opt] - } - } - } - lappend results $rec - } - } finally { - if {$hpid != $my_process_handle} { - CloseHandle $hpid - } - } - - if {$noopts} { - return $results - } else { - return [list $fields $results] - } -} - - -# Kill a process -# Returns 1 if process was ended, 0 if not ended within timeout -proc twapi::end_process {pid args} { - - if {$pid == [pid]} { - error "The passed PID is the PID of the current process. end_process cannot be used to commit suicide." - } - - array set opts [parseargs args { - {exitcode.int 1} - force - {wait.int 0} - }] - - # In order to verify the process is really gone, we open the process - # if possible and then wait on its handle. If access restrictions prevent - # us from doing so, we ignore the issue and will simply check for the - # the PID later (which is not a sure check since PID's can be reused - # immediately) - catch {set hproc [get_process_handle $pid -access synchronize]} - - # First try to close nicely. We need to send messages to toplevels - # as well as message-only windows. We could make use of get_toplevel_windows - # and find_windows but those would require pulling in the whole - # twapi_ui package so do it ourselves. - set toplevels {} - foreach toplevel [EnumWindows] { - # Check if it belongs to pid. Errors are ignored, we simply - # will not send a message to that window - catch { - if {[lindex [GetWindowThreadProcessId $toplevel] 1] == $pid} { - lappend toplevels $toplevel - } - } - } - # Repeat for message only windows as EnumWindows skips them - set prev 0 - while {1} { - # Again, errors are ignored - # -3 -> HWND_MESSAGE windows - if {[catch { - set toplevel [FindWindowEx [list -3 HWND] $prev "" ""] - }]} { - break - } - if {[pointer_null? $toplevel]} break - catch { - if {[lindex [GetWindowThreadProcessId $toplevel] 1] == $pid} { - lappend toplevels $toplevel - } - } - set prev $toplevel - } - - if {[llength $toplevels]} { - # Try and close by sending them a message. WM_CLOSE is 0x10 - foreach toplevel $toplevels { - # Send a message but come back right away - # See Bug #139 as to why PostMessage instead of SendNotifyMessage - catch {PostMessage $toplevel 0x10 0 0} - } - - # Wait for the specified time to verify process has gone away - if {[info exists hproc]} { - set status [WaitForSingleObject $hproc $opts(wait)] - CloseHandle $hproc - set gone [expr {! $status}] - } else { - # We could not get a process handle to wait on, just check if - # PID still exists. This COULD be a false positive... - set gone [twapi::wait {process_exists $pid} 0 $opts(wait)] - } - if {$gone || ! $opts(force)} { - # Succeeded or do not want to force a kill - return $gone - } - - # Only wait 10 ms since we have already waited above - if {$opts(wait)} { - set opts(wait) 10 - } - } - - # Open the process for terminate access. IF access denied (5), retry after - # getting the required privilege - trap { - set hproc [get_process_handle $pid -access {synchronize process_terminate}] - } onerror {TWAPI_WIN32 5} { - # Retry - if still fail, then just throw the error - eval_with_privileges { - set hproc [get_process_handle $pid -access {synchronize process_terminate}] - } SeDebugPrivilege - } onerror {TWAPI_WIN32 87} { - # Process does not exist, we must have succeeded above but just - # took a bit longer for it to exit - return 1 - } - - trap { - TerminateProcess $hproc $opts(exitcode) - set status [WaitForSingleObject $hproc $opts(wait)] - if {$status == 0} { - return 1 - } - } finally { - CloseHandle $hproc - } - - return 0 -} - -# Get the path of a process -proc twapi::get_process_path {pid args} { - return [twapi::_get_process_name_path_helper $pid path {*}$args] -} - -# Get the path of a process -proc twapi::get_process_name {pid args} { - return [twapi::_get_process_name_path_helper $pid name {*}$args] -} - - -# Return list of device drivers -proc twapi::get_device_drivers {args} { - array set opts [parseargs args {name path base all}] - - set fields {} - # Order MUST be same as order of values below - foreach opt {base name path} { - if {$opts($opt) || $opts(all)} { - lappend fields -$opt - } - } - - set results [list ] - foreach module [EnumDeviceDrivers] { - unset -nocomplain rec - if {$opts(base) || $opts(all)} { - lappend rec $module - } - if {$opts(name) || $opts(all)} { - if {[catch {GetDeviceDriverBaseName $module} name]} { - set name "" - } - lappend rec $name - } - if {$opts(path) || $opts(all)} { - if {[catch {GetDeviceDriverFileName $module} path]} { - set path "" - } - lappend rec [_normalize_path $path] - } - if {[info exists rec]} { - lappend results $rec - } - } - - return [list $fields $results] -} - -# Check if the given process exists -# 0 - does not exist or exists but paths/names do not match, -# 1 - exists and matches path (or no -path or -name specified) -# -1 - exists but do not know path and cannot compare -proc twapi::process_exists {pid args} { - array set opts [parseargs args { path.arg name.arg glob}] - - # Simplest case - don't care about name or path - if {! ([info exists opts(path)] || [info exists opts(name)])} { - if {$pid == [pid]} { - return 1 - } - # TBD - would it be faster to do OpenProcess ? If success or - # access denied, process exists. - - if {[llength [Twapi_GetProcessList $pid 0]] == 0} { - return 0 - } else { - return 1 - } - } - - # Can't specify both name and path - if {[info exists opts(path)] && [info exists opts(name)]} { - error "Options -path and -name are mutually exclusive" - } - - if {$opts(glob)} { - set string_cmd match - } else { - set string_cmd equal - } - - if {[info exists opts(name)]} { - # Name is specified - set pidlist [Twapi_GetProcessList $pid 2] - if {[llength $pidlist] == 0} { - return 0 - } - return [string $string_cmd -nocase $opts(name) [lindex $pidlist 1 0 1]] - } - - # Need to match on the path - set process_path [get_process_path $pid -noexist "" -noaccess "(unknown)"] - if {[string length $process_path] == 0} { - # No such process - return 0 - } - - # Process with this pid exists - # Path still has to match - if {[string equal $process_path "(unknown)"]} { - # Exists but cannot check path/name - return -1 - } - - # Note we do not use file normalize here since that will tack on - # absolute paths which we do not want for glob matching - - # We use [file join ] to convert \ to / to avoid special - # interpretation of \ in string match command - return [string $string_cmd -nocase [file join $opts(path)] [file join $process_path]] -} - -# Get the parent process of a thread. Return "" if no such thread -proc twapi::get_thread_parent_process_id {tid} { - set status [catch { - set th [get_thread_handle $tid] - trap { - set pid [lindex [lindex [Twapi_NtQueryInformationThreadBasicInformation $th] 2] 0] - } finally { - CloseHandle $th - } - }] - - if {$status == 0} { - return $pid - } - - - # Could not use undocumented function. Try slooooow perf counter method - set pid_paths [get_perf_thread_counter_paths $tid -pid] - if {[llength $pid_paths] == 0} { - return "" - } - - if {[pdh_counter_path_value [lindex [lindex $pid_paths 0] 3] -var pid]} { - return $pid - } else { - return "" - } -} - -# Get the thread ids belonging to a process -proc twapi::get_process_thread_ids {pid} { - return [recordarray cell [get_multiple_process_info -matchpids [list $pid] -tids] 0 -tids] -} - - -# Get process information -proc twapi::get_process_info {pid args} { - # To avert a common mistake where pid is unspecified, use current pid - # so [get_process_info -name] becomes [get_process_info [pid] -name] - # TBD - should this be documented ? - - if {![string is integer -strict $pid]} { - set args [linsert $args 0 $pid] - set pid [pid] - } - - set rec [recordarray index [get_multiple_process_info {*}$args -matchpids [list $pid]] 0 -format dict] - if {"-pid" ni $args && "-all" ni $args} { - dict unset rec -pid - } - return $rec -} - - -# Get multiple process information -# TBD - document and write tests -proc twapi::get_multiple_process_info {args} { - - # Options that are directly available from Twapi_GetProcessList - # Dict value is the flags to pass to Twapi_GetProcessList - set base_opts { - basepriority 1 - parent 1 tssession 1 - name 2 - createtime 4 usertime 4 - privilegedtime 4 handlecount 4 - threadcount 4 - pagefaults 8 pagefilebytes 8 - pagefilebytespeak 8 poolnonpagedbytes 8 - poolnonpagedbytespeak 8 poolpagedbytes 8 - poolpagedbytespeak 8 virtualbytes 8 - virtualbytespeak 8 workingset 8 - workingsetpeak 8 - ioreadops 16 iowriteops 16 - iootherops 16 ioreadbytes 16 - iowritebytes 16 iootherbytes 16 - } - # Options that also dependent on Twapi_GetProcessList but not - # directly available - set base_calc_opts { elapsedtime 4 tids 32 } - - # Note -user is also a potential token opt but not listed below - # because it can be gotten by other means - set token_opts { - disabledprivileges elevation enabledprivileges groupattrs groups groupsids - integrity integritylabel logonsession primarygroup primarygroupsid - privileges restrictedgroupattrs restrictedgroups virtualized - } - - set optdefs [lconcat {all pid user path commandline priorityclass {noexist.arg {(no such process)}} {noaccess.arg {(unknown)}} matchpids.arg} \ - [dict keys $base_opts] \ - [dict keys $base_calc_opts] \ - $token_opts] - array set opts [parseargs args $optdefs -maxleftover 0] - set opts(pid) 1; # Always return pid, -pid option is for backward compat - - if {[info exists opts(matchpids)]} { - set pids $opts(matchpids) - } else { - set pids [Twapi_GetProcessList -1 0] - } - - set now [get_system_time] - - # We will return a record array. $records tracks a dict of record - # values keyed by pid, $fields tracks the names in the list elements - # [llength $fields] == [llength [lindex $records *]] - set records {} - set fields {} - - # If user is requested, try getting it through terminal services - # if possible since the token method fails on some newer platforms - if {$opts(all) || $opts(user)} { - _get_wts_pids wtssids wtsnames - } - - # See if any Twapi_GetProcessList options are requested and if - # so, calculate the appropriate flags - set baseflags 0 - set basenoexistvals {} - dict for {opt flag} $base_opts { - if {$opts($opt) || $opts(all)} { - set baseflags [expr {$baseflags | $flag}] - lappend basefields -$opt - lappend basenoexistvals $opts(noexist) - } - } - dict for {opt flag} $base_calc_opts { - if {$opts($opt) || $opts(all)} { - set baseflags [expr {$baseflags | $flag}] - } - } - - # See if we need to retrieve any base options - if {$baseflags} { - set pidarg [expr {[llength $pids] == 1 ? [lindex $pids 0] : -1}] - set data [twapi::Twapi_GetProcessList $pidarg [expr {$baseflags|1}]] - if {$opts(all) || $opts(elapsedtime) || $opts(tids)} { - array set baserawdata [recordarray getdict $data -key "-pid" -format dict] - } - if {[info exists basefields]} { - set fields $basefields - set records [recordarray getdict $data -slice $basefields -key "-pid"] - } - } - if {$opts(pid)} { - lappend fields -pid - } - foreach pid $pids { - # If base values were requested, but this pid does not exist - # use the "noexist" values - if {![dict exists $records $pid]} { - dict set records $pid $basenoexistvals - } - if {$opts(pid)} { - dict lappend records $pid $pid - } - } - - # If all we need are baseline options, and no massaging is required - # (as for elapsedtime, for example), we can return what we have - # without looping through below. Saves significant time. - set done 1 - foreach opt [list all user elapsedtime tids path commandline priorityclass \ - {*}$token_opts] { - if {$opts($opt)} { - set done 0 - break - } - } - - if {$done} { - set return_data {} - foreach pid $pids { - lappend return_data [dict get $records $pid] - } - return [list $fields $return_data] - } - - set requested_token_opts {} - foreach opt $token_opts { - if {$opts(all) || $opts($opt)} { - lappend requested_token_opts -$opt - } - } - - if {$opts(elapsedtime) || $opts(all)} { - lappend fields -elapsedtime - foreach pid $pids { - if {[info exists baserawdata($pid)]} { - set elapsed [twapi::kl_get $baserawdata($pid) -createtime] - if {$elapsed} { - # 100ns -> seconds - dict lappend records $pid [expr {($now-$elapsed)/10000000}] - } else { - # For some processes like, System and Idle, kernel - # returns start time of 0. Just use system uptime - if {![info exists system_uptime]} { - # Store locally so no refetch on each iteration - set system_uptime [get_system_uptime] - } - dict lappend records $pid $system_uptime - } - } else { - dict lappend records $pid $opts(noexist) - } - } - } - - if {$opts(tids) || $opts(all)} { - lappend fields -tids - foreach pid $pids { - if {[info exists baserawdata($pid)]} { - dict lappend records $pid [recordarray column [kl_get $baserawdata($pid) Threads] -tid] - } else { - dict lappend records $pid $opts(noexist) - } - } - } - - if {$opts(all) || $opts(path)} { - lappend fields -path - foreach pid $pids { - dict lappend records $pid [get_process_path $pid -noexist $opts(noexist) -noaccess $opts(noaccess)] - } - } - - if {$opts(all) || $opts(priorityclass)} { - lappend fields -priorityclass - foreach pid $pids { - trap { - set prioclass [get_priority_class $pid] - } onerror {TWAPI_WIN32 5} { - set prioclass $opts(noaccess) - } onerror {TWAPI_WIN32 87} { - set prioclass $opts(noexist) - } - dict lappend records $pid $prioclass - } - } - - if {$opts(all) || $opts(commandline)} { - lappend fields -commandline - foreach pid $pids { - dict lappend records $pid [get_process_commandline $pid -noexist $opts(noexist) -noaccess $opts(noaccess)] - } - } - - - if {$opts(all) || $opts(user) || [llength $requested_token_opts]} { - foreach pid $pids { - # Now get token related info, if any requested - # For returning as a record array, we have to be careful that - # each field is added in a specific order for every pid - # keeping in mind a different method might be used for different - # pids. So we collect the data in dictionary token_records and add - # at the end in a fixed order - set token_records {} - set requested_opts $requested_token_opts - unset -nocomplain user - if {$opts(all) || $opts(user)} { - # See if we already have the user. Note sid of system idle - # will be empty string - if {[info exists wtssids($pid)]} { - if {$wtssids($pid) == ""} { - # Put user as System - set user SYSTEM - } else { - # We speed up account lookup by caching sids - if {[info exists sidcache($wtssids($pid))]} { - set user $sidcache($wtssids($pid)) - } else { - set user [lookup_account_sid $wtssids($pid)] - set sidcache($wtssids($pid)) $user - } - } - } else { - lappend requested_opts -user - } - } - - if {[llength $requested_opts]} { - trap { - dict set token_records $pid [_token_info_helper -pid $pid {*}$requested_opts] - } onerror {TWAPI_WIN32 5} { - foreach opt $requested_opts { - dict set token_records $pid $opt $opts(noaccess) - } - # The NETWORK SERVICE and LOCAL SERVICE processes cannot - # be accessed. If we are looking for the logon session for - # these, try getting it from the witssid if we have it - # since the logon session is hardcoded for these accounts - if {"-logonsession" in $requested_opts} { - if {![info exists wtssids]} { - _get_wts_pids wtssids wtsnames - } - if {[info exists wtssids($pid)]} { - # Map user SID to logon session - switch -exact -- $wtssids($pid) { - S-1-5-18 { - # SYSTEM - dict set token_records $pid -logonsession 00000000-000003e7 - } - S-1-5-19 { - # LOCAL SERVICE - dict set token_records $pid -logonsession 00000000-000003e5 - } - S-1-5-20 { - # LOCAL SERVICE - dict set token_records $pid -logonsession 00000000-000003e4 - } - } - } - } - - # Similarly, if we are looking for user account, special case - # system and system idle processes - if {"-user" in $requested_opts} { - if {[is_idle_pid $pid] || [is_system_pid $pid]} { - set user SYSTEM - } else { - set user $opts(noaccess) - } - } - - } onerror {TWAPI_WIN32 87} { - foreach opt $requested_opts { - if {$opt eq "-user"} { - if {[is_idle_pid $pid] || [is_system_pid $pid]} { - set user SYSTEM - } else { - set user $opts(noexist) - } - } else { - dict set token_records $pid $opt $opts(noexist) - } - } - } - } - # Now add token values in a specific order - MUST MATCH fields BELOW - if {$opts(all) || $opts(user)} { - # TBD - BUG - user is supposed to be set to *something* by this - # point but WiTS throws error every blue moon on this line that - # user is not defined. Workaround. - if {![info exists user]} { - set user $opts(noaccess) - } - dict lappend records $pid $user - } - foreach opt $requested_token_opts { - if {[dict exists $token_records $pid $opt]} { - dict lappend records $pid [dict get $token_records $pid $opt] - } - } - } - # Now add token field names in a specific order - MUST MATCH ABOVE - if {$opts(all) || $opts(user)} { - lappend fields -user - } - foreach opt $requested_token_opts { - if {[dict exists $token_records $pid $opt]} { - lappend fields $opt - } - } - } - - set return_data {} - foreach pid $pids { - lappend return_data [dict get $records $pid] - } - return [list $fields $return_data] -} - - - -# Get thread information -# TBD - add info from GetGUIThreadInfo -proc twapi::get_thread_info {tid args} { - # TBD - modify so tid is optional like for get_process_info - - # Options that are directly available from Twapi_GetProcessList - if {![info exists ::twapi::get_thread_info_base_opts]} { - # Array value is the flags to pass to Twapi_GetProcessList - array set ::twapi::get_thread_info_base_opts { - pid 32 - elapsedtime 96 - waittime 96 - usertime 96 - createtime 96 - privilegedtime 96 - contextswitches 96 - basepriority 160 - priority 160 - startaddress 160 - state 160 - waitreason 160 - } - } - - set token_opts { - user - primarygroup - primarygroupsid - groups - groupsids - restrictedgroups - groupattrs - restrictedgroupattrs - privileges - enabledprivileges - disabledprivileges - } - - array set opts [parseargs args \ - [concat [list all \ - relativepriority \ - tid \ - [list noexist.arg "(no such thread)"] \ - [list noaccess.arg "(unknown)"]] \ - [array names ::twapi::get_thread_info_base_opts] \ - $token_opts ]] - - set requested_opts [_array_non_zero_switches opts $token_opts $opts(all)] - # Now get token info, if any - if {[llength $requested_opts]} { - trap { - trap { - set results [_token_info_helper -tid $tid {*}$requested_opts] - } onerror {TWAPI_WIN32 1008} { - # Thread does not have its own token. Use it's parent process - set results [_token_info_helper -pid [get_thread_parent_process_id $tid] {*}$requested_opts] - } - } onerror {TWAPI_WIN32 5} { - # No access - foreach opt $requested_opts { - lappend results $opt $opts(noaccess) - } - } onerror {TWAPI_WIN32 87} { - # Thread does not exist - foreach opt $requested_opts { - lappend results $opt $opts(noexist) - } - } - - } else { - set results [list ] - } - - # Now get the base options - set flags 0 - foreach opt [array names ::twapi::get_thread_info_base_opts] { - if {$opts($opt) || $opts(all)} { - set flags [expr {$flags | $::twapi::get_thread_info_base_opts($opt)}] - } - } - - if {$flags} { - # We need at least one of the base options - foreach tdata [recordarray column [twapi::Twapi_GetProcessList -1 $flags] Threads] { - set tdict [recordarray getdict $tdata -key "-tid" -format dict] - if {[dict exists $tdict $tid]} { - array set threadinfo [dict get $tdict $tid] - break - } - } - # It is possible that we looped through all the processes without - # a thread match. Hence we check again that we have threadinfo for - # each option value - foreach opt { - pid - waittime - usertime - createtime - privilegedtime - basepriority - priority - startaddress - state - waitreason - contextswitches - } { - if {$opts($opt) || $opts(all)} { - if {[info exists threadinfo]} { - lappend results -$opt $threadinfo(-$opt) - } else { - lappend results -$opt $opts(noexist) - } - } - } - - if {$opts(elapsedtime) || $opts(all)} { - if {[info exists threadinfo(-createtime)]} { - lappend results -elapsedtime [expr {[clock seconds]-[large_system_time_to_secs $threadinfo(-createtime)]}] - } else { - lappend results -elapsedtime $opts(noexist) - } - } - } - - - if {$opts(all) || $opts(relativepriority)} { - trap { - lappend results -relativepriority [get_thread_relative_priority $tid] - } onerror {TWAPI_WIN32 5} { - lappend results -relativepriority $opts(noaccess) - } onerror {TWAPI_WIN32 87} { - lappend results -relativepriority $opts(noexist) - } - } - - if {$opts(all) || $opts(tid)} { - lappend results -tid $tid - } - - return $results -} - -# Get a handle to a thread -proc twapi::get_thread_handle {tid args} { - # OpenThread masks off the bottom two bits thereby converting - # an invalid tid to a real one. We do not want this. - if {$tid & 3} { - win32_error 87; # "The parameter is incorrect" - } - - array set opts [parseargs args { - {access.arg thread_query_information} - {inherit.bool 0} - }] - return [OpenThread [_access_rights_to_mask $opts(access)] $opts(inherit) $tid] -} - -# Suspend a thread -proc twapi::suspend_thread {tid} { - set htid [get_thread_handle $tid -access thread_suspend_resume] - trap { - set status [SuspendThread $htid] - } finally { - CloseHandle $htid - } - return $status -} - -# Resume a thread -proc twapi::resume_thread {tid} { - set htid [get_thread_handle $tid -access thread_suspend_resume] - trap { - set status [ResumeThread $htid] - } finally { - CloseHandle $htid - } - return $status -} - -# Get the command line for a process -proc twapi::get_process_commandline {pid args} { - - if {[is_system_pid $pid] || [is_idle_pid $pid]} { - return "" - } - - array set opts [parseargs args { - {noexist.arg "(no such process)"} - {noaccess.arg "(unknown)"} - }] - - trap { - # Assume max command line len is 1024 chars (2048 bytes) - trap { - set hpid [get_process_handle $pid -access {process_query_information process_vm_read}] - } onerror {TWAPI_WIN32 87} { - # Process does not exist - return $opts(noexist) - } - - # Get the address where the PEB is stored - see Nebbett - set peb_addr [lindex [Twapi_NtQueryInformationProcessBasicInformation $hpid] 1] - - # Read the PEB as binary - # The pointer to the process parameter block is the 5th pointer field. - # The struct looks like: - # 32 bit - - # typedef struct _PEB { - # BYTE Reserved1[2]; - # BYTE BeingDebugged; - # BYTE Reserved2[1]; - # PVOID Reserved3[2]; - # PPEB_LDR_DATA Ldr; - # PRTL_USER_PROCESS_PARAMETERS ProcessParameters; - # BYTE Reserved4[104]; - # PVOID Reserved5[52]; - # PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; - # BYTE Reserved6[128]; - # PVOID Reserved7[1]; - # ULONG SessionId; - # } PEB, *PPEB; - # 64 bit - - # typedef struct _PEB { - # BYTE Reserved1[2]; - # BYTE BeingDebugged; - # BYTE Reserved2[21]; - # PPEB_LDR_DATA LoaderData; - # PRTL_USER_PROCESS_PARAMETERS ProcessParameters; - # BYTE Reserved3[520]; - # PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; - # BYTE Reserved4[136]; - # ULONG SessionId; - # } PEB; - # So in both cases the pointer is 4 pointers from the start - - if {[info exists ::tcl_platform(pointerSize)]} { - set pointer_size $::tcl_platform(pointerSize) - } else { - set pointer_size 4 - } - if {$pointer_size == 4} { - set pointer_scanner n - } else { - set pointer_scanner m - } - set mem [ReadProcessMemory $hpid [expr {$peb_addr+(4*$pointer_size)}] $pointer_size] - if {![binary scan $mem $pointer_scanner proc_param_addr]} { - error "Could not read PEB of process $pid" - } - - # Now proc_param_addr contains the address of the Process parameter - # structure which looks like: - # typedef struct _RTL_USER_PROCESS_PARAMETERS { - # Offsets: x86 x64 - # BYTE Reserved1[16]; 0 0 - # PVOID Reserved2[10]; 16 16 - # UNICODE_STRING ImagePathName; 56 96 - # UNICODE_STRING CommandLine; 64 112 - # } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; - # UNICODE_STRING is defined as - # typedef struct _UNICODE_STRING { - # USHORT Length; - # USHORT MaximumLength; - # PWSTR Buffer; - # } UNICODE_STRING; - - # Note - among twapi supported builds, tcl_platform(pointerSize) - # not existing implies 32-bits - if {[info exists ::tcl_platform(pointerSize)] && - $::tcl_platform(pointerSize) == 8} { - # Read the CommandLine field - set mem [ReadProcessMemory $hpid [expr {$proc_param_addr + 112}] 16] - if {![binary scan $mem tutunum cmdline_bytelen cmdline_bufsize unused cmdline_addr]} { - error "Could not get address of command line" - } - } else { - # Read the CommandLine field - set mem [ReadProcessMemory $hpid [expr {$proc_param_addr + 64}] 8] - if {![binary scan $mem tutunu cmdline_bytelen cmdline_bufsize cmdline_addr]} { - error "Could not get address of command line" - } - } - - if {1} { - if {$cmdline_bytelen == 0} { - set cmdline "" - } else { - trap { - set mem [ReadProcessMemory $hpid $cmdline_addr $cmdline_bytelen] - } onerror {TWAPI_WIN32 299} { - # ERROR_PARTIAL_COPY - # Rumour has it this can be a transient error if the - # process is initializing, so try once more - Sleep 0; # Relinquish control to OS to run other process - # Retry - set mem [ReadProcessMemory $hpid $cmdline_addr $cmdline_bytelen] - } - } - } else { - THIS CODE NEEDS TO BE MODIFIED IF REINSTATED. THE ReadProcessMemory - parameters have changed - # Old pre-2.3 code - # Now read the command line itself. We do not know the length - # so assume MAX_PATH (1024) chars (2048 bytes). However, this may - # fail if the memory beyond the command line is not allocated in the - # target process. So we have to check for this error and retry with - # smaller read sizes - set max_len 2048 - while {$max_len > 128} { - trap { - ReadProcessMemory $hpid $cmdline_addr $pgbl $max_len - break - } onerror {TWAPI_WIN32 299} { - # Reduce read size - set max_len [expr {$max_len / 2}] - } - } - # OK, got something. It's in Unicode format, may not be null terminated - # or may have multiple null terminated strings. THe command line - # is the first string. - } - set cmdline [encoding convertfrom unicode $mem] - set null_offset [string first "\0" $cmdline] - if {$null_offset >= 0} { - set cmdline [string range $cmdline 0 [expr {$null_offset-1}]] - } - - } onerror {TWAPI_WIN32 5} { - # Access denied - set cmdline $opts(noaccess) - } onerror {TWAPI_WIN32 299} { - # Only part of the Read* could be completed - # Access denied - set cmdline $opts(noaccess) - } onerror {TWAPI_WIN32 87} { - # The parameter is incorrect - # Access denied (or should it be noexist?) - set cmdline $opts(noaccess) - } finally { - if {[info exists hpid]} { - CloseHandle $hpid - } - } - - return $cmdline -} - - -# Get process parent - can return "" -proc twapi::get_process_parent {pid args} { - array set opts [parseargs args { - {noexist.arg "(no such process)"} - {noaccess.arg "(unknown)"} - }] - - if {[is_system_pid $pid] || [is_idle_pid $pid]} { - return "" - } - - trap { - set parent [recordarray cell [twapi::Twapi_GetProcessList $pid 1] 0 InheritedFromProcessId] - if {$parent ne ""} { - return $parent - } - } onerror {} { - # Just try the other methods below - } - - trap { - set hpid [get_process_handle $pid] - return [lindex [Twapi_NtQueryInformationProcessBasicInformation $hpid] 5] - - } onerror {TWAPI_WIN32 5} { - set error noaccess - } onerror {TWAPI_WIN32 87} { - set error noexist - } finally { - if {[info exists hpid]} { - CloseHandle $hpid - } - } - - return $opts($error) -} - -# Get the base priority class of a process -proc twapi::get_priority_class {pid} { - set ph [get_process_handle $pid] - trap { - return [GetPriorityClass $ph] - } finally { - CloseHandle $ph - } -} - -# Get the base priority class of a process -proc twapi::set_priority_class {pid priority} { - if {$pid == [pid]} { - variable my_process_handle - SetPriorityClass $my_process_handle $priority - return - } - - set ph [get_process_handle $pid -access process_set_information] - trap { - SetPriorityClass $ph $priority - } finally { - CloseHandle $ph - } -} - -# Get the priority of a thread -proc twapi::get_thread_relative_priority {tid} { - set h [get_thread_handle $tid] - trap { - return [GetThreadPriority $h] - } finally { - CloseHandle $h - } -} - -# Set the priority of a thread -proc twapi::set_thread_relative_priority {tid priority} { - switch -exact -- $priority { - abovenormal { set priority 1 } - belownormal { set priority -1 } - highest { set priority 2 } - idle { set priority -15 } - lowest { set priority -2 } - normal { set priority 0 } - timecritical { set priority 15 } - default { - if {![string is integer -strict $priority]} { - error "Invalid priority value '$priority'." - } - } - } - - set h [get_thread_handle $tid -access thread_set_information] - trap { - SetThreadPriority $h $priority - } finally { - CloseHandle $h - } -} - -# Return type of process elevation -proc twapi::get_process_elevation {args} { - lappend args -elevation - return [lindex [_token_info_helper $args] 1] -} - -# Return integrity level of process -proc twapi::get_process_integrity {args} { - lappend args -integrity - return [lindex [_token_info_helper $args] 1] -} - -# Return whether a process is running under WoW64 -proc twapi::wow64_process {args} { - array set opts [parseargs args { - pid.arg - hprocess.arg - } -maxleftover 0] - - if {[info exists opts(hprocess)]} { - if {[info exists opts(pid)]} { - error "Options -pid and -hprocess cannot be used together." - } - return [IsWow64Process $opts(hprocess)] - } - - if {[info exists opts(pid)] && $opts(pid) != [pid]} { - trap { - set hprocess [get_process_handle $opts(pid)] - return [IsWow64Process $hprocess] - } finally { - if {[info exists hprocess]} { - CloseHandle $hprocess - } - } - } - - # Common case - checking about ourselves - variable my_process_handle - return [IsWow64Process $my_process_handle] -} - -# Check whether a process is virtualized -proc twapi::virtualized_process {args} { - lappend args -virtualized - return [lindex [_token_info_helper $args] 1] -} - -proc twapi::set_process_integrity {level args} { - lappend args -integrity $level - _token_set_helper $args -} - -proc twapi::set_process_virtualization {enable args} { - lappend args -virtualized $enable - _token_set_helper $args -} - -# Map a process handle to its pid -proc twapi::get_pid_from_handle {hprocess} { - return [lindex [Twapi_NtQueryInformationProcessBasicInformation $hprocess] 4] -} - -# Check if current process is an administrative process or not -proc twapi::process_in_administrators {} { - - # Administrators group SID - S-1-5-32-544 - - if {[get_process_elevation] ne "limited"} { - return [CheckTokenMembership NULL S-1-5-32-544] - } - - # When running as with a limited token under UAC, we cannot check - # if the process is in administrators group or not since the group - # will be disabled in the token. Rather, we need to get the linked - # token (which is unfiltered) and check that. - set tok [lindex [_token_info_helper -linkedtoken] 1] - trap { - return [CheckTokenMembership $tok S-1-5-32-544] - } finally { - close_token $tok - } -} - -# Get a module handle -proc twapi::get_module_handle {args} { - array set opts [parseargs args { - path.arg - pin.bool - } -nulldefault -maxleftover 0] - - return [GetModuleHandleEx $opts(pin) [file nativename $opts(path)]] -} - -# Get a module handle from an address -proc twapi::get_module_handle_from_address {addr args} { - array set opts [parseargs args { - pin.bool - } -nulldefault -maxleftover 0] - - return [GetModuleHandleEx [expr {$opts(pin) ? 5 : 4}] $addr] -} - - -proc twapi::load_user_profile {token args} { - # PI_NOUI -> 0x1 - parseargs args { - username.arg - {noui.bool 0 0x1} - defaultuserpath.arg - servername.arg - roamingprofilepath.arg - } -maxleftover 0 -setvars -nulldefault - - if {$username eq ""} { - set username [get_token_user $token -name] - } - - return [eval_with_privileges { - LoadUserProfile [list $token $noui $username $roamingprofilepath $defaultuserpath $servername] - } {SeRestorePrivilege SeBackupPrivilege}] -} - -# TBD - document -proc twapi::get_profile_type {} { - return [dict* {0 local 1 temporary 2 roaming 4 mandatory} [GetProfileType]] -} - - -proc twapi::_env_block_to_dict {block normalize} { - set env_dict {} - foreach env_str $block { - set pos [string first = $env_str] - set key [string range $env_str 0 $pos-1] - if {$normalize} { - set key [string toupper $key] - } - lappend env_dict $key [string range $env_str $pos+1 end] - } - return $env_dict -} - -proc twapi::get_system_environment_vars {args} { - parseargs args {normalize.bool} -nulldefault -setvars -maxleftover 0 - return [_env_block_to_dict [CreateEnvironmentBlock 0 0] $normalize] -} - -proc twapi::get_user_environment_vars {token args} { - parseargs args {inherit.bool normalize.bool} -nulldefault -setvars -maxleftover 0 - return [_env_block_to_dict [CreateEnvironmentBlock $token $inherit] $normalize] -} - -proc twapi::expand_system_environment_vars {s} { - return [ExpandEnvironmentStringsForUser 0 $s] -} - -proc twapi::expand_user_environment_vars {tok s} { - return [ExpandEnvironmentStringsForUser $tok $s] -} - -# -# Utility procedures - -# Get the path of a process -proc twapi::_get_process_name_path_helper {pid {type name} args} { - - if {$pid == [pid]} { - # It is our process! - set exe [info nameofexecutable] - if {$type eq "name"} { - return [file tail $exe] - } else { - return $exe - } - } - - array set opts [parseargs args { - {noexist.arg "(no such process)"} - {noaccess.arg "(unknown)"} - } -maxleftover 0] - - if {![string is integer $pid]} { - error "Invalid non-numeric pid $pid" - } - if {[is_system_pid $pid]} { - return "System" - } - if {[is_idle_pid $pid]} { - return "System Idle Process" - } - - # Try the quicker way if looking for a name - if {$type eq "name" && - ![catch { - Twapi_GetProcessList $pid 2 - } plist]} { - set name [lindex $plist 1 0 1] - if {$name ne ""} { - return $name - } - } - - # We first try using GetProcessImageFileName as that does not require - # the PROCESS_VM_READ privilege - if {[min_os_version 6 0]} { - set privs [list process_query_limited_information] - } else { - set privs [list process_query_information] - } - - trap { - set hprocess [get_process_handle $pid -access $privs] - set path [GetProcessImageFileName $hprocess] - if {$type eq "name"} { - return [file tail $path] - } - # Returned path is in native format, convert to win32 - return [normalize_device_rooted_path $path] - } onerror {TWAPI_WIN32 87} { - return $opts(noexist) - } onerror {} { - # Other errors, continue on to other methods - } finally { - if {[info exists hprocess]} { - twapi::close_handle $hprocess - } - } - - trap { - set hprocess [get_process_handle $pid -access {process_query_information process_vm_read}] - } onerror {TWAPI_WIN32 87} { - return $opts(noexist) - } onerror {TWAPI_WIN32 5} { - # Access denied - # If it is the name we want, first try WTS and if that - # fails try getting it from PDH (slowest) - - if {[string equal $type "name"]} { - if {! [catch {WTSEnumerateProcesses NULL} precords]} { - - return [lindex [recordarray column $precords pProcessName -filter [list [list ProcessId == $pid]]] 0] - } - - # That failed as well, try PDH. TBD - get rid of PDH - set pdh_path [lindex [lindex [twapi::get_perf_process_counter_paths [list $pid] -pid] 0] 3] - array set pdhinfo [pdh_parse_counter_path $pdh_path] - return $pdhinfo(instance) - } - return $opts(noaccess) - } - - trap { - set module [lindex [EnumProcessModules $hprocess] 0] - if {[string equal $type "name"]} { - set path [GetModuleBaseName $hprocess $module] - } else { - set path [_normalize_path [GetModuleFileNameEx $hprocess $module]] - } - } onerror {TWAPI_WIN32 5} { - # Access denied - # On win2k (and may be Win2k3), if the process has exited but some - # app still has a handle to the process, the OpenProcess succeeds - # but the EnumProcessModules call returns access denied. So - # check for this case - if {[min_os_version 5 0]} { - # Try getting exit code. 259 means still running. - # Anything else means process has terminated - if {[GetExitCodeProcess $hprocess] == 259} { - return $opts(noaccess) - } else { - return $opts(noexist) - } - } else { - rethrow - } - } onerror {TWAPI_WIN32 299} { - # Partial read - usually means either we are WOW64 and target - # is 64bit, or process is exiting / starting and not all mem is - # reachable yet - return $opts(noaccess) - } finally { - CloseHandle $hprocess - } - return $path -} - -# Fill in arrays with result from WTSEnumerateProcesses if available -proc twapi::_get_wts_pids {v_sids v_names} { - # Note this call is expected to fail on NT 4.0 without terminal server - if {! [catch {WTSEnumerateProcesses NULL} precords]} { - upvar $v_sids wtssids - upvar $v_names wtsnames - array set wtssids [recordarray getlist $precords -slice {ProcessId pUserSid} -format flat] - array set wtsnames [recordarray getlist $precords -slice {ProcessId pUserSid} -format flat] - } -} - -# Return various information from a process token -proc twapi::_token_info_helper {args} { - package require twapi_security - proc _token_info_helper {args} { - if {[llength $args] == 1} { - # All options specified as one argument - set args [lindex $args 0] - } - - if {0} { - Following options are passed on to get_token_info: - elevation - virtualized - restrictedgroups - primarygroup - primarygroupsid - privileges - enabledprivileges - disabledprivileges - logonsession - linkedtoken - Option -integrity is not passed on because it has to deal with - -raw and -label options - } - - array set opts [parseargs args { - pid.arg - hprocess.arg - tid.arg - hthread.arg - integrity - raw - label - user - groups - groupsids - } -ignoreunknown] - - if {[expr {[info exists opts(pid)] + [info exists opts(hprocess)] + - [info exists opts(tid)] + [info exists opts(hthread)]}] > 1} { - error "At most one option from -pid, -tid, -hprocess, -hthread can be specified." - } - - if {$opts(user)} { - lappend args -usersid - } - - if {$opts(groups) || $opts(groupsids)} { - lappend args -groupsids - } - - if {[info exists opts(hprocess)]} { - set tok [open_process_token -hprocess $opts(hprocess)] - } elseif {[info exists opts(pid)]} { - set tok [open_process_token -pid $opts(pid)] - } elseif {[info exists opts(hthread)]} { - set tok [open_thread_token -hthread $opts(hthread)] - } elseif {[info exists opts(tid)]} { - set tok [open_thread_token -tid $opts(tid)] - } else { - # Default is current process - set tok [open_process_token] - } - - trap { - array set result [get_token_info $tok {*}$args] - if {[info exists result(-usersid)]} { - set result(-user) [lookup_account_sid $result(-usersid)] - unset result(-usersid) - } - if {[info exists result(-groupsids)]} { - if {$opts(groups)} { - set result(-groups) {} - foreach sid $result(-groupsids) { - if {[catch {lookup_account_sid $sid} gname]} { - lappend result(-groups) $sid - } else { - lappend result(-groups) $gname - } - } - } - if {!$opts(groupsids)} { - unset result(-groupsids) - } - } - if {$opts(integrity)} { - if {$opts(raw)} { - set integrity [get_token_integrity $tok -raw] - } elseif {$opts(label)} { - set integrity [get_token_integrity $tok -label] - } else { - set integrity [get_token_integrity $tok] - } - set result(-integrity) $integrity - } - } finally { - close_token $tok - } - - return [array get result] - } - - return [_token_info_helper {*}$args] -} - -# Set various information for a process token -# Caller assumed to have enabled appropriate privileges -proc twapi::_token_set_helper {args} { - package require twapi_security - - proc _token_set_helper {args} { - if {[llength $args] == 1} { - # All options specified as one argument - set args [lindex $args 0] - } - - array set opts [parseargs args { - virtualized.bool - integrity.arg - {noexist.arg "(no such process)"} - {noaccess.arg "(unknown)"} - pid.arg - hprocess.arg - } -maxleftover 0] - - if {[info exists opts(pid)] && [info exists opts(hprocess)]} { - error "Options -pid and -hprocess cannot be specified together." - } - - # Open token with appropriate access rights depending on request. - set access [list token_adjust_default] - - if {[info exists opts(hprocess)]} { - set tok [open_process_token -hprocess $opts(hprocess) -access $access] - } elseif {[info exists opts(pid)]} { - set tok [open_process_token -pid $opts(pid) -access $access] - } else { - # Default is current process - set tok [open_process_token -access $access] - } - - set result [list ] - trap { - if {[info exists opts(integrity)]} { - set_token_integrity $tok $opts(integrity) - } - if {[info exists opts(virtualized)]} { - set_token_virtualization $tok $opts(virtualized) - } - } finally { - close_token $tok - } - - return $result - } - return [_token_set_helper {*}$args] -} - -# Map console color name to integer attribute -proc twapi::_map_console_color {colors background} { - set attr 0 - foreach color $colors { - switch -exact -- $color { - blue {setbits attr 1} - green {setbits attr 2} - red {setbits attr 4} - white {setbits attr 7} - bright {setbits attr 8} - black { } - default {error "Unknown color name $color"} - } - } - if {$background} { - set attr [expr {$attr << 4}] - } - return $attr -} - +# +# Copyright (c) 2003-2020, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi {} + + +# Create a process +# TBD Use https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ +# to construct -cmdline value +proc twapi::create_process {path args} { + array set opts [parseargs args { + {debugchildtree.bool 0 0x1} + {debugchild.bool 0 0x2} + {createsuspended.bool 0 0x4} + {detached.bool 0 0x8} + {newconsole.bool 0 0x10} + {newprocessgroup.bool 0 0x200} + {separatevdm.bool 0 0x800} + {sharedvdm.bool 0 0x1000} + {inheriterrormode.bool 1 0x04000000} + {noconsole.bool 0 0x08000000} + {priority.arg normal {normal abovenormal belownormal high realtime idle}} + + {feedbackcursoron.bool 0 0x40} + {feedbackcursoroff.bool 0 0x80} + {fullscreen.bool 0 0x20} + + {cmdline.arg ""} + {inheritablechildprocess.bool 0} + {inheritablechildthread.bool 0} + {childprocesssecd.arg ""} + {childthreadsecd.arg ""} + {inherithandles.bool 0} + {env.arg ""} + {startdir.arg ""} + {desktop.arg __null__} + {title.arg ""} + windowpos.arg + windowsize.arg + screenbuffersize.arg + background.arg + foreground.arg + {showwindow.arg ""} + {stdhandles.arg ""} + {stdchannels.arg ""} + {returnhandles.bool 0} + + token.arg + } -maxleftover 0] + + set process_sec_attr [_make_secattr $opts(childprocesssecd) $opts(inheritablechildprocess)] + set thread_sec_attr [_make_secattr $opts(childthreadsecd) $opts(inheritablechildthread)] + + # Check incompatible options + if {$opts(newconsole) && $opts(detached)} { + error "Options -newconsole and -detached cannot be specified together" + } + if {$opts(sharedvdm) && $opts(separatevdm)} { + error "Options -sharedvdm and -separatevdm cannot be specified together" + } + + # Create the start up info structure + set si_flags 0 + if {[info exists opts(windowpos)]} { + lassign [_parse_integer_pair $opts(windowpos)] xpos ypos + setbits si_flags 0x4 + } else { + set xpos 0 + set ypos 0 + } + if {[info exists opts(windowsize)]} { + lassign [_parse_integer_pair $opts(windowsize)] xsize ysize + setbits si_flags 0x2 + } else { + set xsize 0 + set ysize 0 + } + if {[info exists opts(screenbuffersize)]} { + lassign [_parse_integer_pair $opts(screenbuffersize)] xscreen yscreen + setbits si_flags 0x8 + } else { + set xscreen 0 + set yscreen 0 + } + + set fg 7; # Default to white + set bg 0; # Default to black + if {[info exists opts(foreground)]} { + set fg [_map_console_color $opts(foreground) 0] + setbits si_flags 0x10 + } + if {[info exists opts(background)]} { + set bg [_map_console_color $opts(background) 1] + setbits si_flags 0x10 + } + + set si_flags [expr {$si_flags | + $opts(feedbackcursoron) | $opts(feedbackcursoroff) | + $opts(fullscreen)}] + + switch -exact -- $opts(showwindow) { + "" {set opts(showwindow) 1 } + hidden {set opts(showwindow) 0} + normal {set opts(showwindow) 1} + minimized {set opts(showwindow) 2} + maximized {set opts(showwindow) 3} + default {error "Invalid value '$opts(showwindow)' for -showwindow option"} + } + if {[string length $opts(showwindow)]} { + setbits si_flags 0x1 + } + + if {[llength $opts(stdhandles)] && [llength $opts(stdchannels)]} { + error "Options -stdhandles and -stdchannels cannot be used together" + } + + if {[llength $opts(stdhandles)]} { + if {! $opts(inherithandles)} { + error "Cannot specify -stdhandles option if option -inherithandles is specified as 0" + } + + setbits si_flags 0x100 + } + + # Figure out process creation flags + # 0x400 -> CREATE_UNICODE_ENVIRONMENT + set flags [expr {0x00000400 | + $opts(createsuspended) | $opts(debugchildtree) | + $opts(debugchild) | $opts(detached) | $opts(newconsole) | + $opts(newprocessgroup) | $opts(separatevdm) | + $opts(sharedvdm) | $opts(inheriterrormode) | + $opts(noconsole) }] + + switch -exact -- $opts(priority) { + normal {set priority 0x00000020} + abovenormal {set priority 0x00008000} + belownormal {set priority 0x00004000} + "" {set priority 0} + high {set priority 0x00000080} + realtime {set priority 0x00000100} + idle {set priority 0x00000040} + default {error "Unknown priority '$priority'"} + } + set flags [expr {$flags | $priority}] + + # Create the environment strings + if {[llength $opts(env)]} { + set child_env [list ] + foreach {envvar envval} $opts(env) { + lappend child_env "$envvar=$envval" + } + } else { + set child_env "__null__" + } + + trap { + # This is inside the trap because duplicated handles have + # to be closed. + if {[llength $opts(stdchannels)]} { + if {! $opts(inherithandles)} { + error "Cannot specify -stdhandles option if option -inherithandles is specified as 0" + } + if {[llength $opts(stdchannels)] != 3} { + error "Must specify 3 channels for -stdchannels option corresponding stdin, stdout and stderr" + } + + setbits si_flags 0x100 + + # Convert the channels to handles + lappend opts(stdhandles) [duplicate_handle [get_tcl_channel_handle [lindex $opts(stdchannels) 0] read] -inherit] + lappend opts(stdhandles) [duplicate_handle [get_tcl_channel_handle [lindex $opts(stdchannels) 1] write] -inherit] + lappend opts(stdhandles) [duplicate_handle [get_tcl_channel_handle [lindex $opts(stdchannels) 2] write] -inherit] + } + + set startup [list $opts(desktop) $opts(title) $xpos $ypos \ + $xsize $ysize $xscreen $yscreen \ + [expr {$fg|$bg}] $si_flags $opts(showwindow) \ + $opts(stdhandles)] + + if {[info exists opts(token)]} { + lassign [CreateProcessAsUser $opts(token) [file nativename $path] \ + $opts(cmdline) \ + $process_sec_attr $thread_sec_attr \ + $opts(inherithandles) $flags $child_env \ + [file normalize $opts(startdir)] $startup \ + ] ph th pid tid + + } else { + lassign [CreateProcess [file nativename $path] \ + $opts(cmdline) \ + $process_sec_attr $thread_sec_attr \ + $opts(inherithandles) $flags $child_env \ + [file normalize $opts(startdir)] $startup \ + ] ph th pid tid + } + } finally { + # If opts(stdchannels) is not an empty list, we duplicated the handles + # into opts(stdhandles) ourselves so free them + if {[llength $opts(stdchannels)]} { + # Free corresponding handles in opts(stdhandles) + close_handles $opts(stdhandles) + } + } + + # From the Tcl source code - (tclWinPipe.c) + # /* + # * "When an application spawns a process repeatedly, a new thread + # * instance will be created for each process but the previous + # * instances may not be cleaned up. This results in a significant + # * virtual memory loss each time the process is spawned. If there + # * is a WaitForInputIdle() call between CreateProcess() and + # * CloseHandle(), the problem does not occur." PSS ID Number: Q124121 + # */ + # WaitForInputIdle $ph 5000 -- Apparently this is only needed for NT 3.5 + + + if {$opts(returnhandles)} { + return [list $pid $tid $ph $th] + } else { + CloseHandle $th + CloseHandle $ph + return [list $pid $tid] + } +} + +# Wait until the process is ready +proc twapi::process_waiting_for_input {pid args} { + array set opts [parseargs args { + {wait.int 0} + } -maxleftover 0] + + if {$pid == [pid]} { + variable my_process_handle + return [WaitForInputIdle $my_process_handle $opts(wait)] + } + + set hpid [get_process_handle $pid] + trap { + return [WaitForInputIdle $hpid $opts(wait)] + } finally { + CloseHandle $hpid + } +} + + + +# Get a handle to a process +proc twapi::get_process_handle {pid args} { + # OpenProcess masks off the bottom two bits thereby converting + # an invalid pid to a real one. + if {(![string is integer -strict $pid]) || ($pid & 3)} { + win32_error 87 "Invalid PID '$pid'."; # "The parameter is incorrect" + } + array set opts [parseargs args { + {access.arg process_query_information} + {inherit.bool 0} + } -maxleftover 0] + return [OpenProcess [_access_rights_to_mask $opts(access)] $opts(inherit) $pid] +} + +# Return true if passed pid is system +proc twapi::is_system_pid {pid} { + # Note Windows 2000 System PID was 8 but we no longer support it. + return [expr {$pid == 4}] +} + +# Return true if passed pid is of idle process +proc twapi::is_idle_pid {pid} { + return [expr {$pid == 0}] +} + +# Get my process id +proc twapi::get_current_process_id {} { + return [::pid] +} + +# Get my thread id +proc twapi::get_current_thread_id {} { + return [GetCurrentThreadId] +} + +# Get the exit code for a process. Returns "" if still running. +proc twapi::get_process_exit_code {hpid} { + set code [GetExitCodeProcess $hpid] + return [expr {$code == 259 ? "" : $code}] +} + +# Return list of process ids +# Note if -path or -name is specified, then processes for which this +# information cannot be obtained are skipped +proc twapi::get_process_ids {args} { + + set save_args $args; # Need to pass to process_exists + array set opts [parseargs args { + user.arg + path.arg + name.arg + logonsession.arg + glob} -maxleftover 0] + + if {[info exists opts(path)] && [info exists opts(name)]} { + error "Options -path and -name are mutually exclusive" + } + + if {$opts(glob)} { + set match_op ~ + } else { + set match_op eq + } + + # If we do not care about user or path, Twapi_GetProcessList + # is faster than EnumProcesses or the WTS functions + if {[info exists opts(user)] == 0 && + [info exists opts(logonsession)] == 0 && + [info exists opts(path)] == 0} { + if {[info exists opts(name)] == 0} { + return [Twapi_GetProcessList -1 0] + } + # We need to match against the name + return [recordarray column [Twapi_GetProcessList -1 2] -pid \ + -filter [list [list "-name" $match_op $opts(name) -nocase]]] + } + + # Only want pids with a specific user or path or logon session + + # If is the name we are looking for, try using the faster WTS + # API's first. If they are not available, we try a slower method + # If we need to match paths or logon sessions, we don't try this + # at all as the wts api's don't provide that info + if {[info exists opts(path)] == 0 && + [info exists opts(logonsession)] == 0} { + if {![info exists opts(user)]} { + # How did we get here? + error "Internal error - option -user not specified where expected" + } + if {[catch {map_account_to_sid $opts(user)} sid]} { + # No such user. Return empty list (no processes) + return [list ] + } + + if {[info exists opts(name)]} { + set filter_expr [list [list pUserSid eq $sid -nocase] [list pProcessName $match_op $opts(name) -nocase]] + } else { + set filter_expr [list [list pUserSid eq $sid -nocase]] + } + + # Catch failures so we can try other means + if {! [catch {recordarray column [WTSEnumerateProcesses NULL] \ + ProcessId -filter $filter_expr} wtslist]} { + return $wtslist + } + } + + set process_pids [list ] + + + # Either we are matching on path/logonsession, or the WTS call failed + # Try yet another way. + + # Note that in the code below, we use "file join" with a single arg + # to convert \ to /. Do not use file normalize as that will also + # land up converting relative paths to full paths + if {[info exists opts(path)]} { + set opts(path) [file join $opts(path)] + } + + set process_pids [list ] + if {[info exists opts(name)]} { + # Note we may reach here if the WTS call above failed + set all_pids [recordarray column [Twapi_GetProcessList -1 2] ProcessId -filter [list [list ProcessName $match_op $opts(name) -nocase]]] + } else { + set all_pids [Twapi_GetProcessList -1 0] + } + + set filter_expr {} + set popts [list ] + if {[info exists opts(path)]} { + lappend popts -path + lappend filter_expr [list -path $match_op $opts(path) -nocase] + } + + if {[info exists opts(user)]} { + lappend popts -user + lappend filter_expr [list -user eq $opts(user) -nocase] + } + if {[info exists opts(logonsession)]} { + lappend popts -logonsession + lappend filter_expr [list -logonsession eq $opts(logonsession) -nocase] + } + + + set matches [recordarray get [get_multiple_process_info -matchpids $all_pids {*}$popts] -filter $filter_expr] + return [recordarray column $matches -pid] +} + +proc twapi::get_process_memory_info {{pid {}}} { + variable my_process_handle + + if {$pid eq "" || $pid == [pid]} { + set hpid $my_process_handle + } else { + set hpid [get_process_handle $pid -access {process_query_information process_vm_read}] + } + + try { + # Note: -pagefileusage and -privateusage are same according to SDK. + # However for Win7 and earlier, -pagefileusage is always set to 0. + # We return what was given and not try to fix it up. + return [twine { + -pagefaults -workingsetpeak -workingset + -poolpagedbytespeak -poolpagedbytes + -poolnonpagedbytespeak -poolnonpagedbytes + -pagefilebytes -pagefilebytespeak -privatebytes + } [GetProcessMemoryInfo $hpid]] + } finally { + if {$hpid != $my_process_handle} { + CloseHandle $hpid + } + } +} + +# Return list of modules handles for a process +proc twapi::get_process_modules {pid args} { + variable my_process_handle + + array set opts [parseargs args {handle name path base size entry all}] + + if {$opts(all)} { + foreach opt {handle name path base size entry} { + set opts($opt) 1 + } + } + set noopts [expr {($opts(name) || $opts(path) || $opts(base) || $opts(size) || $opts(entry) || $opts(handle)) == 0}] + + if {! $noopts} { + # Returning a record array + set fields {} + # ORDER MUST be same a value order below + foreach opt {handle name path base size entry} { + if {$opts($opt)} { + lappend fields -$opt + } + } + + } + + if {$pid == [pid]} { + set hpid $my_process_handle + } else { + set hpid [get_process_handle $pid -access {process_query_information process_vm_read}] + } + + set results [list ] + trap { + foreach module [EnumProcessModules $hpid] { + if {$noopts} { + lappend results $module + continue + } + set rec {} + if {$opts(handle)} { + lappend rec $module + } + if {$opts(name)} { + if {[catch {GetModuleBaseName $hpid $module} name]} { + set name "" + } + lappend rec $name + } + if {$opts(path)} { + if {[catch {GetModuleFileNameEx $hpid $module} path]} { + set path "" + } + lappend rec [_normalize_path $path] + } + if {$opts(base) || $opts(size) || $opts(entry)} { + if {[catch {GetModuleInformation $hpid $module} imagedata]} { + set base "" + set size "" + set entry "" + } else { + lassign $imagedata base size entry + } + foreach opt {base size entry} { + if {$opts($opt)} { + lappend rec [set $opt] + } + } + } + lappend results $rec + } + } finally { + if {$hpid != $my_process_handle} { + CloseHandle $hpid + } + } + + if {$noopts} { + return $results + } else { + return [list $fields $results] + } +} + + +# Kill a process +# Returns 1 if process was ended, 0 if not ended within timeout +proc twapi::end_process {pid args} { + + if {$pid == [pid]} { + error "The passed PID is the PID of the current process. end_process cannot be used to commit suicide." + } + + array set opts [parseargs args { + {exitcode.int 1} + force + {wait.int 0} + }] + + # In order to verify the process is really gone, we open the process + # if possible and then wait on its handle. If access restrictions prevent + # us from doing so, we ignore the issue and will simply check for the + # the PID later (which is not a sure check since PID's can be reused + # immediately) + catch {set hproc [get_process_handle $pid -access synchronize]} + + # First try to close nicely. We need to send messages to toplevels + # as well as message-only windows. We could make use of get_toplevel_windows + # and find_windows but those would require pulling in the whole + # twapi_ui package so do it ourselves. + set toplevels {} + foreach toplevel [EnumWindows] { + # Check if it belongs to pid. Errors are ignored, we simply + # will not send a message to that window + catch { + if {[lindex [GetWindowThreadProcessId $toplevel] 1] == $pid} { + lappend toplevels $toplevel + } + } + } + # Repeat for message only windows as EnumWindows skips them + set prev 0 + while {1} { + # Again, errors are ignored + # -3 -> HWND_MESSAGE windows + if {[catch { + set toplevel [FindWindowEx [list -3 HWND] $prev "" ""] + }]} { + break + } + if {[pointer_null? $toplevel]} break + catch { + if {[lindex [GetWindowThreadProcessId $toplevel] 1] == $pid} { + lappend toplevels $toplevel + } + } + set prev $toplevel + } + + if {[llength $toplevels]} { + # Try and close by sending them a message. WM_CLOSE is 0x10 + foreach toplevel $toplevels { + # Send a message but come back right away + # See Bug #139 as to why PostMessage instead of SendNotifyMessage + catch {PostMessage $toplevel 0x10 0 0} + } + + # Wait for the specified time to verify process has gone away + if {[info exists hproc]} { + set status [WaitForSingleObject $hproc $opts(wait)] + CloseHandle $hproc + set gone [expr {! $status}] + } else { + # We could not get a process handle to wait on, just check if + # PID still exists. This COULD be a false positive... + set gone [twapi::wait {process_exists $pid} 0 $opts(wait)] + } + if {$gone || ! $opts(force)} { + # Succeeded or do not want to force a kill + return $gone + } + + # Only wait 10 ms since we have already waited above + if {$opts(wait)} { + set opts(wait) 10 + } + } + + # Open the process for terminate access. IF access denied (5), retry after + # getting the required privilege + trap { + set hproc [get_process_handle $pid -access {synchronize process_terminate}] + } onerror {TWAPI_WIN32 5} { + # Retry - if still fail, then just throw the error + eval_with_privileges { + set hproc [get_process_handle $pid -access {synchronize process_terminate}] + } SeDebugPrivilege + } onerror {TWAPI_WIN32 87} { + # Process does not exist, we must have succeeded above but just + # took a bit longer for it to exit + return 1 + } + + trap { + TerminateProcess $hproc $opts(exitcode) + set status [WaitForSingleObject $hproc $opts(wait)] + if {$status == 0} { + return 1 + } + } finally { + CloseHandle $hproc + } + + return 0 +} + +# Get the path of a process +proc twapi::get_process_path {pid args} { + return [twapi::_get_process_name_path_helper $pid path {*}$args] +} + +# Get the path of a process +proc twapi::get_process_name {pid args} { + return [twapi::_get_process_name_path_helper $pid name {*}$args] +} + + +# Return list of device drivers +proc twapi::get_device_drivers {args} { + array set opts [parseargs args {name path base all}] + + set fields {} + # Order MUST be same as order of values below + foreach opt {base name path} { + if {$opts($opt) || $opts(all)} { + lappend fields -$opt + } + } + + set results [list ] + foreach module [EnumDeviceDrivers] { + unset -nocomplain rec + if {$opts(base) || $opts(all)} { + lappend rec $module + } + if {$opts(name) || $opts(all)} { + if {[catch {GetDeviceDriverBaseName $module} name]} { + set name "" + } + lappend rec $name + } + if {$opts(path) || $opts(all)} { + if {[catch {GetDeviceDriverFileName $module} path]} { + set path "" + } + lappend rec [_normalize_path $path] + } + if {[info exists rec]} { + lappend results $rec + } + } + + return [list $fields $results] +} + +# Check if the given process exists +# 0 - does not exist or exists but paths/names do not match, +# 1 - exists and matches path (or no -path or -name specified) +# -1 - exists but do not know path and cannot compare +proc twapi::process_exists {pid args} { + array set opts [parseargs args { path.arg name.arg glob}] + + # Simplest case - don't care about name or path + if {! ([info exists opts(path)] || [info exists opts(name)])} { + if {$pid == [pid]} { + return 1 + } + # TBD - would it be faster to do OpenProcess ? If success or + # access denied, process exists. + + if {[llength [Twapi_GetProcessList $pid 0]] == 0} { + return 0 + } else { + return 1 + } + } + + # Can't specify both name and path + if {[info exists opts(path)] && [info exists opts(name)]} { + error "Options -path and -name are mutually exclusive" + } + + if {$opts(glob)} { + set string_cmd match + } else { + set string_cmd equal + } + + if {[info exists opts(name)]} { + # Name is specified + set pidlist [Twapi_GetProcessList $pid 2] + if {[llength $pidlist] == 0} { + return 0 + } + return [string $string_cmd -nocase $opts(name) [lindex $pidlist 1 0 1]] + } + + # Need to match on the path + set process_path [get_process_path $pid -noexist "" -noaccess "(unknown)"] + if {[string length $process_path] == 0} { + # No such process + return 0 + } + + # Process with this pid exists + # Path still has to match + if {[string equal $process_path "(unknown)"]} { + # Exists but cannot check path/name + return -1 + } + + # Note we do not use file normalize here since that will tack on + # absolute paths which we do not want for glob matching + + # We use [file join ] to convert \ to / to avoid special + # interpretation of \ in string match command + return [string $string_cmd -nocase [file join $opts(path)] [file join $process_path]] +} + +# Get the parent process of a thread. Return "" if no such thread +proc twapi::get_thread_parent_process_id {tid} { + set status [catch { + set th [get_thread_handle $tid] + trap { + set pid [lindex [lindex [Twapi_NtQueryInformationThreadBasicInformation $th] 2] 0] + } finally { + CloseHandle $th + } + }] + + if {$status == 0} { + return $pid + } + + + # Could not use undocumented function. Try slooooow perf counter method + set pid_paths [get_perf_thread_counter_paths $tid -pid] + if {[llength $pid_paths] == 0} { + return "" + } + + if {[pdh_counter_path_value [lindex [lindex $pid_paths 0] 3] -var pid]} { + return $pid + } else { + return "" + } +} + +# Get the thread ids belonging to a process +proc twapi::get_process_thread_ids {pid} { + return [recordarray cell [get_multiple_process_info -matchpids [list $pid] -tids] 0 -tids] +} + + +# Get process information +proc twapi::get_process_info {pid args} { + # To avert a common mistake where pid is unspecified, use current pid + # so [get_process_info -name] becomes [get_process_info [pid] -name] + # TBD - should this be documented ? + + if {![string is integer -strict $pid]} { + set args [linsert $args 0 $pid] + set pid [pid] + } + + set rec [recordarray index [get_multiple_process_info {*}$args -matchpids [list $pid]] 0 -format dict] + if {"-pid" ni $args && "-all" ni $args} { + dict unset rec -pid + } + return $rec +} + + +# Get multiple process information +# TBD - document and write tests +proc twapi::get_multiple_process_info {args} { + + # Options that are directly available from Twapi_GetProcessList + # Dict value is the flags to pass to Twapi_GetProcessList + set base_opts { + basepriority 1 + parent 1 tssession 1 + name 2 + createtime 4 usertime 4 + privilegedtime 4 handlecount 4 + threadcount 4 + pagefaults 8 pagefilebytes 8 + pagefilebytespeak 8 poolnonpagedbytes 8 + poolnonpagedbytespeak 8 poolpagedbytes 8 + poolpagedbytespeak 8 virtualbytes 8 + virtualbytespeak 8 workingset 8 + workingsetpeak 8 + ioreadops 16 iowriteops 16 + iootherops 16 ioreadbytes 16 + iowritebytes 16 iootherbytes 16 + } + # Options that also dependent on Twapi_GetProcessList but not + # directly available + set base_calc_opts { elapsedtime 4 tids 32 } + + # Note -user is also a potential token opt but not listed below + # because it can be gotten by other means + set token_opts { + disabledprivileges elevation enabledprivileges groupattrs groups groupsids + integrity integritylabel logonsession primarygroup primarygroupsid + privileges restrictedgroupattrs restrictedgroups virtualized + } + + set optdefs [lconcat {all pid user path commandline priorityclass {noexist.arg {(no such process)}} {noaccess.arg {(unknown)}} matchpids.arg} \ + [dict keys $base_opts] \ + [dict keys $base_calc_opts] \ + $token_opts] + array set opts [parseargs args $optdefs -maxleftover 0] + set opts(pid) 1; # Always return pid, -pid option is for backward compat + + if {[info exists opts(matchpids)]} { + set pids $opts(matchpids) + } else { + set pids [Twapi_GetProcessList -1 0] + } + + set now [get_system_time] + + # We will return a record array. $records tracks a dict of record + # values keyed by pid, $fields tracks the names in the list elements + # [llength $fields] == [llength [lindex $records *]] + set records {} + set fields {} + + # If user is requested, try getting it through terminal services + # if possible since the token method fails on some newer platforms + if {$opts(all) || $opts(user)} { + _get_wts_pids wtssids wtsnames + } + + # See if any Twapi_GetProcessList options are requested and if + # so, calculate the appropriate flags + set baseflags 0 + set basenoexistvals {} + dict for {opt flag} $base_opts { + if {$opts($opt) || $opts(all)} { + set baseflags [expr {$baseflags | $flag}] + lappend basefields -$opt + lappend basenoexistvals $opts(noexist) + } + } + dict for {opt flag} $base_calc_opts { + if {$opts($opt) || $opts(all)} { + set baseflags [expr {$baseflags | $flag}] + } + } + + # See if we need to retrieve any base options + if {$baseflags} { + set pidarg [expr {[llength $pids] == 1 ? [lindex $pids 0] : -1}] + set data [twapi::Twapi_GetProcessList $pidarg [expr {$baseflags|1}]] + if {$opts(all) || $opts(elapsedtime) || $opts(tids)} { + array set baserawdata [recordarray getdict $data -key "-pid" -format dict] + } + if {[info exists basefields]} { + set fields $basefields + set records [recordarray getdict $data -slice $basefields -key "-pid"] + } + } + if {$opts(pid)} { + lappend fields -pid + } + foreach pid $pids { + # If base values were requested, but this pid does not exist + # use the "noexist" values + if {![dict exists $records $pid]} { + dict set records $pid $basenoexistvals + } + if {$opts(pid)} { + dict lappend records $pid $pid + } + } + + # If all we need are baseline options, and no massaging is required + # (as for elapsedtime, for example), we can return what we have + # without looping through below. Saves significant time. + set done 1 + foreach opt [list all user elapsedtime tids path commandline priorityclass \ + {*}$token_opts] { + if {$opts($opt)} { + set done 0 + break + } + } + + if {$done} { + set return_data {} + foreach pid $pids { + lappend return_data [dict get $records $pid] + } + return [list $fields $return_data] + } + + set requested_token_opts {} + foreach opt $token_opts { + if {$opts(all) || $opts($opt)} { + lappend requested_token_opts -$opt + } + } + + if {$opts(elapsedtime) || $opts(all)} { + lappend fields -elapsedtime + foreach pid $pids { + if {[info exists baserawdata($pid)]} { + set elapsed [twapi::kl_get $baserawdata($pid) -createtime] + if {$elapsed} { + # 100ns -> seconds + dict lappend records $pid [expr {($now-$elapsed)/10000000}] + } else { + # For some processes like, System and Idle, kernel + # returns start time of 0. Just use system uptime + if {![info exists system_uptime]} { + # Store locally so no refetch on each iteration + set system_uptime [get_system_uptime] + } + dict lappend records $pid $system_uptime + } + } else { + dict lappend records $pid $opts(noexist) + } + } + } + + if {$opts(tids) || $opts(all)} { + lappend fields -tids + foreach pid $pids { + if {[info exists baserawdata($pid)]} { + dict lappend records $pid [recordarray column [kl_get $baserawdata($pid) Threads] -tid] + } else { + dict lappend records $pid $opts(noexist) + } + } + } + + if {$opts(all) || $opts(path)} { + lappend fields -path + foreach pid $pids { + dict lappend records $pid [get_process_path $pid -noexist $opts(noexist) -noaccess $opts(noaccess)] + } + } + + if {$opts(all) || $opts(priorityclass)} { + lappend fields -priorityclass + foreach pid $pids { + trap { + set prioclass [get_priority_class $pid] + } onerror {TWAPI_WIN32 5} { + set prioclass $opts(noaccess) + } onerror {TWAPI_WIN32 87} { + set prioclass $opts(noexist) + } + dict lappend records $pid $prioclass + } + } + + if {$opts(all) || $opts(commandline)} { + lappend fields -commandline + foreach pid $pids { + dict lappend records $pid [get_process_commandline $pid -noexist $opts(noexist) -noaccess $opts(noaccess)] + } + } + + + if {$opts(all) || $opts(user) || [llength $requested_token_opts]} { + foreach pid $pids { + # Now get token related info, if any requested + # For returning as a record array, we have to be careful that + # each field is added in a specific order for every pid + # keeping in mind a different method might be used for different + # pids. So we collect the data in dictionary token_records and add + # at the end in a fixed order + set token_records {} + set requested_opts $requested_token_opts + unset -nocomplain user + if {$opts(all) || $opts(user)} { + # See if we already have the user. Note sid of system idle + # will be empty string + if {[info exists wtssids($pid)]} { + if {$wtssids($pid) == ""} { + # Put user as System + set user SYSTEM + } else { + # We speed up account lookup by caching sids + if {[info exists sidcache($wtssids($pid))]} { + set user $sidcache($wtssids($pid)) + } else { + set user [lookup_account_sid $wtssids($pid)] + set sidcache($wtssids($pid)) $user + } + } + } else { + lappend requested_opts -user + } + } + + if {[llength $requested_opts]} { + trap { + dict set token_records $pid [_token_info_helper -pid $pid {*}$requested_opts] + } onerror {TWAPI_WIN32 5} { + foreach opt $requested_opts { + dict set token_records $pid $opt $opts(noaccess) + } + # The NETWORK SERVICE and LOCAL SERVICE processes cannot + # be accessed. If we are looking for the logon session for + # these, try getting it from the witssid if we have it + # since the logon session is hardcoded for these accounts + if {"-logonsession" in $requested_opts} { + if {![info exists wtssids]} { + _get_wts_pids wtssids wtsnames + } + if {[info exists wtssids($pid)]} { + # Map user SID to logon session + switch -exact -- $wtssids($pid) { + S-1-5-18 { + # SYSTEM + dict set token_records $pid -logonsession 00000000-000003e7 + } + S-1-5-19 { + # LOCAL SERVICE + dict set token_records $pid -logonsession 00000000-000003e5 + } + S-1-5-20 { + # LOCAL SERVICE + dict set token_records $pid -logonsession 00000000-000003e4 + } + } + } + } + + # Similarly, if we are looking for user account, special case + # system and system idle processes + if {"-user" in $requested_opts} { + if {[is_idle_pid $pid] || [is_system_pid $pid]} { + set user SYSTEM + } else { + set user $opts(noaccess) + } + } + + } onerror {TWAPI_WIN32 87} { + foreach opt $requested_opts { + if {$opt eq "-user"} { + if {[is_idle_pid $pid] || [is_system_pid $pid]} { + set user SYSTEM + } else { + set user $opts(noexist) + } + } else { + dict set token_records $pid $opt $opts(noexist) + } + } + } + } + # Now add token values in a specific order - MUST MATCH fields BELOW + if {$opts(all) || $opts(user)} { + # TBD - BUG - user is supposed to be set to *something* by this + # point but WiTS throws error every blue moon on this line that + # user is not defined. Workaround. + if {![info exists user]} { + set user $opts(noaccess) + } + dict lappend records $pid $user + } + foreach opt $requested_token_opts { + if {[dict exists $token_records $pid $opt]} { + dict lappend records $pid [dict get $token_records $pid $opt] + } + } + } + # Now add token field names in a specific order - MUST MATCH ABOVE + if {$opts(all) || $opts(user)} { + lappend fields -user + } + foreach opt $requested_token_opts { + if {[dict exists $token_records $pid $opt]} { + lappend fields $opt + } + } + } + + set return_data {} + foreach pid $pids { + lappend return_data [dict get $records $pid] + } + return [list $fields $return_data] +} + + + +# Get thread information +# TBD - add info from GetGUIThreadInfo +proc twapi::get_thread_info {tid args} { + # TBD - modify so tid is optional like for get_process_info + + # Options that are directly available from Twapi_GetProcessList + if {![info exists ::twapi::get_thread_info_base_opts]} { + # Array value is the flags to pass to Twapi_GetProcessList + array set ::twapi::get_thread_info_base_opts { + pid 32 + elapsedtime 96 + waittime 96 + usertime 96 + createtime 96 + privilegedtime 96 + contextswitches 96 + basepriority 160 + priority 160 + startaddress 160 + state 160 + waitreason 160 + } + } + + set token_opts { + user + primarygroup + primarygroupsid + groups + groupsids + restrictedgroups + groupattrs + restrictedgroupattrs + privileges + enabledprivileges + disabledprivileges + } + + array set opts [parseargs args \ + [concat [list all \ + relativepriority \ + tid \ + [list noexist.arg "(no such thread)"] \ + [list noaccess.arg "(unknown)"]] \ + [array names ::twapi::get_thread_info_base_opts] \ + $token_opts ]] + + set requested_opts [_array_non_zero_switches opts $token_opts $opts(all)] + # Now get token info, if any + if {[llength $requested_opts]} { + trap { + trap { + set results [_token_info_helper -tid $tid {*}$requested_opts] + } onerror {TWAPI_WIN32 1008} { + # Thread does not have its own token. Use it's parent process + set results [_token_info_helper -pid [get_thread_parent_process_id $tid] {*}$requested_opts] + } + } onerror {TWAPI_WIN32 5} { + # No access + foreach opt $requested_opts { + lappend results $opt $opts(noaccess) + } + } onerror {TWAPI_WIN32 87} { + # Thread does not exist + foreach opt $requested_opts { + lappend results $opt $opts(noexist) + } + } + + } else { + set results [list ] + } + + # Now get the base options + set flags 0 + foreach opt [array names ::twapi::get_thread_info_base_opts] { + if {$opts($opt) || $opts(all)} { + set flags [expr {$flags | $::twapi::get_thread_info_base_opts($opt)}] + } + } + + if {$flags} { + # We need at least one of the base options + foreach tdata [recordarray column [twapi::Twapi_GetProcessList -1 $flags] Threads] { + set tdict [recordarray getdict $tdata -key "-tid" -format dict] + if {[dict exists $tdict $tid]} { + array set threadinfo [dict get $tdict $tid] + break + } + } + # It is possible that we looped through all the processes without + # a thread match. Hence we check again that we have threadinfo for + # each option value + foreach opt { + pid + waittime + usertime + createtime + privilegedtime + basepriority + priority + startaddress + state + waitreason + contextswitches + } { + if {$opts($opt) || $opts(all)} { + if {[info exists threadinfo]} { + lappend results -$opt $threadinfo(-$opt) + } else { + lappend results -$opt $opts(noexist) + } + } + } + + if {$opts(elapsedtime) || $opts(all)} { + if {[info exists threadinfo(-createtime)]} { + lappend results -elapsedtime [expr {[clock seconds]-[large_system_time_to_secs $threadinfo(-createtime)]}] + } else { + lappend results -elapsedtime $opts(noexist) + } + } + } + + + if {$opts(all) || $opts(relativepriority)} { + trap { + lappend results -relativepriority [get_thread_relative_priority $tid] + } onerror {TWAPI_WIN32 5} { + lappend results -relativepriority $opts(noaccess) + } onerror {TWAPI_WIN32 87} { + lappend results -relativepriority $opts(noexist) + } + } + + if {$opts(all) || $opts(tid)} { + lappend results -tid $tid + } + + return $results +} + +# Get a handle to a thread +proc twapi::get_thread_handle {tid args} { + # OpenThread masks off the bottom two bits thereby converting + # an invalid tid to a real one. We do not want this. + if {$tid & 3} { + win32_error 87; # "The parameter is incorrect" + } + + array set opts [parseargs args { + {access.arg thread_query_information} + {inherit.bool 0} + }] + return [OpenThread [_access_rights_to_mask $opts(access)] $opts(inherit) $tid] +} + +# Suspend a thread +proc twapi::suspend_thread {tid} { + set htid [get_thread_handle $tid -access thread_suspend_resume] + trap { + set status [SuspendThread $htid] + } finally { + CloseHandle $htid + } + return $status +} + +# Resume a thread +proc twapi::resume_thread {tid} { + set htid [get_thread_handle $tid -access thread_suspend_resume] + trap { + set status [ResumeThread $htid] + } finally { + CloseHandle $htid + } + return $status +} + +# Get the command line for a process +proc twapi::get_process_commandline {pid args} { + + if {[is_system_pid $pid] || [is_idle_pid $pid]} { + return "" + } + + array set opts [parseargs args { + {noexist.arg "(no such process)"} + {noaccess.arg "(unknown)"} + }] + + trap { + # Assume max command line len is 1024 chars (2048 bytes) + trap { + set hpid [get_process_handle $pid -access {process_query_information process_vm_read}] + } onerror {TWAPI_WIN32 87} { + # Process does not exist + return $opts(noexist) + } + + # Get the address where the PEB is stored - see Nebbett + set peb_addr [lindex [Twapi_NtQueryInformationProcessBasicInformation $hpid] 1] + + # Read the PEB as binary + # The pointer to the process parameter block is the 5th pointer field. + # The struct looks like: + # 32 bit - + # typedef struct _PEB { + # BYTE Reserved1[2]; + # BYTE BeingDebugged; + # BYTE Reserved2[1]; + # PVOID Reserved3[2]; + # PPEB_LDR_DATA Ldr; + # PRTL_USER_PROCESS_PARAMETERS ProcessParameters; + # BYTE Reserved4[104]; + # PVOID Reserved5[52]; + # PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; + # BYTE Reserved6[128]; + # PVOID Reserved7[1]; + # ULONG SessionId; + # } PEB, *PPEB; + # 64 bit - + # typedef struct _PEB { + # BYTE Reserved1[2]; + # BYTE BeingDebugged; + # BYTE Reserved2[21]; + # PPEB_LDR_DATA LoaderData; + # PRTL_USER_PROCESS_PARAMETERS ProcessParameters; + # BYTE Reserved3[520]; + # PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; + # BYTE Reserved4[136]; + # ULONG SessionId; + # } PEB; + # So in both cases the pointer is 4 pointers from the start + + if {[info exists ::tcl_platform(pointerSize)]} { + set pointer_size $::tcl_platform(pointerSize) + } else { + set pointer_size 4 + } + if {$pointer_size == 4} { + set pointer_scanner n + } else { + set pointer_scanner m + } + set mem [ReadProcessMemory $hpid [expr {$peb_addr+(4*$pointer_size)}] $pointer_size] + if {![binary scan $mem $pointer_scanner proc_param_addr]} { + error "Could not read PEB of process $pid" + } + + # Now proc_param_addr contains the address of the Process parameter + # structure which looks like: + # typedef struct _RTL_USER_PROCESS_PARAMETERS { + # Offsets: x86 x64 + # BYTE Reserved1[16]; 0 0 + # PVOID Reserved2[10]; 16 16 + # UNICODE_STRING ImagePathName; 56 96 + # UNICODE_STRING CommandLine; 64 112 + # } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + # UNICODE_STRING is defined as + # typedef struct _UNICODE_STRING { + # USHORT Length; + # USHORT MaximumLength; + # PWSTR Buffer; + # } UNICODE_STRING; + + # Note - among twapi supported builds, tcl_platform(pointerSize) + # not existing implies 32-bits + if {[info exists ::tcl_platform(pointerSize)] && + $::tcl_platform(pointerSize) == 8} { + # Read the CommandLine field + set mem [ReadProcessMemory $hpid [expr {$proc_param_addr + 112}] 16] + if {![binary scan $mem tutunum cmdline_bytelen cmdline_bufsize unused cmdline_addr]} { + error "Could not get address of command line" + } + } else { + # Read the CommandLine field + set mem [ReadProcessMemory $hpid [expr {$proc_param_addr + 64}] 8] + if {![binary scan $mem tutunu cmdline_bytelen cmdline_bufsize cmdline_addr]} { + error "Could not get address of command line" + } + } + + if {1} { + if {$cmdline_bytelen == 0} { + set cmdline "" + } else { + trap { + set mem [ReadProcessMemory $hpid $cmdline_addr $cmdline_bytelen] + } onerror {TWAPI_WIN32 299} { + # ERROR_PARTIAL_COPY + # Rumour has it this can be a transient error if the + # process is initializing, so try once more + Sleep 0; # Relinquish control to OS to run other process + # Retry + set mem [ReadProcessMemory $hpid $cmdline_addr $cmdline_bytelen] + } + } + } else { + THIS CODE NEEDS TO BE MODIFIED IF REINSTATED. THE ReadProcessMemory + parameters have changed + # Old pre-2.3 code + # Now read the command line itself. We do not know the length + # so assume MAX_PATH (1024) chars (2048 bytes). However, this may + # fail if the memory beyond the command line is not allocated in the + # target process. So we have to check for this error and retry with + # smaller read sizes + set max_len 2048 + while {$max_len > 128} { + trap { + ReadProcessMemory $hpid $cmdline_addr $pgbl $max_len + break + } onerror {TWAPI_WIN32 299} { + # Reduce read size + set max_len [expr {$max_len / 2}] + } + } + # OK, got something. It's in Unicode format, may not be null terminated + # or may have multiple null terminated strings. THe command line + # is the first string. + } + set cmdline [encoding convertfrom unicode $mem] + set null_offset [string first "\0" $cmdline] + if {$null_offset >= 0} { + set cmdline [string range $cmdline 0 [expr {$null_offset-1}]] + } + + } onerror {TWAPI_WIN32 5} { + # Access denied + set cmdline $opts(noaccess) + } onerror {TWAPI_WIN32 299} { + # Only part of the Read* could be completed + # Access denied + set cmdline $opts(noaccess) + } onerror {TWAPI_WIN32 87} { + # The parameter is incorrect + # Access denied (or should it be noexist?) + set cmdline $opts(noaccess) + } finally { + if {[info exists hpid]} { + CloseHandle $hpid + } + } + + return $cmdline +} + + +# Get process parent - can return "" +proc twapi::get_process_parent {pid args} { + array set opts [parseargs args { + {noexist.arg "(no such process)"} + {noaccess.arg "(unknown)"} + }] + + if {[is_system_pid $pid] || [is_idle_pid $pid]} { + return "" + } + + trap { + set parent [recordarray cell [twapi::Twapi_GetProcessList $pid 1] 0 InheritedFromProcessId] + if {$parent ne ""} { + return $parent + } + } onerror {} { + # Just try the other methods below + } + + trap { + set hpid [get_process_handle $pid] + return [lindex [Twapi_NtQueryInformationProcessBasicInformation $hpid] 5] + + } onerror {TWAPI_WIN32 5} { + set error noaccess + } onerror {TWAPI_WIN32 87} { + set error noexist + } finally { + if {[info exists hpid]} { + CloseHandle $hpid + } + } + + return $opts($error) +} + +# Get the base priority class of a process +proc twapi::get_priority_class {pid} { + set ph [get_process_handle $pid] + trap { + return [GetPriorityClass $ph] + } finally { + CloseHandle $ph + } +} + +# Get the base priority class of a process +proc twapi::set_priority_class {pid priority} { + if {$pid == [pid]} { + variable my_process_handle + SetPriorityClass $my_process_handle $priority + return + } + + set ph [get_process_handle $pid -access process_set_information] + trap { + SetPriorityClass $ph $priority + } finally { + CloseHandle $ph + } +} + +# Get the priority of a thread +proc twapi::get_thread_relative_priority {tid} { + set h [get_thread_handle $tid] + trap { + return [GetThreadPriority $h] + } finally { + CloseHandle $h + } +} + +# Set the priority of a thread +proc twapi::set_thread_relative_priority {tid priority} { + switch -exact -- $priority { + abovenormal { set priority 1 } + belownormal { set priority -1 } + highest { set priority 2 } + idle { set priority -15 } + lowest { set priority -2 } + normal { set priority 0 } + timecritical { set priority 15 } + default { + if {![string is integer -strict $priority]} { + error "Invalid priority value '$priority'." + } + } + } + + set h [get_thread_handle $tid -access thread_set_information] + trap { + SetThreadPriority $h $priority + } finally { + CloseHandle $h + } +} + +# Return type of process elevation +proc twapi::get_process_elevation {args} { + lappend args -elevation + return [lindex [_token_info_helper $args] 1] +} + +# Return integrity level of process +proc twapi::get_process_integrity {args} { + lappend args -integrity + return [lindex [_token_info_helper $args] 1] +} + +# Return whether a process is running under WoW64 +proc twapi::wow64_process {args} { + array set opts [parseargs args { + pid.arg + hprocess.arg + } -maxleftover 0] + + if {[info exists opts(hprocess)]} { + if {[info exists opts(pid)]} { + error "Options -pid and -hprocess cannot be used together." + } + return [IsWow64Process $opts(hprocess)] + } + + if {[info exists opts(pid)] && $opts(pid) != [pid]} { + trap { + set hprocess [get_process_handle $opts(pid)] + return [IsWow64Process $hprocess] + } finally { + if {[info exists hprocess]} { + CloseHandle $hprocess + } + } + } + + # Common case - checking about ourselves + variable my_process_handle + return [IsWow64Process $my_process_handle] +} + +# Check whether a process is virtualized +proc twapi::virtualized_process {args} { + lappend args -virtualized + return [lindex [_token_info_helper $args] 1] +} + +proc twapi::set_process_integrity {level args} { + lappend args -integrity $level + _token_set_helper $args +} + +proc twapi::set_process_virtualization {enable args} { + lappend args -virtualized $enable + _token_set_helper $args +} + +# Map a process handle to its pid +proc twapi::get_pid_from_handle {hprocess} { + return [lindex [Twapi_NtQueryInformationProcessBasicInformation $hprocess] 4] +} + +# Check if current process is an administrative process or not +proc twapi::process_in_administrators {} { + + # Administrators group SID - S-1-5-32-544 + + if {[get_process_elevation] ne "limited"} { + return [CheckTokenMembership NULL S-1-5-32-544] + } + + # When running as with a limited token under UAC, we cannot check + # if the process is in administrators group or not since the group + # will be disabled in the token. Rather, we need to get the linked + # token (which is unfiltered) and check that. + set tok [lindex [_token_info_helper -linkedtoken] 1] + trap { + return [CheckTokenMembership $tok S-1-5-32-544] + } finally { + close_token $tok + } +} + +# Get a module handle +proc twapi::get_module_handle {args} { + array set opts [parseargs args { + path.arg + pin.bool + } -nulldefault -maxleftover 0] + + return [GetModuleHandleEx $opts(pin) [file nativename $opts(path)]] +} + +# Get a module handle from an address +proc twapi::get_module_handle_from_address {addr args} { + array set opts [parseargs args { + pin.bool + } -nulldefault -maxleftover 0] + + return [GetModuleHandleEx [expr {$opts(pin) ? 5 : 4}] $addr] +} + + +proc twapi::load_user_profile {token args} { + # PI_NOUI -> 0x1 + parseargs args { + username.arg + {noui.bool 0 0x1} + defaultuserpath.arg + servername.arg + roamingprofilepath.arg + } -maxleftover 0 -setvars -nulldefault + + if {$username eq ""} { + set username [get_token_user $token -name] + } + + return [eval_with_privileges { + LoadUserProfile [list $token $noui $username $roamingprofilepath $defaultuserpath $servername] + } {SeRestorePrivilege SeBackupPrivilege}] +} + +# TBD - document +proc twapi::get_profile_type {} { + return [dict* {0 local 1 temporary 2 roaming 4 mandatory} [GetProfileType]] +} + + +proc twapi::_env_block_to_dict {block normalize} { + set env_dict {} + foreach env_str $block { + set pos [string first = $env_str] + set key [string range $env_str 0 $pos-1] + if {$normalize} { + set key [string toupper $key] + } + lappend env_dict $key [string range $env_str $pos+1 end] + } + return $env_dict +} + +proc twapi::get_system_environment_vars {args} { + parseargs args {normalize.bool} -nulldefault -setvars -maxleftover 0 + return [_env_block_to_dict [CreateEnvironmentBlock 0 0] $normalize] +} + +proc twapi::get_user_environment_vars {token args} { + parseargs args {inherit.bool normalize.bool} -nulldefault -setvars -maxleftover 0 + return [_env_block_to_dict [CreateEnvironmentBlock $token $inherit] $normalize] +} + +proc twapi::expand_system_environment_vars {s} { + return [ExpandEnvironmentStringsForUser 0 $s] +} + +proc twapi::expand_user_environment_vars {tok s} { + return [ExpandEnvironmentStringsForUser $tok $s] +} + +# +# Utility procedures + +# Get the path of a process +proc twapi::_get_process_name_path_helper {pid {type name} args} { + + if {$pid == [pid]} { + # It is our process! + set exe [info nameofexecutable] + if {$type eq "name"} { + return [file tail $exe] + } else { + return $exe + } + } + + array set opts [parseargs args { + {noexist.arg "(no such process)"} + {noaccess.arg "(unknown)"} + } -maxleftover 0] + + if {![string is integer -strict $pid]} { + error "Invalid non-numeric pid $pid" + } + if {[is_system_pid $pid]} { + return "System" + } + if {[is_idle_pid $pid]} { + return "System Idle Process" + } + + # Try the quicker way if looking for a name + if {$type eq "name" && + ![catch { + Twapi_GetProcessList $pid 2 + } plist]} { + set name [lindex $plist 1 0 1] + if {$name ne ""} { + return $name + } + } + + # We first try using GetProcessImageFileName as that does not require + # the PROCESS_VM_READ privilege + if {[min_os_version 6 0]} { + set privs [list process_query_limited_information] + } else { + set privs [list process_query_information] + } + + trap { + set hprocess [get_process_handle $pid -access $privs] + set path [GetProcessImageFileName $hprocess] + if {$type eq "name"} { + return [file tail $path] + } + # Returned path is in native format, convert to win32 + return [normalize_device_rooted_path $path] + } onerror {TWAPI_WIN32 87} { + return $opts(noexist) + } onerror {} { + # Other errors, continue on to other methods + } finally { + if {[info exists hprocess]} { + twapi::close_handle $hprocess + } + } + + trap { + set hprocess [get_process_handle $pid -access {process_query_information process_vm_read}] + } onerror {TWAPI_WIN32 87} { + return $opts(noexist) + } onerror {TWAPI_WIN32 5} { + # Access denied + # If it is the name we want, first try WTS and if that + # fails try getting it from PDH (slowest) + + if {[string equal $type "name"]} { + if {! [catch {WTSEnumerateProcesses NULL} precords]} { + + return [lindex [recordarray column $precords pProcessName -filter [list [list ProcessId == $pid]]] 0] + } + + # That failed as well, try PDH. TBD - get rid of PDH + set pdh_path [lindex [lindex [twapi::get_perf_process_counter_paths [list $pid] -pid] 0] 3] + array set pdhinfo [pdh_parse_counter_path $pdh_path] + return $pdhinfo(instance) + } + return $opts(noaccess) + } + + trap { + set module [lindex [EnumProcessModules $hprocess] 0] + if {[string equal $type "name"]} { + set path [GetModuleBaseName $hprocess $module] + } else { + set path [_normalize_path [GetModuleFileNameEx $hprocess $module]] + } + } onerror {TWAPI_WIN32 5} { + # Access denied + # On win2k (and may be Win2k3), if the process has exited but some + # app still has a handle to the process, the OpenProcess succeeds + # but the EnumProcessModules call returns access denied. So + # check for this case + if {[min_os_version 5 0]} { + # Try getting exit code. 259 means still running. + # Anything else means process has terminated + if {[GetExitCodeProcess $hprocess] == 259} { + return $opts(noaccess) + } else { + return $opts(noexist) + } + } else { + rethrow + } + } onerror {TWAPI_WIN32 299} { + # Partial read - usually means either we are WOW64 and target + # is 64bit, or process is exiting / starting and not all mem is + # reachable yet + return $opts(noaccess) + } finally { + CloseHandle $hprocess + } + return $path +} + +# Fill in arrays with result from WTSEnumerateProcesses if available +proc twapi::_get_wts_pids {v_sids v_names} { + # Note this call is expected to fail on NT 4.0 without terminal server + if {! [catch {WTSEnumerateProcesses NULL} precords]} { + upvar $v_sids wtssids + upvar $v_names wtsnames + array set wtssids [recordarray getlist $precords -slice {ProcessId pUserSid} -format flat] + array set wtsnames [recordarray getlist $precords -slice {ProcessId pUserSid} -format flat] + } +} + +# Return various information from a process token +proc twapi::_token_info_helper {args} { + package require twapi_security + proc _token_info_helper {args} { + if {[llength $args] == 1} { + # All options specified as one argument + set args [lindex $args 0] + } + + if {0} { + Following options are passed on to get_token_info: + elevation + virtualized + restrictedgroups + primarygroup + primarygroupsid + privileges + enabledprivileges + disabledprivileges + logonsession + linkedtoken + Option -integrity is not passed on because it has to deal with + -raw and -label options + } + + array set opts [parseargs args { + pid.arg + hprocess.arg + tid.arg + hthread.arg + integrity + raw + label + user + groups + groupsids + } -ignoreunknown] + + if {[expr {[info exists opts(pid)] + [info exists opts(hprocess)] + + [info exists opts(tid)] + [info exists opts(hthread)]}] > 1} { + error "At most one option from -pid, -tid, -hprocess, -hthread can be specified." + } + + if {$opts(user)} { + lappend args -usersid + } + + if {$opts(groups) || $opts(groupsids)} { + lappend args -groupsids + } + + if {[info exists opts(hprocess)]} { + set tok [open_process_token -hprocess $opts(hprocess)] + } elseif {[info exists opts(pid)]} { + set tok [open_process_token -pid $opts(pid)] + } elseif {[info exists opts(hthread)]} { + set tok [open_thread_token -hthread $opts(hthread)] + } elseif {[info exists opts(tid)]} { + set tok [open_thread_token -tid $opts(tid)] + } else { + # Default is current process + set tok [open_process_token] + } + + trap { + array set result [get_token_info $tok {*}$args] + if {[info exists result(-usersid)]} { + set result(-user) [lookup_account_sid $result(-usersid)] + unset result(-usersid) + } + if {[info exists result(-groupsids)]} { + if {$opts(groups)} { + set result(-groups) {} + foreach sid $result(-groupsids) { + if {[catch {lookup_account_sid $sid} gname]} { + lappend result(-groups) $sid + } else { + lappend result(-groups) $gname + } + } + } + if {!$opts(groupsids)} { + unset result(-groupsids) + } + } + if {$opts(integrity)} { + if {$opts(raw)} { + set integrity [get_token_integrity $tok -raw] + } elseif {$opts(label)} { + set integrity [get_token_integrity $tok -label] + } else { + set integrity [get_token_integrity $tok] + } + set result(-integrity) $integrity + } + } finally { + close_token $tok + } + + return [array get result] + } + + return [_token_info_helper {*}$args] +} + +# Set various information for a process token +# Caller assumed to have enabled appropriate privileges +proc twapi::_token_set_helper {args} { + package require twapi_security + + proc _token_set_helper {args} { + if {[llength $args] == 1} { + # All options specified as one argument + set args [lindex $args 0] + } + + array set opts [parseargs args { + virtualized.bool + integrity.arg + {noexist.arg "(no such process)"} + {noaccess.arg "(unknown)"} + pid.arg + hprocess.arg + } -maxleftover 0] + + if {[info exists opts(pid)] && [info exists opts(hprocess)]} { + error "Options -pid and -hprocess cannot be specified together." + } + + # Open token with appropriate access rights depending on request. + set access [list token_adjust_default] + + if {[info exists opts(hprocess)]} { + set tok [open_process_token -hprocess $opts(hprocess) -access $access] + } elseif {[info exists opts(pid)]} { + set tok [open_process_token -pid $opts(pid) -access $access] + } else { + # Default is current process + set tok [open_process_token -access $access] + } + + set result [list ] + trap { + if {[info exists opts(integrity)]} { + set_token_integrity $tok $opts(integrity) + } + if {[info exists opts(virtualized)]} { + set_token_virtualization $tok $opts(virtualized) + } + } finally { + close_token $tok + } + + return $result + } + return [_token_set_helper {*}$args] +} + +# Map console color name to integer attribute +proc twapi::_map_console_color {colors background} { + set attr 0 + foreach color $colors { + switch -exact -- $color { + blue {setbits attr 1} + green {setbits attr 2} + red {setbits attr 4} + white {setbits attr 7} + bright {setbits attr 8} + black { } + default {error "Unknown color name $color"} + } + } + if {$background} { + set attr [expr {$attr << 4}] + } + return $attr +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/rds.tcl b/src/vendorlib_tcl8/twapi-5.0b1/rds.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/rds.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/rds.tcl index 9f2757a1..d17a2b32 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/rds.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/rds.tcl @@ -1,191 +1,191 @@ -# -# Copyright (c) 2010, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Remote Desktop Services - TBD - document and test - -namespace eval twapi {} - -proc twapi::rds_enumerate_sessions {args} { - array set opts [parseargs args { - {hserver.arg 0} - state.arg - } -maxleftover 0] - - set states {active connected connectquery shadow disconnected idle listen reset down init} - if {[info exists opts(state)]} { - if {[string is integer -strict $opts(state)]} { - set state $opts(state) - } else { - set state [lsearch -exact $states $opts(state)] - if {$state < 0} { - error "Invalid value '$opts(state)' specified for -state option." - } - } - } - - set sessions [WTSEnumerateSessions $opts(hserver)] - - if {[info exists state]} { - set sessions [recordarray get $sessions -filter [list [list State == $state]]] - } - - set result {} - foreach {sess rec} [recordarray getdict $sessions -key SessionId -format dict] { - set state [lindex $states [kl_get $rec State]] - if {$state eq ""} { - set state [kl_get $rec State] - } - lappend result $sess [list -tssession [kl_get $rec SessionId] \ - -winstaname [kl_get $rec pWinStationName] \ - -state $state] - } - return $result -} - -proc twapi::rds_disconnect_session args { - array set opts [parseargs args { - {hserver.arg 0} - {tssession.int -1} - {async.bool false} - } -maxleftover 0] - - WTSDisconnectSession $opts(hserver) $opts(tssession) [expr {! $opts(async)}] - -} - -proc twapi::rds_logoff_session args { - array set opts [parseargs args { - {hserver.arg 0} - {tssession.int -1} - {async.bool false} - } -maxleftover 0] - - WTSLogoffSession $opts(hserver) $opts(tssession) [expr {! $opts(async)}] -} - -proc twapi::rds_query_session_information {infoclass args} { - array set opts [parseargs args { - {hserver.arg 0} - {tssession.int -1} - } -maxleftover 0] - - return [WTSQuerySessionInformation $opts(hserver) $opts(tssession) $infoclass] -} - -interp alias {} twapi::rds_get_session_appname {} twapi::rds_query_session_information 1 -interp alias {} twapi::rds_get_session_clientdir {} twapi::rds_query_session_information 11 -interp alias {} twapi::rds_get_session_clientname {} twapi::rds_query_session_information 10 -interp alias {} twapi::rds_get_session_userdomain {} twapi::rds_query_session_information 7 -interp alias {} twapi::rds_get_session_initialprogram {} twapi::rds_query_session_information 0 -interp alias {} twapi::rds_get_session_oemid {} twapi::rds_query_session_information 3 -interp alias {} twapi::rds_get_session_user {} twapi::rds_query_session_information 5 -interp alias {} twapi::rds_get_session_winsta {} twapi::rds_query_session_information 6 -interp alias {} twapi::rds_get_session_intialdir {} twapi::rds_query_session_information 2 -interp alias {} twapi::rds_get_session_clientbuild {} twapi::rds_query_session_information 9 -interp alias {} twapi::rds_get_session_clienthwid {} twapi::rds_query_session_information 13 -interp alias {} twapi::rds_get_session_state {} twapi::rds_query_session_information 8 -interp alias {} twapi::rds_get_session_id {} twapi::rds_query_session_information 4 -interp alias {} twapi::rds_get_session_productid {} twapi::rds_query_session_information 12 -interp alias {} twapi::rds_get_session_protocol {} twapi::rds_query_session_information 16 - - -proc twapi::rds_send_message {args} { - - array set opts [parseargs args { - {hserver.arg 0} - tssession.int - title.arg - message.arg - {buttons.arg ok} - {icon.arg information} - defaultbutton.arg - {modality.arg task {task appl application system}} - {justify.arg left {left right}} - rtl.bool - foreground.bool - topmost.bool - showhelp.bool - service.bool - timeout.int - async.bool - } -maxleftover 0 -nulldefault] - - if {![kl_vget { - ok {0 {ok}} - okcancel {1 {ok cancel}} - abortretryignore {2 {abort retry ignore}} - yesnocancel {3 {yes no cancel}} - yesno {4 {yes no}} - retrycancel {5 {retry cancel}} - canceltrycontinue {6 {cancel try continue}} - } $opts(buttons) buttons]} { - error "Invalid value '$opts(buttons)' specified for option -buttons." - } - - set style [lindex $buttons 0] - switch -exact -- $opts(icon) { - warning - - exclamation {setbits style 0x30} - asterisk - - information {setbits style 0x40} - question {setbits style 0x20} - error - - hand - - stop {setbits style 0x10} - default { - error "Invalid value '$opts(icon)' specified for option -icon." - } - } - - # Map the default button - switch -exact -- [lsearch -exact [lindex $buttons 1] $opts(defaultbutton)] { - 1 {setbits style 0x100 } - 2 {setbits style 0x200 } - 3 {setbits style 0x300 } - default { - # First button, - # setbits style 0x000 - } - } - - switch -exact -- $opts(modality) { - system { setbits style 0x1000 } - task { setbits style 0x2000 } - appl - - application - - default { - # setbits style 0x0000 - } - } - - if {$opts(showhelp)} { setbits style 0x00004000 } - if {$opts(rtl)} { setbits style 0x00100000 } - if {$opts(justify) eq "right"} { setbits style 0x00080000 } - if {$opts(topmost)} { setbits style 0x00040000 } - if {$opts(foreground)} { setbits style 0x00010000 } - if {$opts(service)} { setbits style 0x00200000 } - - set response [WTSSendMessage $opts(hserver) $opts(tssession) $opts(title) \ - $opts(message) $style $opts(timeout) \ - [expr {!$opts(async)}]] - - switch -exact -- $response { - 1 { return ok } - 2 { return cancel } - 3 { return abort } - 4 { return retry } - 5 { return ignore } - 6 { return yes } - 7 { return no } - 8 { return close } - 9 { return help } - 10 { return tryagain } - 11 { return continue } - 32000 { return timeout } - 32001 { return async } - default { return $response } - } -} +# +# Copyright (c) 2010, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Remote Desktop Services - TBD - document and test + +namespace eval twapi {} + +proc twapi::rds_enumerate_sessions {args} { + array set opts [parseargs args { + {hserver.arg 0} + state.arg + } -maxleftover 0] + + set states {active connected connectquery shadow disconnected idle listen reset down init} + if {[info exists opts(state)]} { + if {[string is integer -strict $opts(state)]} { + set state $opts(state) + } else { + set state [lsearch -exact $states $opts(state)] + if {$state < 0} { + error "Invalid value '$opts(state)' specified for -state option." + } + } + } + + set sessions [WTSEnumerateSessions $opts(hserver)] + + if {[info exists state]} { + set sessions [recordarray get $sessions -filter [list [list State == $state]]] + } + + set result {} + foreach {sess rec} [recordarray getdict $sessions -key SessionId -format dict] { + set state [lindex $states [kl_get $rec State]] + if {$state eq ""} { + set state [kl_get $rec State] + } + lappend result $sess [list -tssession [kl_get $rec SessionId] \ + -winstaname [kl_get $rec pWinStationName] \ + -state $state] + } + return $result +} + +proc twapi::rds_disconnect_session args { + array set opts [parseargs args { + {hserver.arg 0} + {tssession.int -1} + {async.bool false} + } -maxleftover 0] + + WTSDisconnectSession $opts(hserver) $opts(tssession) [expr {! $opts(async)}] + +} + +proc twapi::rds_logoff_session args { + array set opts [parseargs args { + {hserver.arg 0} + {tssession.int -1} + {async.bool false} + } -maxleftover 0] + + WTSLogoffSession $opts(hserver) $opts(tssession) [expr {! $opts(async)}] +} + +proc twapi::rds_query_session_information {infoclass args} { + array set opts [parseargs args { + {hserver.arg 0} + {tssession.int -1} + } -maxleftover 0] + + return [WTSQuerySessionInformation $opts(hserver) $opts(tssession) $infoclass] +} + +interp alias {} twapi::rds_get_session_appname {} twapi::rds_query_session_information 1 +interp alias {} twapi::rds_get_session_clientdir {} twapi::rds_query_session_information 11 +interp alias {} twapi::rds_get_session_clientname {} twapi::rds_query_session_information 10 +interp alias {} twapi::rds_get_session_userdomain {} twapi::rds_query_session_information 7 +interp alias {} twapi::rds_get_session_initialprogram {} twapi::rds_query_session_information 0 +interp alias {} twapi::rds_get_session_oemid {} twapi::rds_query_session_information 3 +interp alias {} twapi::rds_get_session_user {} twapi::rds_query_session_information 5 +interp alias {} twapi::rds_get_session_winsta {} twapi::rds_query_session_information 6 +interp alias {} twapi::rds_get_session_intialdir {} twapi::rds_query_session_information 2 +interp alias {} twapi::rds_get_session_clientbuild {} twapi::rds_query_session_information 9 +interp alias {} twapi::rds_get_session_clienthwid {} twapi::rds_query_session_information 13 +interp alias {} twapi::rds_get_session_state {} twapi::rds_query_session_information 8 +interp alias {} twapi::rds_get_session_id {} twapi::rds_query_session_information 4 +interp alias {} twapi::rds_get_session_productid {} twapi::rds_query_session_information 12 +interp alias {} twapi::rds_get_session_protocol {} twapi::rds_query_session_information 16 + + +proc twapi::rds_send_message {args} { + + array set opts [parseargs args { + {hserver.arg 0} + tssession.int + title.arg + message.arg + {buttons.arg ok} + {icon.arg information} + defaultbutton.arg + {modality.arg task {task appl application system}} + {justify.arg left {left right}} + rtl.bool + foreground.bool + topmost.bool + showhelp.bool + service.bool + timeout.int + async.bool + } -maxleftover 0 -nulldefault] + + if {![kl_vget { + ok {0 {ok}} + okcancel {1 {ok cancel}} + abortretryignore {2 {abort retry ignore}} + yesnocancel {3 {yes no cancel}} + yesno {4 {yes no}} + retrycancel {5 {retry cancel}} + canceltrycontinue {6 {cancel try continue}} + } $opts(buttons) buttons]} { + error "Invalid value '$opts(buttons)' specified for option -buttons." + } + + set style [lindex $buttons 0] + switch -exact -- $opts(icon) { + warning - + exclamation {setbits style 0x30} + asterisk - + information {setbits style 0x40} + question {setbits style 0x20} + error - + hand - + stop {setbits style 0x10} + default { + error "Invalid value '$opts(icon)' specified for option -icon." + } + } + + # Map the default button + switch -exact -- [lsearch -exact [lindex $buttons 1] $opts(defaultbutton)] { + 1 {setbits style 0x100 } + 2 {setbits style 0x200 } + 3 {setbits style 0x300 } + default { + # First button, + # setbits style 0x000 + } + } + + switch -exact -- $opts(modality) { + system { setbits style 0x1000 } + task { setbits style 0x2000 } + appl - + application - + default { + # setbits style 0x0000 + } + } + + if {$opts(showhelp)} { setbits style 0x00004000 } + if {$opts(rtl)} { setbits style 0x00100000 } + if {$opts(justify) eq "right"} { setbits style 0x00080000 } + if {$opts(topmost)} { setbits style 0x00040000 } + if {$opts(foreground)} { setbits style 0x00010000 } + if {$opts(service)} { setbits style 0x00200000 } + + set response [WTSSendMessage $opts(hserver) $opts(tssession) $opts(title) \ + $opts(message) $style $opts(timeout) \ + [expr {!$opts(async)}]] + + switch -exact -- $response { + 1 { return ok } + 2 { return cancel } + 3 { return abort } + 4 { return retry } + 5 { return ignore } + 6 { return yes } + 7 { return no } + 8 { return close } + 9 { return help } + 10 { return tryagain } + 11 { return continue } + 32000 { return timeout } + 32001 { return async } + default { return $response } + } +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/registry.tcl b/src/vendorlib_tcl8/twapi-5.0b1/registry.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/registry.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/registry.tcl index 9cb3403d..5484227f 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/registry.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/registry.tcl @@ -1,490 +1,490 @@ -# -# Copyright (c) 2020 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi {} - -# -# TBD -32bit and -64bit options are not documented -# pending test cases - -proc twapi::reg_key_copy {hkey to_hkey args} { - parseargs args { - subkey.arg - copysecd.bool - } -setvars -maxleftover 0 -nulldefault - - if {$copysecd} { - RegCopyTree $hkey $subkey $to_hkey - } else { - SHCopyKey $hkey $subkey $to_hkey - } -} - -proc twapi::reg_key_create {hkey subkey args} { - # TBD - document -link - # [opt_def [cmd -link] [arg BOOL]] If [const true], [arg SUBKEY] is stored as the - # value of the [const SymbolicLinkValue] value under [arg HKEY]. Default is - # [const false]. - parseargs args { - {access.arg generic_read} - {inherit.bool 0} - {secd.arg ""} - {volatile.bool 0 0x1} - {link.bool 0 0x2} - {backup.bool 0 0x4} - 32bit - 64bit - disposition.arg - } -maxleftover 0 -setvars - - set access [_access_rights_to_mask $access] - # Note: Following might be set via -access as well. The -32bit and -64bit - # options just make it a little more convenient for caller - if {$32bit} { - set access [expr {$access | 0x200}] - } - if {$64bit} { - set access [expr {$access | 0x100}] - } - lassign [RegCreateKeyEx \ - $hkey \ - $subkey \ - 0 \ - "" \ - [expr {$volatile | $backup}] \ - $access \ - [_make_secattr $secd $inherit] \ - ] hkey disposition_value - if {[info exists disposition]} { - upvar 1 $disposition created_or_existed - if {$disposition_value == 1} { - set created_or_existed created - } else { - # disposition_value == 2 - set created_or_existed existed - } - } - return $hkey -} - -proc twapi::reg_key_delete {hkey subkey args} { - parseargs args { - 32bit - 64bit - } -maxleftover 0 -setvars - - # TBD - document options after adding tests - set access 0 - if {$32bit} { - set access [expr {$access | 0x200}] - } - if {$64bit} { - set access [expr {$access | 0x100}] - } - - RegDeleteKeyEx $hkey $subkey $access -} - -proc twapi::reg_keys {hkey {subkey {}}} { - if {$subkey ne ""} { - set hkey [reg_key_open $hkey $subkey] - } - try { - return [RegEnumKeyEx $hkey 0] - } finally { - if {$subkey ne ""} { - reg_key_close $hkey - } - } -} - -proc twapi::reg_key_open {hkey subkey args} { - # Not documented: -link, -32bit, -64bit - # [opt_def [cmd -link] [arg BOOL]] If [const true], specifies the key is a - # symbolic link. Defaults to [const false]. - parseargs args { - {link.bool 0} - {access.arg generic_read} - 32bit - 64bit - } -maxleftover 0 -setvars - - set access [_access_rights_to_mask $access] - # Note: Following might be set via -access as well. The -32bit and -64bit - # options just make it a little more convenient for caller - if {$32bit} { - set access [expr {$access | 0x200}] - } - if {$64bit} { - set access [expr {$access | 0x100}] - } - return [RegOpenKeyEx $hkey $subkey $link $access] -} - -proc twapi::reg_value_delete {hkey args} { - if {[llength $args] == 1} { - RegDeleteValue $hkey [lindex $args 0] - } elseif {[llength $args] == 2} { - RegDeleteKeyValue $hkey {*}$args - } else { - error "Wrong # args: should be \"reg_value_delete ?SUBKEY? VALUENAME\"" - } -} - -proc twapi::reg_key_current_user {args} { - parseargs args { - {access.arg generic_read} - 32bit - 64bit - } -maxleftover 0 -setvars - - set access [_access_rights_to_mask $access] - # Note: Following might be set via -access as well. The -32bit and -64bit - # options just make it a little more convenient for caller - if {$32bit} { - set access [expr {$access | 0x200}] - } - if {$64bit} { - set access [expr {$access | 0x100}] - } - return [RegOpenCurrentUser $access] -} - -proc twapi::reg_key_user_classes_root {usertoken args} { - parseargs args { - {access.arg generic_read} - 32bit - 64bit - } -maxleftover 0 -setvars - - set access [_access_rights_to_mask $access] - # Note: Following might be set via -access as well. The -32bit and -64bit - # options just make it a little more convenient for caller - if {$32bit} { - set access [expr {$access | 0x200}] - } - if {$64bit} { - set access [expr {$access | 0x100}] - } - return [RegOpenUserClassesRoot $usertoken 0 $access] -} - -proc twapi::reg_key_export {hkey filepath args} { - parseargs args { - {secd.arg {}} - {format.arg xp {win2k xp}} - {compress.bool 1} - } -setvars - - set format [dict get {win2k 1 xp 2} $format] - if {! $compress} { - set format [expr {$format | 4}] - } - twapi::eval_with_privileges { - RegSaveKeyEx $hkey $filepath [_make_secattr $secd 0] $format - } SeBackupPrivilege -} - -proc twapi::reg_key_import {hkey filepath args} { - parseargs args { - {volatile.bool 0 0x1} - {force.bool 0 0x8} - } -setvars - twapi::eval_with_privileges { - RegRestoreKey $hkey $filepath [expr {$force | $volatile}] - } {SeBackupPrivilege SeRestorePrivilege} -} - -proc twapi::reg_key_load {hkey hivename filepath} { - twapi::eval_with_privileges { - RegLoadKey $hkey $subkey $filepath - } {SeBackupPrivilege SeRestorePrivilege} -} - -proc twapi::reg_key_unload {hkey hivename} { - twapi::eval_with_privileges { - RegUnLoadKey $hkey $subkey - } {SeBackupPrivilege SeRestorePrivilege} -} - -proc twapi::reg_key_monitor {hkey hevent args} { - parseargs args { - {keys.bool 0 0x1} - {attr.bool 0 0x2} - {values.bool 0 0x4} - {secd.bool 0 0x8} - {subtree.bool 0} - } -setvars - - set filter [expr {$keys | $attr | $values | $secd}] - if {$filter == 0} { - set filter 0xf - } - - RegNotifyChangeKeyValue $hkey $subtree $filter $hevent 1 -} - -proc twapi::reg_value_names {hkey {subkey {}}} { - if {$subkey eq ""} { - # 0 - value names only - return [RegEnumValue $hkey 0] - } - set hkey [reg_key_open $hkey $subkey] - try { - # 0 - value names only - return [RegEnumValue $hkey 0] - } finally { - reg_key_close $hkey - } -} - -proc twapi::reg_values {hkey {subkey {}}} { - if {$subkey eq ""} { - # 3 -> 0x1 - return data values, 0x2 - cooked data - return [RegEnumValue $hkey 3] - } - set hkey [reg_key_open $hkey $subkey] - try { - # 3 -> 0x1 - return data values, 0x2 - cooked data - return [RegEnumValue $hkey 3] - } finally { - reg_key_close $hkey - } -} - -proc twapi::reg_values_raw {hkey {subkey {}}} { - if {$subkey eq ""} { - # 0x1 - return data values - return [RegEnumValue $hkey 1] - } - set hkey [reg_key_open $hkey $subkey] - try { - return [RegEnumValue $hkey 1] - } finally { - reg_key_close $hkey - } -} - -proc twapi::reg_value_raw {hkey args} { - if {[llength $args] == 1} { - return [RegQueryValueEx $hkey [lindex $args 0] false] - } elseif {[llength $args] == 2} { - return [RegGetValue $hkey {*}$args 0x1000ffff false] - } else { - error "wrong # args: should be \"reg_value_get HKEY ?SUBKEY? VALUENAME\"" - } -} - -proc twapi::reg_value {hkey args} { - if {[llength $args] == 1} { - return [RegQueryValueEx $hkey [lindex $args 0] true] - } elseif {[llength $args] == 2} { - return [RegGetValue $hkey {*}$args 0x1000ffff true] - } else { - error "wrong # args: should be \"reg_value_get HKEY ?SUBKEY? VALUENAME\"" - } -} - -if {[twapi::min_os_version 6]} { - proc twapi::reg_value_set {hkey args} { - if {[llength $args] == 3} { - return [RegSetValueEx $hkey {*}$args] - } elseif {[llength $args] == 4} { - return [RegSetKeyValue $hkey {*}$args] - } else { - error "wrong # args: should be \"reg_value_set HKEY ?SUBKEY? VALUENAME TYPE VALUE\"" - } - } -} else { - proc twapi::reg_value_set {hkey args} { - if {[llength $args] == 3} { - lassign $args value_name value_type value - } elseif {[llength $args] == 4} { - lassign $args subkey value_name value_type value - set hkey [reg_key_open $hkey $subkey -access key_set_value] - } else { - error "wrong # args: should be \"reg_value_set HKEY ?SUBKEY? VALUENAME TYPE VALUE\"" - } - try { - RegSetValueEx $hkey $value_name $value_type $value - } finally { - if {[info exists subkey]} { - # We opened hkey - reg_close_key $hkey - } - } - } -} - -proc twapi::reg_key_override_undo {hkey} { - RegOverridePredefKey $hkey 0 -} - -proc twapi::_reg_walker {hkey path callback cbdata} { - # Callback for the key - set code [catch { - {*}$callback $cbdata $hkey $path - } cbdata ropts] - if {$code != 0} { - if {$code == 4} { - # Continue - skip children, continue with siblings - return $cbdata - } elseif {$code == 3} { - # Skip siblings as well - return -code break $cbdata - } elseif {$code == 2} { - # Stop complete iteration - return -code return $cbdata - } else { - return -options $ropts $cbdata - } - } - - # Iterate over child keys - foreach child_key [reg_keys $hkey] { - set child_hkey [reg_key_open $hkey $child_key] - try { - # Recurse to call into children - set code [catch { - _reg_walker $child_hkey [linsert $path end $child_key] $callback $cbdata - } cbdata ropts] - if {$code != 0 && $code != 4} { - if {$code == 3} { - # break - skip remaining child keys - return $cbdata - } elseif {$code == 2} { - # return - stop all iteration all up the tree - return -code return $cbdata - } else { - return -options $ropts $cbdata - } - } - } finally { - reg_key_close $child_hkey - } - } - - return $cbdata -} - -proc twapi::reg_walk {hkey args} { - parseargs args { - {subkey.arg {}} - callback.arg - {cbdata.arg ""} - } -maxleftover 0 -setvars - - - if {$subkey ne ""} { - set hkey [reg_key_open $hkey $subkey] - set path [list $subkey] - } else { - set path [list ] - } - - if {![info exists callback]} { - set callback [lambda {cbdata hkey path} {puts [join $path \\]}] - } - try { - set code [catch {_reg_walker $hkey $path $callback $cbdata } result ropts] - # Codes 2 (return), 3 (break) and 4 (continue) are just early terminations - if {$code == 1} { - return -options $ropts $result - } - } finally { - if {$subkey ne ""} { - reg_key_close $hkey - } - } - return $result -} - -proc twapi::_reg_iterator_callback {cbdata hkey path args} { - set cmd [yield [list $hkey $path {*}$args]] - # Loop until valid argument - while {1} { - switch -exact -- $cmd { - "" - - next { return $cbdata } - stop { return -code return $cbdata } - parentsibling { return -code break $cbdata } - sibling { return -code continue $cbdata } - default { - set ret [yieldto return -level 0 -code error "Invalid argument \"$cmd\"."] - } - } - } -} - -proc twapi::_reg_iterator_coro {hkey subkey} { - set cmd [yield [info coroutine]] - switch -exact -- $cmd { - "" - - next { - # Drop into reg_walk - } - stop - - parentsibling - - sibling { - return {} - } - default { - error "Invalid argument \"$cmd\"." - } - } - if {$subkey ne ""} { - set hkey [reg_key_open $hkey $subkey] - } - try { - reg_walk $hkey -callback [namespace current]::_reg_iterator_callback - } finally { - if {$subkey ne ""} { - reg_key_close $hkey - } - } - return -} - -proc twapi::reg_iterator {hkey {subkey {}}} { - variable reg_walk_counter - - return [coroutine "regwalk#[incr reg_walk_counter]" _reg_iterator_coro $hkey $subkey] -} - -proc twapi::reg_tree {hkey {subkey {}}} { - - set iter [reg_iterator $hkey $subkey] - - set paths {} - while {[llength [set item [$iter next]]]} { - lappend paths [join [lindex $item 1] \\] - } - return $paths -} - -proc twapi::reg_tree_values {hkey {subkey {}}} { - - set iter [reg_iterator $hkey $subkey] - - set tree {} - # Note here we cannot ignore the first empty node corresponding - # to the root because we have to return any values it contains. - while {[llength [set item [$iter next]]]} { - dict set tree [join [lindex $item 1] \\] [reg_values [lindex $item 0]] - } - return $tree -} - -proc twapi::reg_tree_values_raw {hkey {subkey {}}} { - set iter [reg_iterator $hkey $subkey] - - set tree {} - while {[llength [set item [$iter next]]]} { - dict set tree [join [lindex $item 1] \\] [reg_values_raw [lindex $item 0]] - } - return $tree -} - +# +# Copyright (c) 2020 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi {} + +# +# TBD -32bit and -64bit options are not documented +# pending test cases + +proc twapi::reg_key_copy {hkey to_hkey args} { + parseargs args { + subkey.arg + copysecd.bool + } -setvars -maxleftover 0 -nulldefault + + if {$copysecd} { + RegCopyTree $hkey $subkey $to_hkey + } else { + SHCopyKey $hkey $subkey $to_hkey + } +} + +proc twapi::reg_key_create {hkey subkey args} { + # TBD - document -link + # [opt_def [cmd -link] [arg BOOL]] If [const true], [arg SUBKEY] is stored as the + # value of the [const SymbolicLinkValue] value under [arg HKEY]. Default is + # [const false]. + parseargs args { + {access.arg generic_read} + {inherit.bool 0} + {secd.arg ""} + {volatile.bool 0 0x1} + {link.bool 0 0x2} + {backup.bool 0 0x4} + 32bit + 64bit + disposition.arg + } -maxleftover 0 -setvars + + set access [_access_rights_to_mask $access] + # Note: Following might be set via -access as well. The -32bit and -64bit + # options just make it a little more convenient for caller + if {$32bit} { + set access [expr {$access | 0x200}] + } + if {$64bit} { + set access [expr {$access | 0x100}] + } + lassign [RegCreateKeyEx \ + $hkey \ + $subkey \ + 0 \ + "" \ + [expr {$volatile | $backup}] \ + $access \ + [_make_secattr $secd $inherit] \ + ] hkey disposition_value + if {[info exists disposition]} { + upvar 1 $disposition created_or_existed + if {$disposition_value == 1} { + set created_or_existed created + } else { + # disposition_value == 2 + set created_or_existed existed + } + } + return $hkey +} + +proc twapi::reg_key_delete {hkey subkey args} { + parseargs args { + 32bit + 64bit + } -maxleftover 0 -setvars + + # TBD - document options after adding tests + set access 0 + if {$32bit} { + set access [expr {$access | 0x200}] + } + if {$64bit} { + set access [expr {$access | 0x100}] + } + + RegDeleteKeyEx $hkey $subkey $access +} + +proc twapi::reg_keys {hkey {subkey {}}} { + if {$subkey ne ""} { + set hkey [reg_key_open $hkey $subkey] + } + try { + return [RegEnumKeyEx $hkey 0] + } finally { + if {$subkey ne ""} { + reg_key_close $hkey + } + } +} + +proc twapi::reg_key_open {hkey subkey args} { + # Not documented: -link, -32bit, -64bit + # [opt_def [cmd -link] [arg BOOL]] If [const true], specifies the key is a + # symbolic link. Defaults to [const false]. + parseargs args { + {link.bool 0} + {access.arg generic_read} + 32bit + 64bit + } -maxleftover 0 -setvars + + set access [_access_rights_to_mask $access] + # Note: Following might be set via -access as well. The -32bit and -64bit + # options just make it a little more convenient for caller + if {$32bit} { + set access [expr {$access | 0x200}] + } + if {$64bit} { + set access [expr {$access | 0x100}] + } + return [RegOpenKeyEx $hkey $subkey $link $access] +} + +proc twapi::reg_value_delete {hkey args} { + if {[llength $args] == 1} { + RegDeleteValue $hkey [lindex $args 0] + } elseif {[llength $args] == 2} { + RegDeleteKeyValue $hkey {*}$args + } else { + error "Wrong # args: should be \"reg_value_delete ?SUBKEY? VALUENAME\"" + } +} + +proc twapi::reg_key_current_user {args} { + parseargs args { + {access.arg generic_read} + 32bit + 64bit + } -maxleftover 0 -setvars + + set access [_access_rights_to_mask $access] + # Note: Following might be set via -access as well. The -32bit and -64bit + # options just make it a little more convenient for caller + if {$32bit} { + set access [expr {$access | 0x200}] + } + if {$64bit} { + set access [expr {$access | 0x100}] + } + return [RegOpenCurrentUser $access] +} + +proc twapi::reg_key_user_classes_root {usertoken args} { + parseargs args { + {access.arg generic_read} + 32bit + 64bit + } -maxleftover 0 -setvars + + set access [_access_rights_to_mask $access] + # Note: Following might be set via -access as well. The -32bit and -64bit + # options just make it a little more convenient for caller + if {$32bit} { + set access [expr {$access | 0x200}] + } + if {$64bit} { + set access [expr {$access | 0x100}] + } + return [RegOpenUserClassesRoot $usertoken 0 $access] +} + +proc twapi::reg_key_export {hkey filepath args} { + parseargs args { + {secd.arg {}} + {format.arg xp {win2k xp}} + {compress.bool 1} + } -setvars + + set format [dict get {win2k 1 xp 2} $format] + if {! $compress} { + set format [expr {$format | 4}] + } + twapi::eval_with_privileges { + RegSaveKeyEx $hkey $filepath [_make_secattr $secd 0] $format + } SeBackupPrivilege +} + +proc twapi::reg_key_import {hkey filepath args} { + parseargs args { + {volatile.bool 0 0x1} + {force.bool 0 0x8} + } -setvars + twapi::eval_with_privileges { + RegRestoreKey $hkey $filepath [expr {$force | $volatile}] + } {SeBackupPrivilege SeRestorePrivilege} +} + +proc twapi::reg_key_load {hkey hivename filepath} { + twapi::eval_with_privileges { + RegLoadKey $hkey $subkey $filepath + } {SeBackupPrivilege SeRestorePrivilege} +} + +proc twapi::reg_key_unload {hkey hivename} { + twapi::eval_with_privileges { + RegUnLoadKey $hkey $subkey + } {SeBackupPrivilege SeRestorePrivilege} +} + +proc twapi::reg_key_monitor {hkey hevent args} { + parseargs args { + {keys.bool 0 0x1} + {attr.bool 0 0x2} + {values.bool 0 0x4} + {secd.bool 0 0x8} + {subtree.bool 0} + } -setvars + + set filter [expr {$keys | $attr | $values | $secd}] + if {$filter == 0} { + set filter 0xf + } + + RegNotifyChangeKeyValue $hkey $subtree $filter $hevent 1 +} + +proc twapi::reg_value_names {hkey {subkey {}}} { + if {$subkey eq ""} { + # 0 - value names only + return [RegEnumValue $hkey 0] + } + set hkey [reg_key_open $hkey $subkey] + try { + # 0 - value names only + return [RegEnumValue $hkey 0] + } finally { + reg_key_close $hkey + } +} + +proc twapi::reg_values {hkey {subkey {}}} { + if {$subkey eq ""} { + # 3 -> 0x1 - return data values, 0x2 - cooked data + return [RegEnumValue $hkey 3] + } + set hkey [reg_key_open $hkey $subkey] + try { + # 3 -> 0x1 - return data values, 0x2 - cooked data + return [RegEnumValue $hkey 3] + } finally { + reg_key_close $hkey + } +} + +proc twapi::reg_values_raw {hkey {subkey {}}} { + if {$subkey eq ""} { + # 0x1 - return data values + return [RegEnumValue $hkey 1] + } + set hkey [reg_key_open $hkey $subkey] + try { + return [RegEnumValue $hkey 1] + } finally { + reg_key_close $hkey + } +} + +proc twapi::reg_value_raw {hkey args} { + if {[llength $args] == 1} { + return [RegQueryValueEx $hkey [lindex $args 0] false] + } elseif {[llength $args] == 2} { + return [RegGetValue $hkey {*}$args 0x1000ffff false] + } else { + error "wrong # args: should be \"reg_value_get HKEY ?SUBKEY? VALUENAME\"" + } +} + +proc twapi::reg_value {hkey args} { + if {[llength $args] == 1} { + return [RegQueryValueEx $hkey [lindex $args 0] true] + } elseif {[llength $args] == 2} { + return [RegGetValue $hkey {*}$args 0x1000ffff true] + } else { + error "wrong # args: should be \"reg_value_get HKEY ?SUBKEY? VALUENAME\"" + } +} + +if {[twapi::min_os_version 6]} { + proc twapi::reg_value_set {hkey args} { + if {[llength $args] == 3} { + return [RegSetValueEx $hkey {*}$args] + } elseif {[llength $args] == 4} { + return [RegSetKeyValue $hkey {*}$args] + } else { + error "wrong # args: should be \"reg_value_set HKEY ?SUBKEY? VALUENAME TYPE VALUE\"" + } + } +} else { + proc twapi::reg_value_set {hkey args} { + if {[llength $args] == 3} { + lassign $args value_name value_type value + } elseif {[llength $args] == 4} { + lassign $args subkey value_name value_type value + set hkey [reg_key_open $hkey $subkey -access key_set_value] + } else { + error "wrong # args: should be \"reg_value_set HKEY ?SUBKEY? VALUENAME TYPE VALUE\"" + } + try { + RegSetValueEx $hkey $value_name $value_type $value + } finally { + if {[info exists subkey]} { + # We opened hkey + reg_close_key $hkey + } + } + } +} + +proc twapi::reg_key_override_undo {hkey} { + RegOverridePredefKey $hkey 0 +} + +proc twapi::_reg_walker {hkey path callback cbdata} { + # Callback for the key + set code [catch { + {*}$callback $cbdata $hkey $path + } cbdata ropts] + if {$code != 0} { + if {$code == 4} { + # Continue - skip children, continue with siblings + return $cbdata + } elseif {$code == 3} { + # Skip siblings as well + return -code break $cbdata + } elseif {$code == 2} { + # Stop complete iteration + return -code return $cbdata + } else { + return -options $ropts $cbdata + } + } + + # Iterate over child keys + foreach child_key [reg_keys $hkey] { + set child_hkey [reg_key_open $hkey $child_key] + try { + # Recurse to call into children + set code [catch { + _reg_walker $child_hkey [linsert $path end $child_key] $callback $cbdata + } cbdata ropts] + if {$code != 0 && $code != 4} { + if {$code == 3} { + # break - skip remaining child keys + return $cbdata + } elseif {$code == 2} { + # return - stop all iteration all up the tree + return -code return $cbdata + } else { + return -options $ropts $cbdata + } + } + } finally { + reg_key_close $child_hkey + } + } + + return $cbdata +} + +proc twapi::reg_walk {hkey args} { + parseargs args { + {subkey.arg {}} + callback.arg + {cbdata.arg ""} + } -maxleftover 0 -setvars + + + if {$subkey ne ""} { + set hkey [reg_key_open $hkey $subkey] + set path [list $subkey] + } else { + set path [list ] + } + + if {![info exists callback]} { + set callback [lambda {cbdata hkey path} {puts [join $path \\]}] + } + try { + set code [catch {_reg_walker $hkey $path $callback $cbdata } result ropts] + # Codes 2 (return), 3 (break) and 4 (continue) are just early terminations + if {$code == 1} { + return -options $ropts $result + } + } finally { + if {$subkey ne ""} { + reg_key_close $hkey + } + } + return $result +} + +proc twapi::_reg_iterator_callback {cbdata hkey path args} { + set cmd [yield [list $hkey $path {*}$args]] + # Loop until valid argument + while {1} { + switch -exact -- $cmd { + "" - + next { return $cbdata } + stop { return -code return $cbdata } + parentsibling { return -code break $cbdata } + sibling { return -code continue $cbdata } + default { + set ret [yieldto return -level 0 -code error "Invalid argument \"$cmd\"."] + } + } + } +} + +proc twapi::_reg_iterator_coro {hkey subkey} { + set cmd [yield [info coroutine]] + switch -exact -- $cmd { + "" - + next { + # Drop into reg_walk + } + stop - + parentsibling - + sibling { + return {} + } + default { + error "Invalid argument \"$cmd\"." + } + } + if {$subkey ne ""} { + set hkey [reg_key_open $hkey $subkey] + } + try { + reg_walk $hkey -callback [namespace current]::_reg_iterator_callback + } finally { + if {$subkey ne ""} { + reg_key_close $hkey + } + } + return +} + +proc twapi::reg_iterator {hkey {subkey {}}} { + variable reg_walk_counter + + return [coroutine "regwalk#[incr reg_walk_counter]" _reg_iterator_coro $hkey $subkey] +} + +proc twapi::reg_tree {hkey {subkey {}}} { + + set iter [reg_iterator $hkey $subkey] + + set paths {} + while {[llength [set item [$iter next]]]} { + lappend paths [join [lindex $item 1] \\] + } + return $paths +} + +proc twapi::reg_tree_values {hkey {subkey {}}} { + + set iter [reg_iterator $hkey $subkey] + + set tree {} + # Note here we cannot ignore the first empty node corresponding + # to the root because we have to return any values it contains. + while {[llength [set item [$iter next]]]} { + dict set tree [join [lindex $item 1] \\] [reg_values [lindex $item 0]] + } + return $tree +} + +proc twapi::reg_tree_values_raw {hkey {subkey {}}} { + set iter [reg_iterator $hkey $subkey] + + set tree {} + while {[llength [set item [$iter next]]]} { + dict set tree [join [lindex $item 1] \\] [reg_values_raw [lindex $item 0]] + } + return $tree +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/resource.tcl b/src/vendorlib_tcl8/twapi-5.0b1/resource.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/resource.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/resource.tcl index fea6cdda..4323e8fe 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/resource.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/resource.tcl @@ -1,458 +1,458 @@ -# Commands related to resource manipulation -# -# Copyright (c) 2003-2012 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -package require twapi_nls - -# Retrieve version info for a file -proc twapi::get_file_version_resource {path args} { - array set opts [parseargs args { - all - datetime - signature - structversion - fileversion - productversion - flags - fileos - filetype - foundlangid - foundcodepage - langid.arg - codepage.arg - }] - - - set ver [Twapi_GetFileVersionInfo $path] - - trap { - array set verinfo [Twapi_VerQueryValue_FIXEDFILEINFO $ver] - - set result [list ] - if {$opts(all) || $opts(signature)} { - lappend result -signature [format 0x%x $verinfo(dwSignature)] - } - - if {$opts(all) || $opts(structversion)} { - lappend result -structversion "[expr {0xffff & ($verinfo(dwStrucVersion) >> 16)}].[expr {0xffff & $verinfo(dwStrucVersion)}]" - } - - if {$opts(all) || $opts(fileversion)} { - lappend result -fileversion "[expr {0xffff & ($verinfo(dwFileVersionMS) >> 16)}].[expr {0xffff & $verinfo(dwFileVersionMS)}].[expr {0xffff & ($verinfo(dwFileVersionLS) >> 16)}].[expr {0xffff & $verinfo(dwFileVersionLS)}]" - } - - if {$opts(all) || $opts(productversion)} { - lappend result -productversion "[expr {0xffff & ($verinfo(dwProductVersionMS) >> 16)}].[expr {0xffff & $verinfo(dwProductVersionMS)}].[expr {0xffff & ($verinfo(dwProductVersionLS) >> 16)}].[expr {0xffff & $verinfo(dwProductVersionLS)}]" - } - - if {$opts(all) || $opts(flags)} { - set flags [expr {$verinfo(dwFileFlags) & $verinfo(dwFileFlagsMask)}] - lappend result -flags \ - [_make_symbolic_bitmask \ - [expr {$verinfo(dwFileFlags) & $verinfo(dwFileFlagsMask)}] \ - { - debug 1 - prerelease 2 - patched 4 - privatebuild 8 - infoinferred 16 - specialbuild 32 - } \ - ] - } - - if {$opts(all) || $opts(fileos)} { - switch -exact -- [format %08x $verinfo(dwFileOS)] { - 00010000 {set os dos} - 00020000 {set os os216} - 00030000 {set os os232} - 00040000 {set os nt} - 00050000 {set os wince} - 00000001 {set os windows16} - 00000002 {set os pm16} - 00000003 {set os pm32} - 00000004 {set os windows32} - 00010001 {set os dos_windows16} - 00010004 {set os dos_windows32} - 00020002 {set os os216_pm16} - 00030003 {set os os232_pm32} - 00040004 {set os nt_windows32} - default {set os $verinfo(dwFileOS)} - } - lappend result -fileos $os - } - - if {$opts(all) || $opts(filetype)} { - switch -exact -- [expr {0+$verinfo(dwFileType)}] { - 1 {set type application} - 2 {set type dll} - 3 { - set type "driver." - switch -exact -- [expr {0+$verinfo(dwFileSubtype)}] { - 1 {append type printer} - 2 {append type keyboard} - 3 {append type language} - 4 {append type display} - 5 {append type mouse} - 6 {append type network} - 7 {append type system} - 8 {append type installable} - 9 {append type sound} - 10 {append type comm} - 11 {append type inputmethod} - 12 {append type versionedprinter} - default {append type $verinfo(dwFileSubtype)} - } - } - 4 { - set type "font." - switch -exact -- [expr {0+$verinfo(dwFileSubtype)}] { - 1 {append type raster} - 2 {append type vector} - 3 {append type truetype} - default {append type $verinfo(dwFileSubtype)} - } - } - 5 { set type "vxd.$verinfo(dwFileSubtype)" } - 7 {set type staticlib} - default { - set type "$verinfo(dwFileType).$verinfo(dwFileSubtype)" - } - } - lappend result -filetype $type - } - - if {$opts(all) || $opts(datetime)} { - lappend result -datetime [expr {($verinfo(dwFileDateMS) << 32) + $verinfo(dwFileDateLS)}] - } - - # Any remaining arguments are treated as string names - - if {[llength $args] || $opts(foundlangid) || $opts(foundcodepage) || $opts(all)} { - # Find list of langid's and codepages and do closest match - set langid [expr {[info exists opts(langid)] ? $opts(langid) : [get_user_ui_langid]}] - set primary_langid [extract_primary_langid $langid] - set sub_langid [extract_sublanguage_langid $langid] - set cp [expr {[info exists opts(codepage)] ? $opts(codepage) : 0}] - - # Find a match in the following order: - # 0 Exact match for both langid and codepage - # 1 Exact match for langid - # 2 Primary langid matches (sublang does not) and exact codepage - # 3 Primary langid matches (sublang does not) - # 4 Language neutral - # 5 English - # 6 First langcp in list or "00000000" - set match(7) "00000000"; # In case list is empty - foreach langcp [Twapi_VerQueryValue_TRANSLATIONS $ver] { - set verlangid 0x[string range $langcp 0 3] - set vercp 0x[string range $langcp 4 7] - if {$verlangid == $langid && $vercp == $cp} { - set match(0) $langcp - break; # No need to look further - } - if {[info exists match(1)]} continue - if {$verlangid == $langid} { - set match(1) $langcp - continue; # Continue to look for match(0) - } - if {[info exists match(2)]} continue - set verprimary [extract_primary_langid $verlangid] - if {$verprimary == $primary_langid && $vercp == $cp} { - set match(2) $langcp - continue; # Continue to look for match(1) or better - } - if {[info exists match(3)]} continue - if {$verprimary == $primary_langid} { - set match(3) $langcp - continue; # Continue to look for match(2) or better - } - if {[info exists match(4)]} continue - if {$verprimary == 0} { - set match(4) $langcp; # LANG_NEUTRAL - continue; # Continue to look for match(3) or better - } - if {[info exists match(5)]} continue - if {$verprimary == 9} { - set match(5) $langcp; # English - continue; # Continue to look for match(4) or better - } - if {![info exists match(6)]} { - set match(6) $langcp - } - } - - # Figure out what is the best match we have - for {set i 0} {$i <= 7} {incr i} { - if {[info exists match($i)]} { - break - } - } - - if {$opts(foundlangid) || $opts(all)} { - set langid 0x[string range $match($i) 0 3] - lappend result -foundlangid [list $langid [VerLanguageName $langid]] - } - - if {$opts(foundcodepage) || $opts(all)} { - lappend result -foundcodepage 0x[string range $match($i) 4 7] - } - - foreach sname $args { - lappend result $sname [Twapi_VerQueryValue_STRING $ver $match($i) $sname] - } - - } - - } finally { - Twapi_FreeFileVersionInfo $ver - } - - return $result -} - -proc twapi::begin_resource_update {path args} { - array set opts [parseargs args { - deleteall - } -maxleftover 0] - - return [BeginUpdateResource $path $opts(deleteall)] -} - -# Note this is not an alias because we want to control arguments -# to UpdateResource (which can take more args that specified here) -proc twapi::delete_resource {hmod restype resname langid} { - UpdateResource $hmod $restype $resname $langid -} - - -# Note this is not an alias because we want to make sure $bindata is specified -# as an argument else it will have the effect of deleting a resource -proc twapi::update_resource {hmod restype resname langid bindata} { - UpdateResource $hmod $restype $resname $langid $bindata -} - -proc twapi::end_resource_update {hmod args} { - array set opts [parseargs args { - discard - } -maxleftover 0] - - return [EndUpdateResource $hmod $opts(discard)] -} - -proc twapi::read_resource {hmod restype resname langid} { - return [Twapi_LoadResource $hmod [FindResourceEx $hmod $restype $resname $langid]] -} - -proc twapi::read_resource_string {hmod resname langid} { - # As an aside, note that we do not use a LoadString call - # because it does not allow for specification of a langid - - # For a reference to how strings are stored, see - # http://blogs.msdn.com/b/oldnewthing/archive/2004/01/30/65013.aspx - # or http://support.microsoft.com/kb/196774 - - if {![string is integer -strict $resname]} { - error "String resources must have an integer id" - } - - lassign [resource_stringid_to_stringblockid $resname] block_id index_within_block - - return [lindex \ - [resource_stringblock_to_strings \ - [read_resource $hmod 6 $block_id $langid] ] \ - $index_within_block] -} - -# Give a list of strings, formats it as a string block. Number of strings -# must not be greater than 16. If less than 16 strings, remaining are -# treated as empty. -proc twapi::strings_to_resource_stringblock {strings} { - if {[llength $strings] > 16} { - error "Cannot have more than 16 strings in a resource string block." - } - - for {set i 0} {$i < 16} {incr i} { - set s [lindex $strings $i] - set n [string length $s] - append bin [binary format sa* $n [encoding convertto unicode $s]] - } - - return $bin -} - -proc twapi::resource_stringid_to_stringblockid {id} { - # Strings are stored in blocks of 16, with block id's beginning at 1, not 0 - return [list [expr {($id / 16) + 1}] [expr {$id & 15}]] -} - -proc twapi::extract_resources {hmod {withdata 0}} { - set result [dict create] - foreach type [enumerate_resource_types $hmod] { - set typedict [dict create] - foreach name [enumerate_resource_names $hmod $type] { - set namedict [dict create] - foreach lang [enumerate_resource_languages $hmod $type $name] { - if {$withdata} { - dict set namedict $lang [read_resource $hmod $type $name $lang] - } else { - dict set namedict $lang {} - } - } - dict set typedict $name $namedict - } - dict set result $type $typedict - } - return $result -} - -# TBD - test -proc twapi::write_bmp_file {filename bmp} { - # Assumes $bmp is clipboard content in format 8 (CF_DIB) - - # First parse the bitmap data to collect header information - binary scan $bmp "iiissiiiiii" size width height planes bitcount compression sizeimage xpelspermeter ypelspermeter clrused clrimportant - - # We only handle BITMAPINFOHEADER right now (size must be 40) - if {$size != 40} { - error "Unsupported bitmap format. Header size=$size" - } - - # We need to figure out the offset to the actual bitmap data - # from the start of the file header. For this we need to know the - # size of the color table which directly follows the BITMAPINFOHEADER - if {$bitcount == 0} { - error "Unsupported format: implicit JPEG or PNG" - } elseif {$bitcount == 1} { - set color_table_size 2 - } elseif {$bitcount == 4} { - # TBD - Not sure if this is the size or the max size - set color_table_size 16 - } elseif {$bitcount == 8} { - # TBD - Not sure if this is the size or the max size - set color_table_size 256 - } elseif {$bitcount == 16 || $bitcount == 32} { - if {$compression == 0} { - # BI_RGB - set color_table_size $clrused - } elseif {$compression == 3} { - # BI_BITFIELDS - set color_table_size 3 - } else { - error "Unsupported compression type '$compression' for bitcount value $bitcount" - } - } elseif {$bitcount == 24} { - set color_table_size $clrused - } else { - error "Unsupported value '$bitcount' in bitmap bitcount field" - } - - set filehdr_size 14; # sizeof(BITMAPFILEHEADER) - set bitmap_file_offset [expr {$filehdr_size+$size+($color_table_size*4)}] - set filehdr [binary format "a2 i x2 x2 i" "BM" [expr {$filehdr_size + [string length $bmp]}] $bitmap_file_offset] - - set fd [open $filename w] - fconfigure $fd -translation binary - - puts -nonewline $fd $filehdr - puts -nonewline $fd $bmp - - close $fd -} - -proc twapi::_load_image {flags type hmod path args} { - # The flags arg is generally 0x10 (load from file), or 0 (module) - # or'ed with 0x8000 (shared). The latter can be overridden by - # the -shared option but should not be except when loading from module. - array set opts [parseargs args { - {createdibsection.bool 0 0x2000} - {defaultsize.bool 0 0x40} - height.int - {loadtransparent.bool 0 0x20} - {monochrome.bool 0 0x1} - {shared.bool 0 0x8000} - {vgacolor.bool 0 0x80} - width.int - } -maxleftover 0 -nulldefault] - - set flags [expr {$flags | $opts(defaultsize) | $opts(loadtransparent) | $opts(monochrome) | $opts(shared) | $opts(vgacolor)}] - - set h [LoadImage $hmod $path $type $opts(width) $opts(height) $flags] - # Cast as _SHARED if required to offer some protection against - # being freed using DestroyIcon etc. - set type [lindex {HGDIOBJ HICON HCURSOR} $type] - if {$flags & 0x8000} { - append type _SHARED - } - return [cast_handle $h $type] -} - - -proc twapi::_load_image_from_system {type id args} { - variable _oem_image_syms - - if {![string is integer -strict $id]} { - if {![info exists _oem_image_syms]} { - # Bitmap symbols (type 0) - dict set _oem_image_syms 0 { - CLOSE 32754 UPARROW 32753 - DNARROW 32752 RGARROW 32751 - LFARROW 32750 REDUCE 32749 - ZOOM 32748 RESTORE 32747 - REDUCED 32746 ZOOMD 32745 - RESTORED 32744 UPARROWD 32743 - DNARROWD 32742 RGARROWD 32741 - LFARROWD 32740 MNARROW 32739 - COMBO 32738 UPARROWI 32737 - DNARROWI 32736 RGARROWI 32735 - LFARROWI 32734 SIZE 32766 - BTSIZE 32761 CHECK 32760 - CHECKBOXES 32759 BTNCORNERS 32758 - } - # Icon symbols (type 1) - dict set _oem_image_syms 1 { - SAMPLE 32512 HAND 32513 - QUES 32514 BANG 32515 - NOTE 32516 WINLOGO 32517 - WARNING 32515 ERROR 32513 - INFORMATION 32516 SHIELD 32518 - } - # Cursor symbols (type 2) - dict set _oem_image_syms 2 { - NORMAL 32512 IBEAM 32513 - WAIT 32514 CROSS 32515 - UP 32516 SIZENWSE 32642 - SIZENESW 32643 SIZEWE 32644 - SIZENS 32645 SIZEALL 32646 - NO 32648 HAND 32649 - APPSTARTING 32650 - } - - } - } - - set id [dict get $_oem_image_syms $type [string toupper $id]] - # Built-in system images must always be loaded shared (0x8000) - return [_load_image 0x8000 $type NULL $id {*}$args] -} - - -# 0x10 -> LR_LOADFROMFILE. Also 0x8000 not set (meaning unshared) -interp alias {} twapi::load_bitmap_from_file {} twapi::_load_image 0x10 0 NULL -interp alias {} twapi::load_icon_from_file {} twapi::_load_image 0x10 1 NULL -interp alias {} twapi::load_cursor_from_file {} twapi::_load_image 0x10 2 NULL - -interp alias {} twapi::load_bitmap_from_module {} twapi::_load_image 0 0 -interp alias {} twapi::load_icon_from_module {} twapi::_load_image 0 1 -interp alias {} twapi::load_cursor_from_module {} twapi::_load_image 0 2 - -interp alias {} twapi::load_bitmap_from_system {} twapi::_load_image_from_system 0 -interp alias {} twapi::load_icon_from_system {} twapi::_load_image_from_system 1 -interp alias {} twapi::load_cursor_from_system {} twapi::_load_image_from_system 2 - -interp alias {} twapi::free_icon {} twapi::DestroyIcon -interp alias {} twapi::free_bitmap {} twapi::DeleteObject -interp alias {} twapi::free_cursor {} twapi::DestroyCursor +# Commands related to resource manipulation +# +# Copyright (c) 2003-2012 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +package require twapi_nls + +# Retrieve version info for a file +proc twapi::get_file_version_resource {path args} { + array set opts [parseargs args { + all + datetime + signature + structversion + fileversion + productversion + flags + fileos + filetype + foundlangid + foundcodepage + langid.arg + codepage.arg + }] + + + set ver [Twapi_GetFileVersionInfo $path] + + trap { + array set verinfo [Twapi_VerQueryValue_FIXEDFILEINFO $ver] + + set result [list ] + if {$opts(all) || $opts(signature)} { + lappend result -signature [format 0x%x $verinfo(dwSignature)] + } + + if {$opts(all) || $opts(structversion)} { + lappend result -structversion "[expr {0xffff & ($verinfo(dwStrucVersion) >> 16)}].[expr {0xffff & $verinfo(dwStrucVersion)}]" + } + + if {$opts(all) || $opts(fileversion)} { + lappend result -fileversion "[expr {0xffff & ($verinfo(dwFileVersionMS) >> 16)}].[expr {0xffff & $verinfo(dwFileVersionMS)}].[expr {0xffff & ($verinfo(dwFileVersionLS) >> 16)}].[expr {0xffff & $verinfo(dwFileVersionLS)}]" + } + + if {$opts(all) || $opts(productversion)} { + lappend result -productversion "[expr {0xffff & ($verinfo(dwProductVersionMS) >> 16)}].[expr {0xffff & $verinfo(dwProductVersionMS)}].[expr {0xffff & ($verinfo(dwProductVersionLS) >> 16)}].[expr {0xffff & $verinfo(dwProductVersionLS)}]" + } + + if {$opts(all) || $opts(flags)} { + set flags [expr {$verinfo(dwFileFlags) & $verinfo(dwFileFlagsMask)}] + lappend result -flags \ + [_make_symbolic_bitmask \ + [expr {$verinfo(dwFileFlags) & $verinfo(dwFileFlagsMask)}] \ + { + debug 1 + prerelease 2 + patched 4 + privatebuild 8 + infoinferred 16 + specialbuild 32 + } \ + ] + } + + if {$opts(all) || $opts(fileos)} { + switch -exact -- [format %08x $verinfo(dwFileOS)] { + 00010000 {set os dos} + 00020000 {set os os216} + 00030000 {set os os232} + 00040000 {set os nt} + 00050000 {set os wince} + 00000001 {set os windows16} + 00000002 {set os pm16} + 00000003 {set os pm32} + 00000004 {set os windows32} + 00010001 {set os dos_windows16} + 00010004 {set os dos_windows32} + 00020002 {set os os216_pm16} + 00030003 {set os os232_pm32} + 00040004 {set os nt_windows32} + default {set os $verinfo(dwFileOS)} + } + lappend result -fileos $os + } + + if {$opts(all) || $opts(filetype)} { + switch -exact -- [expr {0+$verinfo(dwFileType)}] { + 1 {set type application} + 2 {set type dll} + 3 { + set type "driver." + switch -exact -- [expr {0+$verinfo(dwFileSubtype)}] { + 1 {append type printer} + 2 {append type keyboard} + 3 {append type language} + 4 {append type display} + 5 {append type mouse} + 6 {append type network} + 7 {append type system} + 8 {append type installable} + 9 {append type sound} + 10 {append type comm} + 11 {append type inputmethod} + 12 {append type versionedprinter} + default {append type $verinfo(dwFileSubtype)} + } + } + 4 { + set type "font." + switch -exact -- [expr {0+$verinfo(dwFileSubtype)}] { + 1 {append type raster} + 2 {append type vector} + 3 {append type truetype} + default {append type $verinfo(dwFileSubtype)} + } + } + 5 { set type "vxd.$verinfo(dwFileSubtype)" } + 7 {set type staticlib} + default { + set type "$verinfo(dwFileType).$verinfo(dwFileSubtype)" + } + } + lappend result -filetype $type + } + + if {$opts(all) || $opts(datetime)} { + lappend result -datetime [expr {($verinfo(dwFileDateMS) << 32) + $verinfo(dwFileDateLS)}] + } + + # Any remaining arguments are treated as string names + + if {[llength $args] || $opts(foundlangid) || $opts(foundcodepage) || $opts(all)} { + # Find list of langid's and codepages and do closest match + set langid [expr {[info exists opts(langid)] ? $opts(langid) : [get_user_ui_langid]}] + set primary_langid [extract_primary_langid $langid] + set sub_langid [extract_sublanguage_langid $langid] + set cp [expr {[info exists opts(codepage)] ? $opts(codepage) : 0}] + + # Find a match in the following order: + # 0 Exact match for both langid and codepage + # 1 Exact match for langid + # 2 Primary langid matches (sublang does not) and exact codepage + # 3 Primary langid matches (sublang does not) + # 4 Language neutral + # 5 English + # 6 First langcp in list or "00000000" + set match(7) "00000000"; # In case list is empty + foreach langcp [Twapi_VerQueryValue_TRANSLATIONS $ver] { + set verlangid 0x[string range $langcp 0 3] + set vercp 0x[string range $langcp 4 7] + if {$verlangid == $langid && $vercp == $cp} { + set match(0) $langcp + break; # No need to look further + } + if {[info exists match(1)]} continue + if {$verlangid == $langid} { + set match(1) $langcp + continue; # Continue to look for match(0) + } + if {[info exists match(2)]} continue + set verprimary [extract_primary_langid $verlangid] + if {$verprimary == $primary_langid && $vercp == $cp} { + set match(2) $langcp + continue; # Continue to look for match(1) or better + } + if {[info exists match(3)]} continue + if {$verprimary == $primary_langid} { + set match(3) $langcp + continue; # Continue to look for match(2) or better + } + if {[info exists match(4)]} continue + if {$verprimary == 0} { + set match(4) $langcp; # LANG_NEUTRAL + continue; # Continue to look for match(3) or better + } + if {[info exists match(5)]} continue + if {$verprimary == 9} { + set match(5) $langcp; # English + continue; # Continue to look for match(4) or better + } + if {![info exists match(6)]} { + set match(6) $langcp + } + } + + # Figure out what is the best match we have + for {set i 0} {$i <= 7} {incr i} { + if {[info exists match($i)]} { + break + } + } + + if {$opts(foundlangid) || $opts(all)} { + set langid 0x[string range $match($i) 0 3] + lappend result -foundlangid [list $langid [VerLanguageName $langid]] + } + + if {$opts(foundcodepage) || $opts(all)} { + lappend result -foundcodepage 0x[string range $match($i) 4 7] + } + + foreach sname $args { + lappend result $sname [Twapi_VerQueryValue_STRING $ver $match($i) $sname] + } + + } + + } finally { + Twapi_FreeFileVersionInfo $ver + } + + return $result +} + +proc twapi::begin_resource_update {path args} { + array set opts [parseargs args { + deleteall + } -maxleftover 0] + + return [BeginUpdateResource $path $opts(deleteall)] +} + +# Note this is not an alias because we want to control arguments +# to UpdateResource (which can take more args that specified here) +proc twapi::delete_resource {hmod restype resname langid} { + UpdateResource $hmod $restype $resname $langid +} + + +# Note this is not an alias because we want to make sure $bindata is specified +# as an argument else it will have the effect of deleting a resource +proc twapi::update_resource {hmod restype resname langid bindata} { + UpdateResource $hmod $restype $resname $langid $bindata +} + +proc twapi::end_resource_update {hmod args} { + array set opts [parseargs args { + discard + } -maxleftover 0] + + return [EndUpdateResource $hmod $opts(discard)] +} + +proc twapi::read_resource {hmod restype resname langid} { + return [Twapi_LoadResource $hmod [FindResourceEx $hmod $restype $resname $langid]] +} + +proc twapi::read_resource_string {hmod resname langid} { + # As an aside, note that we do not use a LoadString call + # because it does not allow for specification of a langid + + # For a reference to how strings are stored, see + # http://blogs.msdn.com/b/oldnewthing/archive/2004/01/30/65013.aspx + # or http://support.microsoft.com/kb/196774 + + if {![string is integer -strict $resname]} { + error "String resources must have an integer id" + } + + lassign [resource_stringid_to_stringblockid $resname] block_id index_within_block + + return [lindex \ + [resource_stringblock_to_strings \ + [read_resource $hmod 6 $block_id $langid] ] \ + $index_within_block] +} + +# Give a list of strings, formats it as a string block. Number of strings +# must not be greater than 16. If less than 16 strings, remaining are +# treated as empty. +proc twapi::strings_to_resource_stringblock {strings} { + if {[llength $strings] > 16} { + error "Cannot have more than 16 strings in a resource string block." + } + + for {set i 0} {$i < 16} {incr i} { + set s [lindex $strings $i] + set n [string length $s] + append bin [binary format sa* $n [encoding convertto unicode $s]] + } + + return $bin +} + +proc twapi::resource_stringid_to_stringblockid {id} { + # Strings are stored in blocks of 16, with block id's beginning at 1, not 0 + return [list [expr {($id / 16) + 1}] [expr {$id & 15}]] +} + +proc twapi::extract_resources {hmod {withdata 0}} { + set result [dict create] + foreach type [enumerate_resource_types $hmod] { + set typedict [dict create] + foreach name [enumerate_resource_names $hmod $type] { + set namedict [dict create] + foreach lang [enumerate_resource_languages $hmod $type $name] { + if {$withdata} { + dict set namedict $lang [read_resource $hmod $type $name $lang] + } else { + dict set namedict $lang {} + } + } + dict set typedict $name $namedict + } + dict set result $type $typedict + } + return $result +} + +# TBD - test +proc twapi::write_bmp_file {filename bmp} { + # Assumes $bmp is clipboard content in format 8 (CF_DIB) + + # First parse the bitmap data to collect header information + binary scan $bmp "iiissiiiiii" size width height planes bitcount compression sizeimage xpelspermeter ypelspermeter clrused clrimportant + + # We only handle BITMAPINFOHEADER right now (size must be 40) + if {$size != 40} { + error "Unsupported bitmap format. Header size=$size" + } + + # We need to figure out the offset to the actual bitmap data + # from the start of the file header. For this we need to know the + # size of the color table which directly follows the BITMAPINFOHEADER + if {$bitcount == 0} { + error "Unsupported format: implicit JPEG or PNG" + } elseif {$bitcount == 1} { + set color_table_size 2 + } elseif {$bitcount == 4} { + # TBD - Not sure if this is the size or the max size + set color_table_size 16 + } elseif {$bitcount == 8} { + # TBD - Not sure if this is the size or the max size + set color_table_size 256 + } elseif {$bitcount == 16 || $bitcount == 32} { + if {$compression == 0} { + # BI_RGB + set color_table_size $clrused + } elseif {$compression == 3} { + # BI_BITFIELDS + set color_table_size 3 + } else { + error "Unsupported compression type '$compression' for bitcount value $bitcount" + } + } elseif {$bitcount == 24} { + set color_table_size $clrused + } else { + error "Unsupported value '$bitcount' in bitmap bitcount field" + } + + set filehdr_size 14; # sizeof(BITMAPFILEHEADER) + set bitmap_file_offset [expr {$filehdr_size+$size+($color_table_size*4)}] + set filehdr [binary format "a2 i x2 x2 i" "BM" [expr {$filehdr_size + [string length $bmp]}] $bitmap_file_offset] + + set fd [open $filename w] + fconfigure $fd -translation binary + + puts -nonewline $fd $filehdr + puts -nonewline $fd $bmp + + close $fd +} + +proc twapi::_load_image {flags type hmod path args} { + # The flags arg is generally 0x10 (load from file), or 0 (module) + # or'ed with 0x8000 (shared). The latter can be overridden by + # the -shared option but should not be except when loading from module. + array set opts [parseargs args { + {createdibsection.bool 0 0x2000} + {defaultsize.bool 0 0x40} + height.int + {loadtransparent.bool 0 0x20} + {monochrome.bool 0 0x1} + {shared.bool 0 0x8000} + {vgacolor.bool 0 0x80} + width.int + } -maxleftover 0 -nulldefault] + + set flags [expr {$flags | $opts(defaultsize) | $opts(loadtransparent) | $opts(monochrome) | $opts(shared) | $opts(vgacolor)}] + + set h [LoadImage $hmod $path $type $opts(width) $opts(height) $flags] + # Cast as _SHARED if required to offer some protection against + # being freed using DestroyIcon etc. + set type [lindex {HGDIOBJ HICON HCURSOR} $type] + if {$flags & 0x8000} { + append type _SHARED + } + return [cast_handle $h $type] +} + + +proc twapi::_load_image_from_system {type id args} { + variable _oem_image_syms + + if {![string is integer -strict $id]} { + if {![info exists _oem_image_syms]} { + # Bitmap symbols (type 0) + dict set _oem_image_syms 0 { + CLOSE 32754 UPARROW 32753 + DNARROW 32752 RGARROW 32751 + LFARROW 32750 REDUCE 32749 + ZOOM 32748 RESTORE 32747 + REDUCED 32746 ZOOMD 32745 + RESTORED 32744 UPARROWD 32743 + DNARROWD 32742 RGARROWD 32741 + LFARROWD 32740 MNARROW 32739 + COMBO 32738 UPARROWI 32737 + DNARROWI 32736 RGARROWI 32735 + LFARROWI 32734 SIZE 32766 + BTSIZE 32761 CHECK 32760 + CHECKBOXES 32759 BTNCORNERS 32758 + } + # Icon symbols (type 1) + dict set _oem_image_syms 1 { + SAMPLE 32512 HAND 32513 + QUES 32514 BANG 32515 + NOTE 32516 WINLOGO 32517 + WARNING 32515 ERROR 32513 + INFORMATION 32516 SHIELD 32518 + } + # Cursor symbols (type 2) + dict set _oem_image_syms 2 { + NORMAL 32512 IBEAM 32513 + WAIT 32514 CROSS 32515 + UP 32516 SIZENWSE 32642 + SIZENESW 32643 SIZEWE 32644 + SIZENS 32645 SIZEALL 32646 + NO 32648 HAND 32649 + APPSTARTING 32650 + } + + } + } + + set id [dict get $_oem_image_syms $type [string toupper $id]] + # Built-in system images must always be loaded shared (0x8000) + return [_load_image 0x8000 $type NULL $id {*}$args] +} + + +# 0x10 -> LR_LOADFROMFILE. Also 0x8000 not set (meaning unshared) +interp alias {} twapi::load_bitmap_from_file {} twapi::_load_image 0x10 0 NULL +interp alias {} twapi::load_icon_from_file {} twapi::_load_image 0x10 1 NULL +interp alias {} twapi::load_cursor_from_file {} twapi::_load_image 0x10 2 NULL + +interp alias {} twapi::load_bitmap_from_module {} twapi::_load_image 0 0 +interp alias {} twapi::load_icon_from_module {} twapi::_load_image 0 1 +interp alias {} twapi::load_cursor_from_module {} twapi::_load_image 0 2 + +interp alias {} twapi::load_bitmap_from_system {} twapi::_load_image_from_system 0 +interp alias {} twapi::load_icon_from_system {} twapi::_load_image_from_system 1 +interp alias {} twapi::load_cursor_from_system {} twapi::_load_image_from_system 2 + +interp alias {} twapi::free_icon {} twapi::DestroyIcon +interp alias {} twapi::free_bitmap {} twapi::DeleteObject +interp alias {} twapi::free_cursor {} twapi::DestroyCursor diff --git a/src/vendorlib_tcl8/twapi4.7.2/security.tcl b/src/vendorlib_tcl8/twapi-5.0b1/security.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/security.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/security.tcl index a0a799b5..e1689a12 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/security.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/security.tcl @@ -1,2385 +1,2392 @@ -# -# Copyright (c) 2003-2014, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# TBD - allow SID and account name to be used interchangeably in various -# functions -# TBD - ditto for LUID v/s privilege names - -namespace eval twapi { - # Map privilege level mnemonics to priv level - array set priv_level_map {guest 0 user 1 admin 2} - - # TBD - the following are not used, enhancements needed ? - # OBJECT_INHERIT_ACE 0x1 - # CONTAINER_INHERIT_ACE 0x2 - # NO_PROPAGATE_INHERIT_ACE 0x4 - # INHERIT_ONLY_ACE 0x8 - # INHERITED_ACE 0x10 - # VALID_INHERIT_FLAGS 0x1F - - # Cache of privilege names to LUID's - variable _privilege_to_luid_map - set _privilege_to_luid_map {} - variable _luid_to_privilege_map {} - -} - - -# Returns token for a process -proc twapi::open_process_token {args} { - array set opts [parseargs args { - pid.int - hprocess.arg - {access.arg token_query} - } -maxleftover 0] - - set access [_access_rights_to_mask $opts(access)] - - # Get a handle for the process - if {[info exists opts(hprocess)]} { - if {[info exists opts(pid)]} { - error "Options -pid and -hprocess cannot be used together." - } - set ph $opts(hprocess) - } elseif {[info exists opts(pid)]} { - set ph [get_process_handle $opts(pid)] - } else { - variable my_process_handle - set ph $my_process_handle - } - trap { - # Get a token for the process - set ptok [OpenProcessToken $ph $access] - } finally { - # Close handle only if we did an OpenProcess - if {[info exists opts(pid)]} { - CloseHandle $ph - } - } - - return $ptok -} - -# Returns token for a process -proc twapi::open_thread_token {args} { - array set opts [parseargs args { - tid.int - hthread.arg - {access.arg token_query} - {self.bool false} - } -maxleftover 0] - - set access [_access_rights_to_mask $opts(access)] - - # Get a handle for the thread - if {[info exists opts(hthread)]} { - if {[info exists opts(tid)]} { - error "Options -tid and -hthread cannot be used together." - } - set th $opts(hthread) - } elseif {[info exists opts(tid)]} { - set th [get_thread_handle $opts(tid)] - } else { - set th [GetCurrentThread] - } - - trap { - # Get a token for the thread - set tok [OpenThreadToken $th $access $opts(self)] - } finally { - # Close handle only if we did an OpenProcess - if {[info exists opts(tid)]} { - CloseHandle $th - } - } - - return $tok -} - -proc twapi::close_token {tok} { - CloseHandle $tok -} - -# TBD - document and test -proc twapi::duplicate_token {tok args} { - parseargs args { - access.arg - {inherit.bool 0} - {secd.arg ""} - {impersonationlevel.sym impersonation {anonymous 0 identification 1 impersonation 2 delegation 3}} - {type.sym primary {primary 1 impersonation 2}} - } -maxleftover 0 -setvars - - if {[info exists access]} { - set access [_access_rights_to_mask $access] - } else { - # If no desired access is indicated, we want the same access as - # the original handle - set access 0 - } - - return [DuplicateTokenEx $tok $access \ - [_make_secattr $secd $inherit] \ - $impersonationlevel $type] -} - -proc twapi::get_token_info {tok args} { - array set opts [parseargs args { - defaultdacl - disabledprivileges - elevation - enabledprivileges - groupattrs - groups - groupsids - integrity - integritylabel - linkedtoken - logonsession - logonsessionsid - origin - primarygroup - primarygroupsid - privileges - restrictedgroupattrs - restrictedgroups - tssession - usersid - virtualized - } -maxleftover 0] - - # Do explicit check so we return error if no args specified - # and $tok is invalid - if {![pointer? $tok]} { - error "Invalid token handle '$tok'" - } - - # TBD - add an -ignorerrors option - - set result [dict create] - trap { - if {$opts(privileges) || $opts(disabledprivileges) || $opts(enabledprivileges)} { - lassign [GetTokenInformation $tok 13] gtigroups gtirestrictedgroups privs gtilogonsession - set privs [_map_luids_and_attrs_to_privileges $privs] - if {$opts(privileges)} { - lappend result -privileges $privs - } - if {$opts(enabledprivileges)} { - lappend result -enabledprivileges [lindex $privs 0] - } - if {$opts(disabledprivileges)} { - lappend result -disabledprivileges [lindex $privs 1] - } - } - if {$opts(defaultdacl)} { - lappend result -defaultdacl [get_token_default_dacl $tok] - } - if {$opts(origin)} { - lappend result -origin [get_token_origin $tok] - } - if {$opts(linkedtoken)} { - lappend result -linkedtoken [get_token_linked_token $tok] - } - if {$opts(elevation)} { - lappend result -elevation [get_token_elevation $tok] - } - if {$opts(integrity)} { - lappend result -integrity [get_token_integrity $tok] - } - if {$opts(integritylabel)} { - lappend result -integritylabel [get_token_integrity $tok -label] - } - if {$opts(virtualized)} { - lappend result -virtualized [get_token_virtualization $tok] - } - if {$opts(tssession)} { - lappend result -tssession [get_token_tssession $tok] - } - if {$opts(usersid)} { - # First element of groups is user sid - if {[info exists gtigroups]} { - lappend result -usersid [lindex $gtigroups 0 0 0] - } else { - lappend result -usersid [get_token_user $tok] - } - } - if {$opts(groups) || $opts(groupsids)} { - if {[info exists gtigroups]} { - set gsids {} - # First element of groups is user sid, skip it - foreach item [lrange $gtigroups 1 end] { - lappend gsids [lindex $item 0] - } - } else { - set gsids [get_token_groups $tok] - } - if {$opts(groupsids)} { - lappend result -groupsids $gsids - } - if {$opts(groups)} { - set items {} - foreach gsid $gsids { - lappend items [lookup_account_sid $gsid] - } - lappend result -groups $items - } - } - if {[min_os_version 6] && $opts(logonsessionsid)} { - # Only possible on Vista+ - lappend result -logonsessionsid [lindex [GetTokenInformation $tok 28] 0 0] - set opts(logonsessionsid) 0; # So we don't try second method below - } - if {$opts(groupattrs) || $opts(logonsessionsid)} { - if {[info exists gtigroups]} { - set items {} - # First element of groups is user sid, skip it - foreach item [lrange $gtigroups 1 end] { - set gattrs [map_token_group_attr [lindex $item 1]] - if {$opts(groupattrs)} { - lappend items [lindex $item 0] $gattrs - } - if {$opts(logonsessionsid) && "logon_id" in $gattrs} { - set logonsessionsid [lindex $item 0] - } - } - if {$opts(groupattrs)} { - lappend result -groupattrs $items - } - } else { - set groupattrs [get_token_groups_and_attrs $tok] - if {$opts(logonsessionsid)} { - foreach {sid gattrs} $groupattrs { - if {"logon_id" in $gattrs} { - set logonsessionsid $sid - break - } - } - } - if {$opts(groupattrs)} { - lappend result -groupattrs $groupattrs - } - } - if {$opts(logonsessionsid)} { - if {[info exists logonsessionsid]} { - lappend result -logonsessionsid $logonsessionsid - } else { - error "No logon session id found in token" - } - } - } - if {$opts(restrictedgroups)} { - if {![info exists gtirestrictedgroups]} { - set gtirestrictedgroups [get_token_restricted_groups_and_attrs $tok] - } - set items {} - foreach item $gtirestrictedgroups { - lappend items [lookup_account_sid [lindex $item 0]] - } - lappend result -restrictedgroups $items - } - if {$opts(restrictedgroupattrs)} { - if {[info exists gtirestrictedgroups]} { - set items {} - foreach item $gtirestrictedgroups { - lappend items [lindex $item 0] [map_token_group_attr [lindex $item 1]] - } - lappend result -restrictedgroupattrs $items - } else { - lappend result -restrictedgroupattrs [get_token_restricted_groups_and_attrs $tok] - } - } - if {$opts(primarygroupsid)} { - lappend result -primarygroupsid [get_token_primary_group $tok] - } - if {$opts(primarygroup)} { - lappend result -primarygroup [get_token_primary_group $tok -name] - } - if {$opts(logonsession)} { - if {[info exists gtilogonsession]} { - lappend result -logonsession $gtilogonsession - } else { - array set stats [get_token_statistics $tok] - lappend result -logonsession $stats(authluid) - } - } - } - - return $result -} - -proc twapi::get_token_tssession {tok} { - return [GetTokenInformation $tok 12] -} - -# TBD - document and test -proc twapi::set_token_tssession {tok tssession} { - Twapi_SetTokenSessionId $tok $tssession - return -} - -# Procs that differ between Vista and prior versions -if {[twapi::min_os_version 6]} { - proc twapi::get_token_elevation {tok} { - set elevation [GetTokenInformation $tok 18]; #TokenElevationType - switch -exact -- $elevation { - 1 { set elevation default } - 2 { set elevation full } - 3 { set elevation limited } - } - return $elevation - } - - proc twapi::get_token_virtualization {tok} { - return [GetTokenInformation $tok 24]; # TokenVirtualizationEnabled - } - - proc twapi::set_token_virtualization {tok enabled} { - # tok must have TOKEN_ADJUST_DEFAULT access - Twapi_SetTokenVirtualizationEnabled $tok [expr {$enabled ? 1 : 0}] - } - - # Get the integrity level associated with a token - proc twapi::get_token_integrity {tok args} { - # TokenIntegrityLevel -> 25 - lassign [GetTokenInformation $tok 25] integrity attrs - if {$attrs != 96} { - # TBD - is this ok? - } - return [_sid_to_integrity $integrity {*}$args] - } - - # Get the integrity level associated with a token - proc twapi::set_token_integrity {tok integrity} { - # SE_GROUP_INTEGRITY attribute - 0x20 - Twapi_SetTokenIntegrityLevel $tok [list [_integrity_to_sid $integrity] 0x20] - } - - proc twapi::get_token_integrity_policy {tok} { - set policy [GetTokenInformation $tok 27]; #TokenMandatoryPolicy - set result {} - if {$policy & 1} { - lappend result no_write_up - } - if {$policy & 2} { - lappend result new_process_min - } - return $result - } - - - proc twapi::set_token_integrity_policy {tok args} { - set policy [_parse_symbolic_bitmask $args { - no_write_up 0x1 - new_process_min 0x2 - }] - - Twapi_SetTokenMandatoryPolicy $tok $policy - } -} else { - # Versions for pre-Vista - proc twapi::get_token_elevation {tok} { - # Older OS versions have no concept of elevation. - return "default" - } - - proc twapi::get_token_virtualization {tok} { - # Older OS versions have no concept of elevation. - return 0 - } - - proc twapi::set_token_virtualization {tok enabled} { - # Older OS versions have no concept of elevation, so only disable - # allowed - if {$enabled} { - error "Virtualization not available on this platform." - } - return - } - - # Get the integrity level associated with a token - proc twapi::get_token_integrity {tok args} { - # Older OS versions have no concept of elevation. - # For future consistency in label mapping, fall through to mapping - # below instead of directly returning mapped value - set integrity S-1-16-8192 - - return [_sid_to_integrity $integrity {*}$args] - } - - # Get the integrity level associated with a token - proc twapi::set_token_integrity {tok integrity} { - # Old platforms have a "default" of medium that cannot be changed. - if {[_integrity_to_sid $integrity] ne "S-1-16-8192"} { - error "Invalid integrity level value '$integrity' for this platform." - } - return - } - - proc twapi::get_token_integrity_policy {tok} { - # Old platforms - no integrity - return 0 - } - - proc twapi::set_token_integrity_policy {tok args} { - # Old platforms - no integrity - return 0 - } -} - -proc twapi::well_known_sid {sidname args} { - parseargs args { - {domainsid.arg {}} - } -maxleftover 0 -setvars - - return [CreateWellKnownSid [_map_well_known_sid_name $sidname] $domainsid] -} - -proc twapi::is_well_known_sid {sid sidname} { - return [IsWellKnownSid $sid [_map_well_known_sid_name $sidname]] -} - -# Get the user account associated with a token -proc twapi::get_token_user {tok args} { - - array set opts [parseargs args [list name]] - # TokenUser -> 1 - set user [lindex [GetTokenInformation $tok 1] 0] - if {$opts(name)} { - set user [lookup_account_sid $user] - } - return $user -} - -# Get the groups associated with a token -proc twapi::get_token_groups {tok args} { - array set opts [parseargs args [list name] -maxleftover 0] - - set groups [list ] - # TokenGroups -> 2 - foreach group [GetTokenInformation $tok 2] { - if {$opts(name)} { - lappend groups [lookup_account_sid [lindex $group 0]] - } else { - lappend groups [lindex $group 0] - } - } - - return $groups -} - -# Get the groups associated with a token along with their attributes -# These are returned as a flat list of the form "sid attrlist sid attrlist..." -# where the attrlist is a list of attributes -proc twapi::get_token_groups_and_attrs {tok} { - - set sids_and_attrs [list ] - # TokenGroups -> 2 - foreach {group} [GetTokenInformation $tok 2] { - lappend sids_and_attrs [lindex $group 0] [map_token_group_attr [lindex $group 1]] - } - - return $sids_and_attrs -} - -# Get the groups associated with a token along with their attributes -# These are returned as a flat list of the form "sid attrlist sid attrlist..." -# where the attrlist is a list of attributes -proc twapi::get_token_restricted_groups_and_attrs {tok} { - set sids_and_attrs [list ] - # TokenRestrictedGroups -> 11 - foreach {group} [GetTokenInformation $tok 11] { - lappend sids_and_attrs [lindex $group 0] [map_token_group_attr [lindex $group 1]] - } - - return $sids_and_attrs -} - - -# Get list of privileges that are currently enabled for the token -# If -all is specified, returns a list {enabled_list disabled_list} -proc twapi::get_token_privileges {tok args} { - - set all [expr {[lsearch -exact $args -all] >= 0}] - # TokenPrivileges -> 3 - set privs [_map_luids_and_attrs_to_privileges [GetTokenInformation $tok 3]] - if {$all} { - return $privs - } else { - return [lindex $privs 0] - } -} - -# Return true if the token has the given privilege -proc twapi::check_enabled_privileges {tok privlist args} { - set all_required [expr {[lsearch -exact $args "-any"] < 0}] - - set luid_attr_list [list ] - foreach priv $privlist { - lappend luid_attr_list [list [map_privilege_to_luid $priv] 0] - } - return [Twapi_PrivilegeCheck $tok $luid_attr_list $all_required] -} - - -# Enable specified privileges. Returns "" if the given privileges were -# already enabled, else returns the privileges that were modified -proc twapi::enable_privileges {privlist} { - variable my_process_handle - - # Get our process token - set tok [OpenProcessToken $my_process_handle 0x28]; # QUERY + ADJUST_PRIVS - trap { - return [enable_token_privileges $tok $privlist] - } finally { - close_token $tok - } -} - - -# Disable specified privileges. Returns "" if the given privileges were -# already enabled, else returns the privileges that were modified -proc twapi::disable_privileges {privlist} { - variable my_process_handle - - # Get our process token - set tok [OpenProcessToken $my_process_handle 0x28]; # QUERY + ADJUST_PRIVS - trap { - return [disable_token_privileges $tok $privlist] - } finally { - close_token $tok - } -} - - -# Execute the given script with the specified privileges. -# After the script completes, the original privileges are restored -proc twapi::eval_with_privileges {script privs args} { - array set opts [parseargs args {besteffort} -maxleftover 0] - - if {[catch {enable_privileges $privs} privs_to_disable]} { - if {! $opts(besteffort)} { - return -code error -errorinfo $::errorInfo \ - -errorcode $::errorCode $privs_to_disable - } - set privs_to_disable [list ] - } - - set code [catch {uplevel $script} result] - switch $code { - 0 { - disable_privileges $privs_to_disable - return $result - } - 1 { - # Save error info before calling disable_privileges - set erinfo $::errorInfo - set ercode $::errorCode - disable_privileges $privs_to_disable - return -code error -errorinfo $::errorInfo \ - -errorcode $::errorCode $result - } - default { - disable_privileges $privs_to_disable - return -code $code $result - } - } -} - - -# Get the privilege associated with a token and their attributes -proc twapi::get_token_privileges_and_attrs {tok} { - set privs_and_attrs [list ] - # TokenPrivileges -> 3 - foreach priv [GetTokenInformation $tok 3] { - lassign $priv luid attr - lappend privs_and_attrs [map_luid_to_privilege $luid -mapunknown] \ - [map_token_privilege_attr $attr] - } - - return $privs_and_attrs - -} - - -# Get the sid that will be used as the owner for objects created using this -# token. Returns name instead of sid if -name options specified -proc twapi::get_token_owner {tok args} { - # TokenOwner -> 4 - return [ _get_token_sid_field $tok 4 $args] -} - - -# Get the sid that will be used as the primary group for objects created using -# this token. Returns name instead of sid if -name options specified -proc twapi::get_token_primary_group {tok args} { - # TokenPrimaryGroup -> 5 - return [ _get_token_sid_field $tok 5 $args] -} - -proc twapi::get_token_default_dacl {tok} { - # TokenDefaultDacl -> 6 - return [GetTokenInformation $tok 6] -} - -proc twapi::get_token_origin {tok} { - # TokenOrigin -> 17 - return [GetTokenInformation $tok 17] -} - -# Return the source of an access token -proc twapi::get_token_source {tok} { - return [GetTokenInformation $tok 7]; # TokenSource -} - - -# Return the token type of an access token -proc twapi::get_token_type {tok} { - # TokenType -> 8 - set type [GetTokenInformation $tok 8] - if {$type == 1} { - return "primary" - } elseif {$type == 2} { - return "impersonation" - } else { - return $type - } -} - -# Return the token type of an access token -proc twapi::get_token_impersonation_level {tok} { - # TokenImpersonationLevel -> 9 - return [_map_impersonation_level [GetTokenInformation $tok 9]] -} - -# Return the linked token when a token is filtered -proc twapi::get_token_linked_token {tok} { - # TokenLinkedToken -> 19 - return [GetTokenInformation $tok 19] -} - -# Return token statistics -proc twapi::get_token_statistics {tok} { - array set stats {} - set labels {luid authluid expiration type impersonationlevel - dynamiccharged dynamicavailable groupcount - privilegecount modificationluid} - # TokenStatistics -> 10 - set statinfo [GetTokenInformation $tok 10] - foreach label $labels val $statinfo { - set stats($label) $val - } - set stats(type) [expr {$stats(type) == 1 ? "primary" : "impersonation"}] - set stats(impersonationlevel) [_map_impersonation_level $stats(impersonationlevel)] - - return [array get stats] -} - - -# Enable the privilege state of a token. Generates an error if -# the specified privileges do not exist in the token (either -# disabled or enabled), or cannot be adjusted -proc twapi::enable_token_privileges {tok privs} { - set luid_attrs [list] - foreach priv $privs { - # SE_PRIVILEGE_ENABLED -> 2 - lappend luid_attrs [list [map_privilege_to_luid $priv] 2] - } - - set privs [list ] - foreach {item} [Twapi_AdjustTokenPrivileges $tok 0 $luid_attrs] { - lappend privs [map_luid_to_privilege [lindex $item 0] -mapunknown] - } - return $privs - - - -} - -# Disable the privilege state of a token. Generates an error if -# the specified privileges do not exist in the token (either -# disabled or enabled), or cannot be adjusted -proc twapi::disable_token_privileges {tok privs} { - set luid_attrs [list] - foreach priv $privs { - lappend luid_attrs [list [map_privilege_to_luid $priv] 0] - } - - set privs [list ] - foreach {item} [Twapi_AdjustTokenPrivileges $tok 0 $luid_attrs] { - lappend privs [map_luid_to_privilege [lindex $item 0] -mapunknown] - } - return $privs -} - -# Disable all privs in a token -proc twapi::disable_all_token_privileges {tok} { - set privs [list ] - foreach {item} [Twapi_AdjustTokenPrivileges $tok 1 [list ]] { - lappend privs [map_luid_to_privilege [lindex $item 0] -mapunknown] - } - return $privs -} - - -# Map a privilege given as a LUID -proc twapi::map_luid_to_privilege {luid args} { - variable _luid_to_privilege_map - - array set opts [parseargs args [list system.arg mapunknown] -nulldefault] - - if {[dict exists $_luid_to_privilege_map $opts(system) $luid]} { - return [dict get $_luid_to_privilege_map $opts(system) $luid] - } - - # luid may in fact be a privilege name. Check for this - if {[is_valid_luid_syntax $luid]} { - trap { - set name [LookupPrivilegeName $opts(system) $luid] - dict set _luid_to_privilege_map $opts(system) $luid $name - } onerror {TWAPI_WIN32 1313} { - if {! $opts(mapunknown)} { - rethrow - } - set name "Privilege-$luid" - # Do not put in cache as privilege name might change? - } - } else { - # Not a valid LUID syntax. Check if it's a privilege name - if {[catch {map_privilege_to_luid $luid -system $opts(system)}]} { - error "Invalid LUID '$luid'" - } - return $luid; # $luid is itself a priv name - } - - return $name -} - - -# Map a privilege to a LUID -proc twapi::map_privilege_to_luid {priv args} { - variable _privilege_to_luid_map - - array set opts [parseargs args [list system.arg] -nulldefault] - - if {[dict exists $_privilege_to_luid_map $opts(system) $priv]} { - return [dict get $_privilege_to_luid_map $opts(system) $priv] - } - - # First check for privilege names we might have generated - if {[string match "Privilege-*" $priv]} { - set priv [string range $priv 10 end] - } - - # If already a LUID format, return as is, else look it up - if {[is_valid_luid_syntax $priv]} { - return $priv - } - - set luid [LookupPrivilegeValue $opts(system) $priv] - # This is an expensive call so stash it unless cache too big - if {[dict size $_privilege_to_luid_map] < 100} { - dict set _privilege_to_luid_map $opts(system) $priv $luid - } - - return $luid -} - - -# Return 1/0 if in LUID format -proc twapi::is_valid_luid_syntax {luid} { - return [regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$} $luid] -} - - -################################################################ -# Functions related to ACE's and ACL's - -# Create a new ACE -proc twapi::new_ace {type account rights args} { - array set opts [parseargs args { - {self.bool 1} - {recursecontainers.bool 0 2} - {recurseobjects.bool 0 1} - {recurseonelevelonly.bool 0 4} - {auditsuccess.bool 1 0x40} - {auditfailure.bool 1 0x80} - }] - - set sid [map_account_to_sid $account] - - set access_mask [_access_rights_to_mask $rights] - - switch -exact -- $type { - mandatory_label - - allow - - deny - - audit { - set typecode [_ace_type_symbol_to_code $type] - } - default { - error "Invalid or unsupported ACE type '$type'" - } - } - - set inherit_flags [expr {$opts(recursecontainers) | $opts(recurseobjects) | - $opts(recurseonelevelonly)}] - if {! $opts(self)} { - incr inherit_flags 8; #INHERIT_ONLY_ACE - } - - if {$type eq "audit"} { - set inherit_flags [expr {$inherit_flags | $opts(auditsuccess) | $opts(auditfailure)}] - } - - return [list $typecode $inherit_flags $access_mask $sid] -} - -# Get the ace type (allow, deny etc.) -proc twapi::get_ace_type {ace} { - return [_ace_type_code_to_symbol [lindex $ace 0]] -} - - -# Set the ace type (allow, deny etc.) -proc twapi::set_ace_type {ace type} { - return [lreplace $ace 0 0 [_ace_type_symbol_to_code $type]] -} - -# Get the access rights in an ACE -proc twapi::get_ace_rights {ace args} { - array set opts [parseargs args { - {type.arg ""} - resourcetype.arg - raw - } -maxleftover 0] - - if {$opts(raw)} { - return [format 0x%x [lindex $ace 2]] - } - - if {[lindex $ace 0] == 0x11} { - # MANDATORY_LABEL -> 0x11 - # Resource type is immaterial - return [_access_mask_to_rights [lindex $ace 2] mandatory_label] - } - - # Backward compatibility - in 2.x -type was documented instead - # of -resourcetype - if {[info exists opts(resourcetype)]} { - return [_access_mask_to_rights [lindex $ace 2] $opts(resourcetype)] - } else { - return [_access_mask_to_rights [lindex $ace 2] $opts(type)] - } -} - -# Set the access rights in an ACE -proc twapi::set_ace_rights {ace rights} { - return [lreplace $ace 2 2 [_access_rights_to_mask $rights]] -} - - -# Get the ACE sid -proc twapi::get_ace_sid {ace} { - return [lindex $ace 3] -} - -# Set the ACE sid -proc twapi::set_ace_sid {ace account} { - return [lreplace $ace 3 3 [map_account_to_sid $account]] -} - - -# Get audit flags - TBD document and test -proc twapi::get_ace_audit {ace} { - set audit {} - set mask [lindex $ace 1] - if {$mask & 0x40} { - lappend audit "success" - } - if {$mask & 0x80} { - lappend audit "failure" - } - return $audit -} - -# Get the inheritance options -proc twapi::get_ace_inheritance {ace} { - - set inherit_opts [list ] - set inherit_mask [lindex $ace 1] - - lappend inherit_opts -self \ - [expr {($inherit_mask & 8) == 0}] - lappend inherit_opts -recursecontainers \ - [expr {($inherit_mask & 2) != 0}] - lappend inherit_opts -recurseobjects \ - [expr {($inherit_mask & 1) != 0}] - lappend inherit_opts -recurseonelevelonly \ - [expr {($inherit_mask & 4) != 0}] - lappend inherit_opts -inherited \ - [expr {($inherit_mask & 16) != 0}] - - return $inherit_opts -} - -# Set the inheritance options. Unspecified options are not set -proc twapi::set_ace_inheritance {ace args} { - - array set opts [parseargs args { - self.bool - recursecontainers.bool - recurseobjects.bool - recurseonelevelonly.bool - }] - - set inherit_flags [lindex $ace 1] - if {[info exists opts(self)]} { - if {$opts(self)} { - resetbits inherit_flags 0x8; #INHERIT_ONLY_ACE -> 0x8 - } else { - setbits inherit_flags 0x8; #INHERIT_ONLY_ACE -> 0x8 - } - } - - foreach { - opt mask - } { - recursecontainers 2 - recurseobjects 1 - recurseonelevelonly 4 - } { - if {[info exists opts($opt)]} { - if {$opts($opt)} { - setbits inherit_flags $mask - } else { - resetbits inherit_flags $mask - } - } - } - - return [lreplace $ace 1 1 $inherit_flags] -} - - -# Sort ACE's in the standard recommended Win2K order -proc twapi::sort_aces {aces} { - - _init_ace_type_symbol_to_code_map - - foreach type [array names twapi::_ace_type_symbol_to_code_map] { - set direct_aces($type) [list ] - set inherited_aces($type) [list ] - } - - # Sort order is as follows: all direct (non-inherited) ACEs come - # before all inherited ACEs. Within these groups, the order should be - # access denied ACEs, access denied ACEs for objects/properties, - # access allowed ACEs, access allowed ACEs for objects/properties, - # TBD - check this ordering against http://msdn.microsoft.com/en-us/library/windows/desktop/aa379298%28v=vs.85%29.aspx - foreach ace $aces { - set type [get_ace_type $ace] - # INHERITED_ACE -> 0x10 - if {[lindex $ace 1] & 0x10} { - lappend inherited_aces($type) $ace - } else { - lappend direct_aces($type) $ace - } - } - - # TBD - check this order ACE's, especially audit and mandatory label - return [concat \ - $direct_aces(deny) \ - $direct_aces(deny_object) \ - $direct_aces(deny_callback) \ - $direct_aces(deny_callback_object) \ - $direct_aces(allow) \ - $direct_aces(allow_object) \ - $direct_aces(allow_compound) \ - $direct_aces(allow_callback) \ - $direct_aces(allow_callback_object) \ - $direct_aces(audit) \ - $direct_aces(audit_object) \ - $direct_aces(audit_callback) \ - $direct_aces(audit_callback_object) \ - $direct_aces(mandatory_label) \ - $direct_aces(alarm) \ - $direct_aces(alarm_object) \ - $direct_aces(alarm_callback) \ - $direct_aces(alarm_callback_object) \ - $inherited_aces(deny) \ - $inherited_aces(deny_object) \ - $inherited_aces(deny_callback) \ - $inherited_aces(deny_callback_object) \ - $inherited_aces(allow) \ - $inherited_aces(allow_object) \ - $inherited_aces(allow_compound) \ - $inherited_aces(allow_callback) \ - $inherited_aces(allow_callback_object) \ - $inherited_aces(audit) \ - $inherited_aces(audit_object) \ - $inherited_aces(audit_callback) \ - $inherited_aces(audit_callback_object) \ - $inherited_aces(mandatory_label) \ - $inherited_aces(alarm) \ - $inherited_aces(alarm_object) \ - $inherited_aces(alarm_callback) \ - $inherited_aces(alarm_callback_object)] -} - -# Pretty print an ACL -proc twapi::get_acl_text {acl args} { - array set opts [parseargs args { - {resourcetype.arg raw} - {offset.arg ""} - } -maxleftover 0] - - set count 0 - set result "$opts(offset)Rev: [get_acl_rev $acl]\n" - foreach ace [get_acl_aces $acl] { - append result "$opts(offset)ACE #[incr count]\n" - append result [get_ace_text $ace -offset "$opts(offset) " -resourcetype $opts(resourcetype)] - } - return $result -} - -# Pretty print an ACE -proc twapi::get_ace_text {ace args} { - array set opts [parseargs args { - {resourcetype.arg raw} - {offset.arg ""} - } -maxleftover 0] - - if {$ace eq "null"} { - return "Null" - } - - set offset $opts(offset) - array set bools {0 No 1 Yes} - array set inherit_flags [get_ace_inheritance $ace] - append inherit_text "${offset}Inherited: $bools($inherit_flags(-inherited))\n" - append inherit_text "${offset}Include self: $bools($inherit_flags(-self))\n" - append inherit_text "${offset}Recurse containers: $bools($inherit_flags(-recursecontainers))\n" - append inherit_text "${offset}Recurse objects: $bools($inherit_flags(-recurseobjects))\n" - append inherit_text "${offset}Recurse single level only: $bools($inherit_flags(-recurseonelevelonly))\n" - - set rights [get_ace_rights $ace -type $opts(resourcetype)] - if {[lsearch -glob $rights *_all_access] >= 0} { - set rights "All" - } else { - set rights [join $rights ", "] - } - - set acetype [get_ace_type $ace] - append result "${offset}Type: [string totitle $acetype]\n" - set user [get_ace_sid $ace] - catch {append user " ([map_account_to_name [get_ace_sid $ace]])"} - append result "${offset}User: $user\n" - append result "${offset}Rights: $rights\n" - if {$acetype eq "audit"} { - append result "${offset}Audit conditions: [join [get_ace_audit $ace] {, }]\n" - } - append result $inherit_text - - return $result -} - -# Create a new ACL -proc twapi::new_acl {{aces ""}} { - # NOTE: we ALWAYS set aclrev to 2. This may not be correct for the - # supplied ACEs but that's ok. The C level code calculates the correct - # acl rev level and overwrites anyways. - return [list 2 $aces] -} - -# Creates an ACL that gives the specified rights to specified trustees -proc twapi::new_restricted_dacl {accounts rights args} { - set access_mask [_access_rights_to_mask $rights] - - set aces {} - foreach account $accounts { - lappend aces [new_ace allow $account $access_mask {*}$args] - } - - return [new_acl $aces] - -} - -# Return the list of ACE's in an ACL -proc twapi::get_acl_aces {acl} { - return [lindex $acl 1] -} - -# Set the ACE's in an ACL -proc twapi::set_acl_aces {acl aces} { - # Note, we call new_acl since when ACEs change, the rev may also change - return [new_acl $aces] -} - -# Append to the ACE's in an ACL -proc twapi::append_acl_aces {acl aces} { - return [set_acl_aces $acl [concat [get_acl_aces $acl] $aces]] -} - -# Prepend to the ACE's in an ACL -proc twapi::prepend_acl_aces {acl aces} { - return [set_acl_aces $acl [concat $aces [get_acl_aces $acl]]] -} - -# Arrange the ACE's in an ACL in a standard order -proc twapi::sort_acl_aces {acl} { - return [set_acl_aces $acl [sort_aces [get_acl_aces $acl]]] -} - -# Return the ACL revision of an ACL -proc twapi::get_acl_rev {acl} { - return [lindex $acl 0] -} - - -# Create a new security descriptor -proc twapi::new_security_descriptor {args} { - array set opts [parseargs args { - owner.arg - group.arg - dacl.arg - sacl.arg - } -maxleftover 0] - - set secd [Twapi_InitializeSecurityDescriptor] - - # TBD - where are the control bits set? THe set_security_descrip[tor_* - # don't seem to set the control bits for related fields either. - foreach field {owner group dacl sacl} { - if {[info exists opts($field)]} { - set secd [set_security_descriptor_$field $secd $opts($field)] - } - } - - return $secd -} - -# Return the control bits in a security descriptor -# TBD - update for new Windows versions -proc twapi::get_security_descriptor_control {secd} { - if {[_null_secd $secd]} { - error "Attempt to get control field from NULL security descriptor." - } - - set control [lindex $secd 0] - - set retval [list ] - if {$control & 0x0001} { - lappend retval owner_defaulted - } - if {$control & 0x0002} { - lappend retval group_defaulted - } - if {$control & 0x0004} { - lappend retval dacl_present - } - if {$control & 0x0008} { - lappend retval dacl_defaulted - } - if {$control & 0x0010} { - lappend retval sacl_present - } - if {$control & 0x0020} { - lappend retval sacl_defaulted - } - if {$control & 0x0100} { - # Not documented because should not actually appear when reading a secd - lappend retval dacl_auto_inherit_req - } - if {$control & 0x0200} { - # Not documented because should not actually appear when reading a secd - lappend retval sacl_auto_inherit_req - } - if {$control & 0x0400} { - lappend retval dacl_auto_inherited - } - if {$control & 0x0800} { - lappend retval sacl_auto_inherited - } - if {$control & 0x1000} { - lappend retval dacl_protected - } - if {$control & 0x2000} { - lappend retval sacl_protected - } - if {$control & 0x4000} { - lappend retval rm_control_valid - } - if {$control & 0x8000} { - lappend retval self_relative - } - return $retval -} - -# Return the owner in a security descriptor -proc twapi::get_security_descriptor_owner {secd} { - if {[_null_secd $secd]} { - win32_error 87 "Attempt to get owner field from NULL security descriptor." - } - return [lindex $secd 1] -} - -# Set the owner in a security descriptor -proc twapi::set_security_descriptor_owner {secd account {defaulted 0}} { - if {[_null_secd $secd]} { - set secd [new_security_descriptor] - } - lassign $secd control - group dacl sacl - set sid [map_account_to_sid $account] - if {$defaulted} { - set control [expr {$control | 0x1}]; # SE_OWNER_DEFAULTED - } else { - set control [expr {$control & ~0x1}]; # ! SE_OWNER_DEFAULTED - } - return [list $control $sid $group $dacl $sacl] -} - -# Return the group in a security descriptor -proc twapi::get_security_descriptor_group {secd} { - if {[_null_secd $secd]} { - win32_error 87 "Attempt to get group field from NULL security descriptor." - } - return [lindex $secd 2] -} - -# Set the group in a security descriptor -proc twapi::set_security_descriptor_group {secd account {defaulted 0}} { - if {[_null_secd $secd]} { - set secd [new_security_descriptor] - } - lassign $secd control owner - dacl sacl - set sid [map_account_to_sid $account] - if {$defaulted} { - set control [expr {$control | 0x2}]; # SE_GROUP_DEFAULTED - } else { - set control [expr {$control & ~0x2}]; # ! SE_GROUP_DEFAULTED - } - return [list $control $owner $sid $dacl $sacl] -} - -# Return the DACL in a security descriptor -proc twapi::get_security_descriptor_dacl {secd} { - if {[_null_secd $secd]} { - win32_error 87 "Attempt to get DACL field from NULL security descriptor." - } - return [lindex $secd 3] -} - -# Set the dacl in a security descriptor -proc twapi::set_security_descriptor_dacl {secd acl {defaulted 0}} { - if {![_is_valid_acl $acl]} { - error "Invalid ACL <$acl>." - } - if {[_null_secd $secd]} { - set secd [new_security_descriptor] - } - lassign $secd control owner group - sacl - if {$acl eq "null"} { - set control [expr {$control & ~0x4}]; # ! SE_DACL_PRESENT - } else { - set control [expr {$control | 0x4}]; # SE_DACL_PRESENT - } - if {$defaulted} { - set control [expr {$control | 0x8}]; # SE_DACL_DEFAULTED - } else { - set control [expr {$control & ~0x8}]; # ! SE_DACL_DEFAULTED - } - return [list $control $owner $group $acl $sacl] -} - -# Return the SACL in a security descriptor -proc twapi::get_security_descriptor_sacl {secd} { - if {[_null_secd $secd]} { - win32_error 87 "Attempt to get SACL field from NULL security descriptor." - } - return [lindex $secd 4] -} - -# Set the sacl in a security descriptor -proc twapi::set_security_descriptor_sacl {secd acl {defaulted 0}} { - if {![_is_valid_acl $acl]} { - error "Invalid ACL <$acl>." - } - if {[_null_secd $secd]} { - set secd [new_security_descriptor] - } - lassign $secd control owner group dacl - - if {$acl eq "null"} { - set control [expr {$control & ~0x10}]; # ! SE_SACL_PRESENT - } else { - set control [expr {$control | 0x10}]; # SE_SACL_PRESENT - } - if {$defaulted} { - set control [expr {$control | 0x20}]; # SE_SACL_DEFAULTED - } else { - set control [expr {$control & ~0x20}]; # ! SE_SACL_DEFAULTED - } - return [list $control $owner $group $dacl $acl] -} - -# Get the specified security information for the given object -proc twapi::get_resource_security_descriptor {restype name args} { - - # -mandatory_label field is not documented. Should we ? TBD - array set opts [parseargs args { - owner - group - dacl - sacl - mandatory_label - all - handle - }] - - set wanted 0 - - # OWNER_SECURITY_INFORMATION 1 - # GROUP_SECURITY_INFORMATION 2 - # DACL_SECURITY_INFORMATION 4 - # SACL_SECURITY_INFORMATION 8 - foreach {field mask} {owner 1 group 2 dacl 4 sacl 8} { - if {$opts($field) || $opts(all)} { - incr wanted $mask; # Equivalent to OR operation - } - } - - # LABEL_SECURITY_INFORMATION 0x10 - if {[min_os_version 6]} { - if {$opts(mandatory_label) || $opts(all)} { - incr wanted 16; # OR with 0x10 - } - } - - # Note if no options specified, we ask for everything except - # SACL's which require special privileges - if {! $wanted} { - set wanted 0x7 - if {[min_os_version 6]} { - incr wanted 0x10 - } - } - - if {$opts(handle)} { - set restype [_map_resource_symbol_to_type $restype false] - if {$restype == 5} { - # GetSecurityInfo crashes if a handles is passed in for - # SE_LMSHARE (even erroneously). It expects a string name - # even though the prototype says HANDLE. Protect against this. - error "Share resource type (share or 5) cannot be used with -handle option" - } - set secd [GetSecurityInfo \ - [CastToHANDLE $name] \ - $restype \ - $wanted] - } else { - # GetNamedSecurityInfo seems to fail with a overlapped i/o - # in progress error under some conditions. If this happens - # try getting with resource-specific API's if possible. - trap { - set secd [GetNamedSecurityInfo \ - $name \ - [_map_resource_symbol_to_type $restype true] \ - $wanted] - } onerror {} { - # TBD - see what other resource-specific API's there are - if {$restype eq "share"} { - set secd [lindex [get_share_info $name -secd] 1] - } else { - # Throw the same error - rethrow - } - } - } - - return $secd -} - - -# Set the specified security information for the given object -# See http://search.cpan.org/src/TEVERETT/Win32-Security-0.50/README -# for a good discussion even though that applies to Perl -proc twapi::set_resource_security_descriptor {restype name secd args} { - - # PROTECTED_DACL_SECURITY_INFORMATION 0x80000000 - # PROTECTED_SACL_SECURITY_INFORMATION 0x40000000 - # UNPROTECTED_DACL_SECURITY_INFORMATION 0x20000000 - # UNPROTECTED_SACL_SECURITY_INFORMATION 0x10000000 - array set opts [parseargs args { - all - handle - owner - group - dacl - sacl - mandatory_label - {protect_dacl {} 0x80000000} - {unprotect_dacl {} 0x20000000} - {protect_sacl {} 0x40000000} - {unprotect_sacl {} 0x10000000} - }] - - - if {![min_os_version 6]} { - if {$opts(mandatory_label)} { - error "Option -mandatory_label not supported by this version of Windows" - } - } - - if {$opts(protect_dacl) && $opts(unprotect_dacl)} { - error "Cannot specify both -protect_dacl and -unprotect_dacl." - } - - if {$opts(protect_sacl) && $opts(unprotect_sacl)} { - error "Cannot specify both -protect_sacl and -unprotect_sacl." - } - - set mask [expr {$opts(protect_dacl) | $opts(unprotect_dacl) | - $opts(protect_sacl) | $opts(unprotect_sacl)}] - - if {$opts(owner) || $opts(all)} { - set opts(owner) [get_security_descriptor_owner $secd] - setbits mask 1; # OWNER_SECURITY_INFORMATION - } else { - set opts(owner) "" - } - - if {$opts(group) || $opts(all)} { - set opts(group) [get_security_descriptor_group $secd] - setbits mask 2; # GROUP_SECURITY_INFORMATION - } else { - set opts(group) "" - } - - if {$opts(dacl) || $opts(all)} { - set opts(dacl) [get_security_descriptor_dacl $secd] - setbits mask 4; # DACL_SECURITY_INFORMATION - } else { - set opts(dacl) null - } - - if {$opts(sacl) || $opts(mandatory_label) || $opts(all)} { - set sacl [get_security_descriptor_sacl $secd] - if {$opts(sacl) || $opts(all)} { - setbits mask 0x8; # SACL_SECURITY_INFORMATION - } - if {[min_os_version 6]} { - if {$opts(mandatory_label) || $opts(all)} { - setbits mask 0x10; # LABEL_SECURITY_INFORMATION - } - } - set opts(sacl) $sacl - } else { - set opts(sacl) null - } - - if {$mask == 0} { - error "Must specify at least one of the options -all, -dacl, -sacl, -owner, -group or -mandatory_label" - } - - if {$opts(handle)} { - set restype [_map_resource_symbol_to_type $restype false] - if {$restype == 5} { - # GetSecurityInfo crashes if a handles is passed in for - # SE_LMSHARE (even erroneously). It expects a string name - # even though the prototype says HANDLE. Protect against this. - error "Share resource type (share or 5) cannot be used with -handle option" - } - - SetSecurityInfo \ - [CastToHANDLE $name] \ - [_map_resource_symbol_to_type $restype false] \ - $mask \ - $opts(owner) \ - $opts(group) \ - $opts(dacl) \ - $opts(sacl) - } else { - SetNamedSecurityInfo \ - $name \ - [_map_resource_symbol_to_type $restype true] \ - $mask \ - $opts(owner) \ - $opts(group) \ - $opts(dacl) \ - $opts(sacl) - } -} - -# Get integrity level from a security descriptor -proc twapi::get_security_descriptor_integrity {secd args} { - if {[min_os_version 6]} { - foreach ace [get_acl_aces [get_security_descriptor_sacl $secd]] { - if {[get_ace_type $ace] eq "mandatory_label"} { - if {! [dict get [get_ace_inheritance $ace] -self]} continue; # Does not apply to itself - set integrity [_sid_to_integrity [get_ace_sid $ace] {*}$args] - set rights [get_ace_rights $ace -resourcetype mandatory_label] - return [list $integrity $rights] - } - } - } - return {} -} - -# Get integrity level for a resource -proc twapi::get_resource_integrity {restype name args} { - # Note label and raw options are simply passed on - - if {![min_os_version 6]} { - return "" - } - set saved_args $args - array set opts [parseargs args { - label - raw - handle - }] - - if {$opts(handle)} { - set secd [get_resource_security_descriptor $restype $name -mandatory_label -handle] - } else { - set secd [get_resource_security_descriptor $restype $name -mandatory_label] - } - - return [get_security_descriptor_integrity $secd {*}$saved_args] -} - - -proc twapi::set_security_descriptor_integrity {secd integrity rights args} { - # Not clear from docs whether this can - # be done without interfering with SACL fields. Nevertheless - # we provide this proc because we might want to set the - # integrity level on new objects create thru CreateFile etc. - # TBD - need to test under vista and win 7 - - array set opts [parseargs args { - {recursecontainers.bool 0} - {recurseobjects.bool 0} - } -maxleftover 0] - - # We preserve any non-integrity aces in the sacl. - set sacl [get_security_descriptor_sacl $secd] - set aces {} - foreach ace [get_acl_aces $sacl] { - if {[get_ace_type $ace] ne "mandatory_label"} { - lappend aces $ace - } - } - - # Now create and attach an integrity ace. Note placement does not - # matter - lappend aces [new_ace mandatory_label \ - [_integrity_to_sid $integrity] \ - [_access_rights_to_mask $rights] \ - -self 1 \ - -recursecontainers $opts(recursecontainers) \ - -recurseobjects $opts(recurseobjects)] - - return [set_security_descriptor_sacl $secd [new_acl $aces]] -} - -proc twapi::set_resource_integrity {restype name integrity rights args} { - array set opts [parseargs args { - {recursecontainers.bool 0} - {recurseobjects.bool 0} - handle - } -maxleftover 0] - - set secd [set_security_descriptor_integrity \ - [new_security_descriptor] \ - $integrity \ - $rights \ - -recurseobjects $opts(recurseobjects) \ - -recursecontainers $opts(recursecontainers)] - - if {$opts(handle)} { - set_resource_security_descriptor $restype $name $secd -mandatory_label -handle - } else { - set_resource_security_descriptor $restype $name $secd -mandatory_label - } -} - - -# Convert a security descriptor to SDDL format -proc twapi::security_descriptor_to_sddl {secd} { - return [twapi::ConvertSecurityDescriptorToStringSecurityDescriptor $secd 1 0x1f] -} - -# Convert SDDL to a security descriptor -proc twapi::sddl_to_security_descriptor {sddl} { - return [twapi::ConvertStringSecurityDescriptorToSecurityDescriptor $sddl 1] -} - -# Return the text for a security descriptor -proc twapi::get_security_descriptor_text {secd args} { - if {[_null_secd $secd]} { - return "null" - } - - array set opts [parseargs args { - {resourcetype.arg raw} - } -maxleftover 0] - - append result "Flags:\t[get_security_descriptor_control $secd]\n" - set name [get_security_descriptor_owner $secd] - if {$name eq ""} { - set name Undefined - } else { - catch {set name [map_account_to_name $name]} - } - append result "Owner:\t$name\n" - set name [get_security_descriptor_group $secd] - if {$name eq ""} { - set name Undefined - } else { - catch {set name [map_account_to_name $name]} - } - append result "Group:\t$name\n" - - if {0} { - set acl [get_security_descriptor_dacl $secd] - append result "DACL Rev: [get_acl_rev $acl]\n" - set index 0 - foreach ace [get_acl_aces $acl] { - append result "\tDACL Entry [incr index]\n" - append result "[get_ace_text $ace -offset "\t " -resourcetype $opts(resourcetype)]" - } - set acl [get_security_descriptor_sacl $secd] - append result "SACL Rev: [get_acl_rev $acl]\n" - set index 0 - foreach ace [get_acl_aces $acl] { - append result "\tSACL Entry $index\n" - append result [get_ace_text $ace -offset "\t " -resourcetype $opts(resourcetype)] - } - } else { - append result "DACL:\n" - append result [get_acl_text [get_security_descriptor_dacl $secd] -offset " " -resourcetype $opts(resourcetype)] - append result "SACL:\n" - append result [get_acl_text [get_security_descriptor_sacl $secd] -offset " " -resourcetype $opts(resourcetype)] - } - - return $result -} - - -# Log off -proc twapi::logoff {args} { - array set opts [parseargs args { - {force {} 0x4} - {forceifhung {} 0x10} - } -maxleftover 0] - ExitWindowsEx [expr {$opts(force) | $opts(forceifhung)}] 0 -} - -# Lock the workstation -proc twapi::lock_workstation {} { - LockWorkStation -} - - -# Get a new LUID -proc twapi::new_luid {} { - return [AllocateLocallyUniqueId] -} - - -# Get the description of a privilege -proc twapi::get_privilege_description {priv} { - if {[catch {LookupPrivilegeDisplayName "" $priv} desc]} { - # The above function will only return descriptions for - # privileges, not account rights. Hard code descriptions - # for some account rights - set desc [dict* { - SeBatchLogonRight "Log on as a batch job" - SeDenyBatchLogonRight "Deny logon as a batch job" - SeDenyInteractiveLogonRight "Deny interactive logon" - SeDenyNetworkLogonRight "Deny access to this computer from the network" - SeRemoteInteractiveLogonRight "Remote interactive logon" - SeDenyRemoteInteractiveLogonRight "Deny interactive remote logon" - SeDenyServiceLogonRight "Deny logon as a service" - SeInteractiveLogonRight "Log on locally" - SeNetworkLogonRight "Access this computer from the network" - SeServiceLogonRight "Log on as a service" - } $priv] - } - return $desc -} - - - -# For backward compatibility, emulate GetUserName using GetUserNameEx -proc twapi::GetUserName {} { - return [file tail [GetUserNameEx 2]] -} - - -################################################################ -# Utility and helper functions - - - -# Returns an sid field from a token -proc twapi::_get_token_sid_field {tok field options} { - array set opts [parseargs options {name}] - set owner [GetTokenInformation $tok $field] - if {$opts(name)} { - set owner [lookup_account_sid $owner] - } - return $owner -} - -# Map token group attributes -# TBD - write a test for this -proc twapi::map_token_group_attr {attr} { - # SE_GROUP_MANDATORY 0x00000001 - # SE_GROUP_ENABLED_BY_DEFAULT 0x00000002 - # SE_GROUP_ENABLED 0x00000004 - # SE_GROUP_OWNER 0x00000008 - # SE_GROUP_USE_FOR_DENY_ONLY 0x00000010 - # SE_GROUP_LOGON_ID 0xC0000000 - # SE_GROUP_RESOURCE 0x20000000 - # SE_GROUP_INTEGRITY 0x00000020 - # SE_GROUP_INTEGRITY_ENABLED 0x00000040 - - return [_make_symbolic_bitmask $attr { - mandatory 0x00000001 - enabled_by_default 0x00000002 - enabled 0x00000004 - owner 0x00000008 - use_for_deny_only 0x00000010 - logon_id 0xC0000000 - resource 0x20000000 - integrity 0x00000020 - integrity_enabled 0x00000040 - }] -} - -# Map token privilege attributes -# TBD - write a test for this -proc twapi::map_token_privilege_attr {attr} { - # SE_PRIVILEGE_ENABLED_BY_DEFAULT 0x00000001 - # SE_PRIVILEGE_ENABLED 0x00000002 - # SE_PRIVILEGE_USED_FOR_ACCESS 0x80000000 - - return [_make_symbolic_bitmask $attr { - enabled_by_default 0x00000001 - enabled 0x00000002 - used_for_access 0x80000000 - }] -} - - - -# Map an ace type symbol (eg. allow) to the underlying ACE type code -proc twapi::_ace_type_symbol_to_code {type} { - _init_ace_type_symbol_to_code_map - return $::twapi::_ace_type_symbol_to_code_map($type) -} - - -# Map an ace type code to an ACE type symbol -proc twapi::_ace_type_code_to_symbol {type} { - _init_ace_type_symbol_to_code_map - return $::twapi::_ace_type_code_to_symbol_map($type) -} - - -# Init the arrays used for mapping ACE type symbols to codes and back -proc twapi::_init_ace_type_symbol_to_code_map {} { - - if {[info exists ::twapi::_ace_type_symbol_to_code_map]} { - return - } - - # ACCESS_ALLOWED_ACE_TYPE 0x0 - # ACCESS_DENIED_ACE_TYPE 0x1 - # SYSTEM_AUDIT_ACE_TYPE 0x2 - # SYSTEM_ALARM_ACE_TYPE 0x3 - # ACCESS_ALLOWED_COMPOUND_ACE_TYPE 0x4 - # ACCESS_ALLOWED_OBJECT_ACE_TYPE 0x5 - # ACCESS_DENIED_OBJECT_ACE_TYPE 0x6 - # SYSTEM_AUDIT_OBJECT_ACE_TYPE 0x7 - # SYSTEM_ALARM_OBJECT_ACE_TYPE 0x8 - # ACCESS_ALLOWED_CALLBACK_ACE_TYPE 0x9 - # ACCESS_DENIED_CALLBACK_ACE_TYPE 0xA - # ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE 0xB - # ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE 0xC - # SYSTEM_AUDIT_CALLBACK_ACE_TYPE 0xD - # SYSTEM_ALARM_CALLBACK_ACE_TYPE 0xE - # SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE 0xF - # SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE 0x10 - # SYSTEM_MANDATORY_LABEL_ACE_TYPE 0x11 - - # Define the array. - array set ::twapi::_ace_type_symbol_to_code_map { - allow 0 deny 1 audit 2 alarm 3 allow_compound 4 - allow_object 5 deny_object 6 audit_object 7 - alarm_object 8 allow_callback 9 deny_callback 10 - allow_callback_object 11 deny_callback_object 12 - audit_callback 13 alarm_callback 14 audit_callback_object 15 - alarm_callback_object 16 mandatory_label 17 - } - - # Now define the array in the other direction - foreach {sym code} [array get ::twapi::_ace_type_symbol_to_code_map] { - set ::twapi::_ace_type_code_to_symbol_map($code) $sym - } -} - -# Map a resource symbol type to value -proc twapi::_map_resource_symbol_to_type {sym {named true}} { - if {[string is integer $sym]} { - return $sym - } - - # Note "window" is not here because window stations and desktops - # do not have unique names and cannot be used with Get/SetNamedSecurityInfo - switch -exact -- $sym { - file { return 1 } - service { return 2 } - printer { return 3 } - registry { return 4 } - share { return 5 } - kernelobj { return 6 } - } - if {$named} { - error "Resource type '$sym' not valid for named resources." - } - - switch -exact -- $sym { - windowstation { return 7 } - directoryservice { return 8 } - directoryserviceall { return 9 } - providerdefined { return 10 } - wmiguid { return 11 } - registrywow6432key { return 12 } - } - - error "Resource type '$sym' not valid" -} - -# Valid LUID syntax -proc twapi::_is_valid_luid_syntax luid { - return [regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$} $luid] -} - - -# Delete rights for an account -proc twapi::_delete_rights {account system} { - # Remove the user from the LSA rights database. Ignore any errors - catch { - remove_account_rights $account {} -all -system $system - - # On Win2k SP1 and SP2, we need to delay a bit for notifications - # to complete before deleting the account. - # See http://support.microsoft.com/?id=316827 - lassign [get_os_version] major minor sp dontcare - if {($major == 5) && ($minor == 0) && ($sp < 3)} { - after 1000 - } - } -} - - -# Get a token for a user -proc twapi::open_user_token {username password args} { - - array set opts [parseargs args { - domain.arg - {type.arg batch {interactive network batch service unlock network_cleartext new_credentials}} - {provider.arg default {default winnt35 winnt40 winnt50}} - } -nulldefault] - - # LOGON32_LOGON_INTERACTIVE 2 - # LOGON32_LOGON_NETWORK 3 - # LOGON32_LOGON_BATCH 4 - # LOGON32_LOGON_SERVICE 5 - # LOGON32_LOGON_UNLOCK 7 - # LOGON32_LOGON_NETWORK_CLEARTEXT 8 - # LOGON32_LOGON_NEW_CREDENTIALS 9 - set type [dict get {interactive 2 network 3 batch 4 service 5 - unlock 7 network_cleartext 8 new_credentials 9} $opts(type)] - - # LOGON32_PROVIDER_DEFAULT 0 - # LOGON32_PROVIDER_WINNT35 1 - # LOGON32_PROVIDER_WINNT40 2 - # LOGON32_PROVIDER_WINNT50 3 - set provider [dict get {default 0 winnt35 1 winnt40 2 winnt50 3} $opts(provider)] - - # If username is of the form user@domain, then domain must not be specified - # If username is not of the form user@domain, then domain is set to "." - # if it is empty - if {[regexp {^([^@]+)@(.+)} $username dummy user domain]} { - if {[string length $opts(domain)] != 0} { - error "The -domain option must not be specified when the username is in UPN format (user@domain)" - } - } else { - if {[string length $opts(domain)] == 0} { - set opts(domain) "." - } - } - - return [LogonUser $username $opts(domain) $password $type $provider] -} - - -# Impersonate a user given a token -proc twapi::impersonate_token {token} { - ImpersonateLoggedOnUser $token -} - - -# Impersonate a user -proc twapi::impersonate_user {args} { - set token [open_user_token {*}$args] - trap { - impersonate_token $token - } finally { - close_token $token - } -} - -# Impersonate self -proc twapi::impersonate_self {level} { - switch -exact -- $level { - anonymous { set level 0 } - identification { set level 1 } - impersonation { set level 2 } - delegation { set level 3 } - default { - error "Invalid impersonation level $level" - } - } - ImpersonateSelf $level -} - -# Set a thread token - currently only for current thread -proc twapi::set_thread_token {token} { - SetThreadToken NULL $token -} - -# Reset a thread token - currently only for current thread -proc twapi::reset_thread_token {} { - SetThreadToken NULL NULL -} - -proc twapi::_cred_cook {cred} { - set rec [twine {flags type target comment lastwritten credblob persist attributes targetalias username} $cred] - dict with rec { - set type [dict* { - 1 generic 2 domain_password 3 domain_certificate 4 domain_visible_password 5 generic_certificate 6 domain_extended} $type] - set persist [dict* { - 1 session 2 local_machine 3 enterprise - } $persist] - } - return $rec -} - -proc twapi::credentials {{pattern {}}} { - trap { - set raw [CredEnumerate $pattern 0] - } onerror {TWAPI_WIN32 1168} { - # Not found / no entries - return {} - } - - return [lmap cred $raw { _cred_cook $cred }] -} - -proc twapi::cred_delete {target {type generic}} { - if {[string is integer -strict $type]} { - set type_flags $type - } else { - set type_flags [dict get { - generic 1 - domain_password 2 - domain_certificate 3 - domain_visible_password 4 - generic_certificate 5 - domain_extended 6 - } $type] - } - CredDelete $target $type_flags 0 - return -} - -proc twapi::cred_get {target {type generic}} { - if {[string is integer -strict $type]} { - set type_flags $type - } else { - set type_flags [dict get { - generic 1 - domain_password 2 - domain_certificate 3 - domain_visible_password 4 - generic_certificate 5 - domain_extended 6 - } $type] - } - return [_cred_cook [CredRead $target $type_flags 0]] -} - - -# TBD - document after implementing AuditQuerySystemPolicy and friends -# for Vista & later -proc twapi::get_audit_policy {lsah} { - lassign [LsaQueryInformationPolicy $lsah 2] enabled audit_masks - set settings {} - foreach name { - system logon object_access privilege_use detailed_tracking - policy_change account_management directory_service_access - account_logon - } mask $audit_masks { - # Copied from the Perl Win32 book. - set setting {} - if {$mask == 0 || ($mask & 4)} { - set setting {} - } elseif {$mask & 3} { - if {$mask & 1} { lappend setting log_on_success } - if {$mask & 2} { lappend setting log_on_failure } - } else { - error "Unexpected audit mask value $mask" - } - lappend settings $name $setting - } - - return [list $enabled $settings] -} - - -# TBD - document after implementing AuditQuerySystemPolicy and friends -# for Vista & later -proc twapi::set_audit_policy {lsah enable settings} { - set audit_masks {} - # NOTE: the order here MUST match the enum definition for - # POLICY_AUDIT_EVENT_TYPE (see SDK docs) - foreach name { - system logon object_access privilege_use detailed_tracking - policy_change account_management directory_service_access - account_logon - } { - set mask 0; # POLICY_AUDIT_EVENT_UNCHANGED - if {[dict exists $settings $name]} { - set setting [dict get $settings $name] - # 4 -> POLICY_AUDIT_EVENT_NONE resets existing FAILURE|SUCCESS - set mask 4 - if {"log_on_success" in $setting} { - set mask [expr {$mask | 1}]; # POLICY_AUDIT_EVENT_SUCCESS - } - if {"log_on_failure" in $setting} { - set mask [expr {$mask | 2}]; # POLICY_AUDIT_EVENT_FAILURE - } - } - lappend audit_masks $mask - } - - Twapi_LsaSetInformationPolicy_AuditEvents $lsah $enable $audit_masks -} - -# Returns true if null security descriptor -proc twapi::_null_secd {secd} { - if {[llength $secd] == 0} { - return 1 - } else { - return 0 - } -} - -# Returns true if a valid ACL -proc twapi::_is_valid_acl {acl} { - if {$acl eq "null"} { - return 1 - } else { - return [IsValidAcl $acl] - } -} - -# Returns true if a valid ACL -proc twapi::_is_valid_security_descriptor {secd} { - if {[_null_secd $secd]} { - return 1 - } else { - return [IsValidSecurityDescriptor $secd] - } -} - -# Maps a integrity SID to integer or label -proc twapi::_sid_to_integrity {sid args} { - # Note - to make it simpler for callers, additional options are ignored - array set opts [parseargs args { - label - raw - }] - - if {$opts(raw) && $opts(label)} { - error "Options -raw and -label may not be specified together." - } - - if {![string equal -length 7 S-1-16-* $sid]} { - error "Unexpected integrity level value '$sid' returned by GetTokenInformation." - } - - if {$opts(raw)} { - return $sid - } - - set integrity [string range $sid 7 end] - - if {! $opts(label)} { - # Return integer level - return $integrity - } - - # Map to a label - if {$integrity < 4096} { - return untrusted - } elseif {$integrity < 8192} { - return low - } elseif {$integrity < 8448} { - return medium - } elseif {$integrity < 12288} { - return mediumplus - } elseif {$integrity < 16384} { - return high - } else { - return system - } - -} - -proc twapi::_integrity_to_sid {integrity} { - # Integrity level must be either a number < 65536 or a valid string - # or a SID. Check for the first two and convert to SID. Anything else - # will be trapped by the actual call as an invalid format. - if {[string is integer -strict $integrity]} { - set integrity S-1-16-[format %d $integrity]; # In case in hex - } else { - switch -glob -- $integrity { - untrusted { set integrity S-1-16-0 } - low { set integrity S-1-16-4096 } - medium { set integrity S-1-16-8192 } - mediumplus { set integrity S-1-16-8448 } - high { set integrity S-1-16-12288 } - system { set integrity S-1-16-16384 } - S-1-16-* { - if {![string is integer -strict [string range $integrity 7 end]]} { - error "Invalid integrity level '$integrity'" - } - # Format in case level component was in hex/octal - set integrity S-1-16-[format %d [string range $integrity 7 end]] - } - default { - error "Invalid integrity level '$integrity'" - } - } - } - return $integrity -} - -proc twapi::_map_luids_and_attrs_to_privileges {luids_and_attrs} { - set enabled_privs [list ] - set disabled_privs [list ] - foreach item $luids_and_attrs { - set priv [map_luid_to_privilege [lindex $item 0] -mapunknown] - # SE_PRIVILEGE_ENABLED -> 0x2 - if {[lindex $item 1] & 2} { - lappend enabled_privs $priv - } else { - lappend disabled_privs $priv - } - } - - return [list $enabled_privs $disabled_privs] -} - -# Map impersonation level to symbol -proc twapi::_map_impersonation_level ilevel { - set map { - 0 anonymous - 1 identification - 2 impersonation - 3 delegation - } - if {[dict exists $map [incr ilevel 0]]} { - return [dict get $map $ilevel] - } else { - return $ilevel - } -} - -proc twapi::_map_well_known_sid_name {sidname} { - if {[string is integer -strict $sidname]} { - return $sidname - } - - set sidname [string tolower $sidname] - set sidname [dict* { - administrator accountadministrator - {cert publishers} accountcertadmins - {domain computers} accountcomputers - {domain controllers} accountcontrollers - {domain admins} accountdomainadmins - {domain guests} accountdomainguests - {domain users} accountdomainusers - {enterprise admins} accountenterpriseadmins - guest accountguest - krbtgt accountkrbtgt - {read-only domain controllers} accountreadonlycontrollers - {schema admins} accountschemaadmins - {anonymous logon} anonymous - {authenticated users} authenticateduser - batch batch - administrators builtinadministrators - {all application packages} builtinanypackage - {backup operators} builtinbackupoperators - {distributed com users} builtindcomusers - builtin builtindomain - {event log readers} builtineventlogreadersgroup - guests builtinguests - {performance log users} builtinperfloggingusers - {performance monitor users} builtinperfmonitoringusers - {power users} builtinpowerusers - {remote desktop users} builtinremotedesktopusers - replicator builtinreplicator - users builtinusers - {console logon} consolelogon - {creator group} creatorgroup - {creator group server} creatorgroupserver - {creator owner} creatorowner - {owner rights} creatorownerrights - {creator owner server} creatorownerserver - dialup dialup - {digest authentication} digestauthentication - {enterprise domain controllers} enterprisecontrollers - {enterprise read-only domain controllers beta} enterprisereadonlycontrollers - {high mandatory level} highlabel - interactive interactive - local local - {local service} localservice - system localsystem - {low mandatory level} lowlabel - {medium mandatory level} mediumlabel - {medium plus mandatory level} mediumpluslabel - network network - {network service} networkservice - {enterprise read-only domain controllers} newenterprisereadonlycontrollers - {ntlm authentication} ntlmauthentication - {null sid} null - proxy proxy - {remote interactive logon} remotelogonid - restricted restrictedcode - {schannel authentication} schannelauthentication - self self - service service - {system mandatory level} systemlabel - {terminal server user} terminalserver - {untrusted mandatory level} untrustedlabel - everyone world - {write restricted} writerestrictedcode - } $sidname] - - return [dict! { - null 0 - world 1 - local 2 - creatorowner 3 - creatorgroup 4 - creatorownerserver 5 - creatorgroupserver 6 - ntauthority 7 - dialup 8 - network 9 - batch 10 - interactive 11 - service 12 - anonymous 13 - proxy 14 - enterprisecontrollers 15 - self 16 - authenticateduser 17 - restrictedcode 18 - terminalserver 19 - remotelogonid 20 - logonids 21 - localsystem 22 - localservice 23 - networkservice 24 - builtindomain 25 - builtinadministrators 26 - builtinusers 27 - builtinguests 28 - builtinpowerusers 29 - builtinaccountoperators 30 - builtinsystemoperators 31 - builtinprintoperators 32 - builtinbackupoperators 33 - builtinreplicator 34 - builtinprewindows2000compatibleaccess 35 - builtinremotedesktopusers 36 - builtinnetworkconfigurationoperators 37 - accountadministrator 38 - accountguest 39 - accountkrbtgt 40 - accountdomainadmins 41 - accountdomainusers 42 - accountdomainguests 43 - accountcomputers 44 - accountcontrollers 45 - accountcertadmins 46 - accountschemaadmins 47 - accountenterpriseadmins 48 - accountpolicyadmins 49 - accountrasandiasservers 50 - ntlmauthentication 51 - digestauthentication 52 - schannelauthentication 53 - thisorganization 54 - otherorganization 55 - builtinincomingforesttrustbuilders 56 - builtinperfmonitoringusers 57 - builtinperfloggingusers 58 - builtinauthorizationaccess 59 - builtinterminalserverlicenseservers 60 - builtindcomusers 61 - builtiniusers 62 - iuser 63 - builtincryptooperators 64 - untrustedlabel 65 - lowlabel 66 - mediumlabel 67 - highlabel 68 - systemlabel 69 - writerestrictedcode 70 - creatorownerrights 71 - cacheableprincipalsgroup 72 - noncacheableprincipalsgroup 73 - enterprisereadonlycontrollers 74 - accountreadonlycontrollers 75 - builtineventlogreadersgroup 76 - newenterprisereadonlycontrollers 77 - builtincertsvcdcomaccessgroup 78 - mediumpluslabel 79 - locallogon 80 - consolelogon 81 - thisorganizationcertificate 82 - applicationpackageauthority 83 - builtinanypackage 84 - capabilityinternetclient 85 - capabilityinternetclientserver 86 - capabilityprivatenetworkclientserver 87 - capabilitypictureslibrary 88 - capabilityvideoslibrary 89 - capabilitymusiclibrary 90 - capabilitydocumentslibrary 91 - capabilitysharedusercertificates 92 - capabilityenterpriseauthentication 93 - capabilityremovablestorage 94 - } $sidname] -} - +# +# Copyright (c) 2003-2014, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# TBD - allow SID and account name to be used interchangeably in various +# functions +# TBD - ditto for LUID v/s privilege names + +namespace eval twapi { + # Map privilege level mnemonics to priv level + variable priv_level_map + array set priv_level_map {guest 0 user 1 admin 2} + + # TBD - the following are not used, enhancements needed ? + # OBJECT_INHERIT_ACE 0x1 + # CONTAINER_INHERIT_ACE 0x2 + # NO_PROPAGATE_INHERIT_ACE 0x4 + # INHERIT_ONLY_ACE 0x8 + # INHERITED_ACE 0x10 + # VALID_INHERIT_FLAGS 0x1F + + # Cache of privilege names to LUID's + variable _privilege_to_luid_map + set _privilege_to_luid_map {} + variable _luid_to_privilege_map {} + +} + + +# Returns token for a process +proc twapi::open_process_token {args} { + array set opts [parseargs args { + pid.int + hprocess.arg + {access.arg token_query} + } -maxleftover 0] + + set access [_access_rights_to_mask $opts(access)] + + # Get a handle for the process + if {[info exists opts(hprocess)]} { + if {[info exists opts(pid)]} { + error "Options -pid and -hprocess cannot be used together." + } + set ph $opts(hprocess) + } elseif {[info exists opts(pid)]} { + set ph [get_process_handle $opts(pid)] + } else { + variable my_process_handle + set ph $my_process_handle + } + trap { + # Get a token for the process + set ptok [OpenProcessToken $ph $access] + } finally { + # Close handle only if we did an OpenProcess + if {[info exists opts(pid)]} { + CloseHandle $ph + } + } + + return $ptok +} + +# Returns token for a process +proc twapi::open_thread_token {args} { + array set opts [parseargs args { + tid.int + hthread.arg + {access.arg token_query} + {self.bool false} + } -maxleftover 0] + + set access [_access_rights_to_mask $opts(access)] + + # Get a handle for the thread + if {[info exists opts(hthread)]} { + if {[info exists opts(tid)]} { + error "Options -tid and -hthread cannot be used together." + } + set th $opts(hthread) + } elseif {[info exists opts(tid)]} { + set th [get_thread_handle $opts(tid)] + } else { + set th [GetCurrentThread] + } + + trap { + # Get a token for the thread + set tok [OpenThreadToken $th $access $opts(self)] + } finally { + # Close handle only if we did an OpenProcess + if {[info exists opts(tid)]} { + CloseHandle $th + } + } + + return $tok +} + +proc twapi::close_token {tok} { + CloseHandle $tok +} + +# TBD - document and test +proc twapi::duplicate_token {tok args} { + parseargs args { + access.arg + {inherit.bool 0} + {secd.arg ""} + {impersonationlevel.sym impersonation {anonymous 0 identification 1 impersonation 2 delegation 3}} + {type.sym primary {primary 1 impersonation 2}} + } -maxleftover 0 -setvars + + if {[info exists access]} { + set access [_access_rights_to_mask $access] + } else { + # If no desired access is indicated, we want the same access as + # the original handle + set access 0 + } + + return [DuplicateTokenEx $tok $access \ + [_make_secattr $secd $inherit] \ + $impersonationlevel $type] +} + +proc twapi::get_token_info {tok args} { + array set opts [parseargs args { + defaultdacl + disabledprivileges + elevation + enabledprivileges + groupattrs + groups + groupsids + integrity + integritylabel + linkedtoken + logonsession + logonsessionsid + origin + primarygroup + primarygroupsid + privileges + restrictedgroupattrs + restrictedgroups + tssession + usersid + virtualized + } -maxleftover 0] + + # Do explicit check so we return error if no args specified + # and $tok is invalid + if {![pointer? $tok]} { + error "Invalid token handle '$tok'" + } + + # TBD - add an -ignorerrors option + + set result [dict create] + trap { + if {$opts(privileges) || $opts(disabledprivileges) || $opts(enabledprivileges)} { + lassign [GetTokenInformation $tok 13] gtigroups gtirestrictedgroups privs gtilogonsession + set privs [_map_luids_and_attrs_to_privileges $privs] + if {$opts(privileges)} { + lappend result -privileges $privs + } + if {$opts(enabledprivileges)} { + lappend result -enabledprivileges [lindex $privs 0] + } + if {$opts(disabledprivileges)} { + lappend result -disabledprivileges [lindex $privs 1] + } + } + if {$opts(defaultdacl)} { + lappend result -defaultdacl [get_token_default_dacl $tok] + } + if {$opts(origin)} { + lappend result -origin [get_token_origin $tok] + } + if {$opts(linkedtoken)} { + lappend result -linkedtoken [get_token_linked_token $tok] + } + if {$opts(elevation)} { + lappend result -elevation [get_token_elevation $tok] + } + if {$opts(integrity)} { + lappend result -integrity [get_token_integrity $tok] + } + if {$opts(integritylabel)} { + lappend result -integritylabel [get_token_integrity $tok -label] + } + if {$opts(virtualized)} { + lappend result -virtualized [get_token_virtualization $tok] + } + if {$opts(tssession)} { + lappend result -tssession [get_token_tssession $tok] + } + if {$opts(usersid)} { + # First element of groups is user sid + if {[info exists gtigroups]} { + lappend result -usersid [lindex $gtigroups 0 0 0] + } else { + lappend result -usersid [get_token_user $tok] + } + } + if {$opts(groups) || $opts(groupsids)} { + if {[info exists gtigroups]} { + set gsids {} + # First element of groups is user sid, skip it + foreach item [lrange $gtigroups 1 end] { + lappend gsids [lindex $item 0] + } + } else { + set gsids [get_token_groups $tok] + } + if {$opts(groupsids)} { + lappend result -groupsids $gsids + } + if {$opts(groups)} { + set items {} + foreach gsid $gsids { + lappend items [lookup_account_sid $gsid] + } + lappend result -groups $items + } + } + if {[min_os_version 6] && $opts(logonsessionsid)} { + # Only possible on Vista+ + lappend result -logonsessionsid [lindex [GetTokenInformation $tok 28] 0 0] + set opts(logonsessionsid) 0; # So we don't try second method below + } + if {$opts(groupattrs) || $opts(logonsessionsid)} { + if {[info exists gtigroups]} { + set items {} + # First element of groups is user sid, skip it + foreach item [lrange $gtigroups 1 end] { + set gattrs [map_token_group_attr [lindex $item 1]] + if {$opts(groupattrs)} { + lappend items [lindex $item 0] $gattrs + } + if {$opts(logonsessionsid) && "logon_id" in $gattrs} { + set logonsessionsid [lindex $item 0] + } + } + if {$opts(groupattrs)} { + lappend result -groupattrs $items + } + } else { + set groupattrs [get_token_groups_and_attrs $tok] + if {$opts(logonsessionsid)} { + foreach {sid gattrs} $groupattrs { + if {"logon_id" in $gattrs} { + set logonsessionsid $sid + break + } + } + } + if {$opts(groupattrs)} { + lappend result -groupattrs $groupattrs + } + } + if {$opts(logonsessionsid)} { + if {[info exists logonsessionsid]} { + lappend result -logonsessionsid $logonsessionsid + } else { + error "No logon session id found in token" + } + } + } + if {$opts(restrictedgroups)} { + if {![info exists gtirestrictedgroups]} { + set gtirestrictedgroups [get_token_restricted_groups_and_attrs $tok] + } + set items {} + foreach item $gtirestrictedgroups { + lappend items [lookup_account_sid [lindex $item 0]] + } + lappend result -restrictedgroups $items + } + if {$opts(restrictedgroupattrs)} { + if {[info exists gtirestrictedgroups]} { + set items {} + foreach item $gtirestrictedgroups { + lappend items [lindex $item 0] [map_token_group_attr [lindex $item 1]] + } + lappend result -restrictedgroupattrs $items + } else { + lappend result -restrictedgroupattrs [get_token_restricted_groups_and_attrs $tok] + } + } + if {$opts(primarygroupsid)} { + lappend result -primarygroupsid [get_token_primary_group $tok] + } + if {$opts(primarygroup)} { + lappend result -primarygroup [get_token_primary_group $tok -name] + } + if {$opts(logonsession)} { + if {[info exists gtilogonsession]} { + lappend result -logonsession $gtilogonsession + } else { + array set stats [get_token_statistics $tok] + lappend result -logonsession $stats(authluid) + } + } + } + + return $result +} + +proc twapi::get_token_tssession {tok} { + return [GetTokenInformation $tok 12] +} + +# TBD - document and test +proc twapi::set_token_tssession {tok tssession} { + Twapi_SetTokenSessionId $tok $tssession + return +} + +# Procs that differ between Vista and prior versions +if {[twapi::min_os_version 6]} { + proc twapi::get_token_elevation {tok} { + set elevation [GetTokenInformation $tok 18]; #TokenElevationType + switch -exact -- $elevation { + 1 { set elevation default } + 2 { set elevation full } + 3 { set elevation limited } + } + return $elevation + } + + proc twapi::get_token_virtualization {tok} { + return [GetTokenInformation $tok 24]; # TokenVirtualizationEnabled + } + + proc twapi::set_token_virtualization {tok enabled} { + # tok must have TOKEN_ADJUST_DEFAULT access + Twapi_SetTokenVirtualizationEnabled $tok [expr {$enabled ? 1 : 0}] + } + + # Get the integrity level associated with a token + proc twapi::get_token_integrity {tok args} { + # TokenIntegrityLevel -> 25 + lassign [GetTokenInformation $tok 25] integrity attrs + if {$attrs != 96} { + # TBD - is this ok? + } + return [_sid_to_integrity $integrity {*}$args] + } + + # Get the integrity level associated with a token + proc twapi::set_token_integrity {tok integrity} { + # SE_GROUP_INTEGRITY attribute - 0x20 + Twapi_SetTokenIntegrityLevel $tok [list [_integrity_to_sid $integrity] 0x20] + } + + proc twapi::get_token_integrity_policy {tok} { + set policy [GetTokenInformation $tok 27]; #TokenMandatoryPolicy + set result {} + if {$policy & 1} { + lappend result no_write_up + } + if {$policy & 2} { + lappend result new_process_min + } + return $result + } + + + proc twapi::set_token_integrity_policy {tok args} { + set policy [_parse_symbolic_bitmask $args { + no_write_up 0x1 + new_process_min 0x2 + }] + + Twapi_SetTokenMandatoryPolicy $tok $policy + } +} else { + # Versions for pre-Vista + proc twapi::get_token_elevation {tok} { + # Older OS versions have no concept of elevation. + return "default" + } + + proc twapi::get_token_virtualization {tok} { + # Older OS versions have no concept of elevation. + return 0 + } + + proc twapi::set_token_virtualization {tok enabled} { + # Older OS versions have no concept of elevation, so only disable + # allowed + if {$enabled} { + error "Virtualization not available on this platform." + } + return + } + + # Get the integrity level associated with a token + proc twapi::get_token_integrity {tok args} { + # Older OS versions have no concept of elevation. + # For future consistency in label mapping, fall through to mapping + # below instead of directly returning mapped value + set integrity S-1-16-8192 + + return [_sid_to_integrity $integrity {*}$args] + } + + # Get the integrity level associated with a token + proc twapi::set_token_integrity {tok integrity} { + # Old platforms have a "default" of medium that cannot be changed. + if {[_integrity_to_sid $integrity] ne "S-1-16-8192"} { + error "Invalid integrity level value '$integrity' for this platform." + } + return + } + + proc twapi::get_token_integrity_policy {tok} { + # Old platforms - no integrity + return 0 + } + + proc twapi::set_token_integrity_policy {tok args} { + # Old platforms - no integrity + return 0 + } +} + +proc twapi::well_known_sid {sidname args} { + parseargs args { + {domainsid.arg {}} + } -maxleftover 0 -setvars + + return [CreateWellKnownSid [_map_well_known_sid_name $sidname] $domainsid] +} + +proc twapi::is_well_known_sid {sid sidname} { + return [IsWellKnownSid $sid [_map_well_known_sid_name $sidname]] +} + +# Get the user account associated with a token +proc twapi::get_token_user {tok args} { + + array set opts [parseargs args [list name]] + # TokenUser -> 1 + set user [lindex [GetTokenInformation $tok 1] 0] + if {$opts(name)} { + set user [lookup_account_sid $user] + } + return $user +} + +# Get the groups associated with a token +proc twapi::get_token_groups {tok args} { + array set opts [parseargs args [list name] -maxleftover 0] + + set groups [list ] + # TokenGroups -> 2 + foreach group [GetTokenInformation $tok 2] { + if {$opts(name)} { + lappend groups [lookup_account_sid [lindex $group 0]] + } else { + lappend groups [lindex $group 0] + } + } + + return $groups +} + +# Get the groups associated with a token along with their attributes +# These are returned as a flat list of the form "sid attrlist sid attrlist..." +# where the attrlist is a list of attributes +proc twapi::get_token_groups_and_attrs {tok} { + + set sids_and_attrs [list ] + # TokenGroups -> 2 + foreach {group} [GetTokenInformation $tok 2] { + lappend sids_and_attrs [lindex $group 0] [map_token_group_attr [lindex $group 1]] + } + + return $sids_and_attrs +} + +# Get the groups associated with a token along with their attributes +# These are returned as a flat list of the form "sid attrlist sid attrlist..." +# where the attrlist is a list of attributes +proc twapi::get_token_restricted_groups_and_attrs {tok} { + set sids_and_attrs [list ] + # TokenRestrictedGroups -> 11 + foreach {group} [GetTokenInformation $tok 11] { + lappend sids_and_attrs [lindex $group 0] [map_token_group_attr [lindex $group 1]] + } + + return $sids_and_attrs +} + + +# Get list of privileges that are currently enabled for the token +# If -all is specified, returns a list {enabled_list disabled_list} +proc twapi::get_token_privileges {tok args} { + + set all [expr {[lsearch -exact $args -all] >= 0}] + # TokenPrivileges -> 3 + set privs [_map_luids_and_attrs_to_privileges [GetTokenInformation $tok 3]] + if {$all} { + return $privs + } else { + return [lindex $privs 0] + } +} + +# Return true if the token has the given privilege +proc twapi::check_enabled_privileges {tok privlist args} { + set all_required [expr {[lsearch -exact $args "-any"] < 0}] + + set luid_attr_list [list ] + foreach priv $privlist { + lappend luid_attr_list [list [map_privilege_to_luid $priv] 0] + } + return [Twapi_PrivilegeCheck $tok $luid_attr_list $all_required] +} + + +# Enable specified privileges. Returns "" if the given privileges were +# already enabled, else returns the privileges that were modified +proc twapi::enable_privileges {privlist} { + variable my_process_handle + + # Get our process token + set tok [OpenProcessToken $my_process_handle 0x28]; # QUERY + ADJUST_PRIVS + trap { + return [enable_token_privileges $tok $privlist] + } finally { + close_token $tok + } +} + + +# Disable specified privileges. Returns "" if the given privileges were +# already enabled, else returns the privileges that were modified +proc twapi::disable_privileges {privlist} { + variable my_process_handle + + # Get our process token + set tok [OpenProcessToken $my_process_handle 0x28]; # QUERY + ADJUST_PRIVS + trap { + return [disable_token_privileges $tok $privlist] + } finally { + close_token $tok + } +} + + +# Execute the given script with the specified privileges. +# After the script completes, the original privileges are restored +proc twapi::eval_with_privileges {script privs args} { + array set opts [parseargs args {besteffort} -maxleftover 0] + + if {[catch {enable_privileges $privs} privs_to_disable]} { + if {! $opts(besteffort)} { + return -code error -errorinfo $::errorInfo \ + -errorcode $::errorCode $privs_to_disable + } + set privs_to_disable [list ] + } + + set code [catch {uplevel $script} result] + switch $code { + 0 { + disable_privileges $privs_to_disable + return $result + } + 1 { + # Save error info before calling disable_privileges + set erinfo $::errorInfo + set ercode $::errorCode + disable_privileges $privs_to_disable + return -code error -errorinfo $::errorInfo \ + -errorcode $::errorCode $result + } + default { + disable_privileges $privs_to_disable + return -code $code $result + } + } +} + + +# Get the privilege associated with a token and their attributes +proc twapi::get_token_privileges_and_attrs {tok} { + set privs_and_attrs [list ] + # TokenPrivileges -> 3 + foreach priv [GetTokenInformation $tok 3] { + lassign $priv luid attr + lappend privs_and_attrs [map_luid_to_privilege $luid -mapunknown] \ + [map_token_privilege_attr $attr] + } + + return $privs_and_attrs + +} + + +# Get the sid that will be used as the owner for objects created using this +# token. Returns name instead of sid if -name options specified +proc twapi::get_token_owner {tok args} { + # TokenOwner -> 4 + return [ _get_token_sid_field $tok 4 $args] +} + + +# Get the sid that will be used as the primary group for objects created using +# this token. Returns name instead of sid if -name options specified +proc twapi::get_token_primary_group {tok args} { + # TokenPrimaryGroup -> 5 + return [ _get_token_sid_field $tok 5 $args] +} + +proc twapi::get_token_default_dacl {tok} { + # TokenDefaultDacl -> 6 + return [GetTokenInformation $tok 6] +} + +proc twapi::get_token_origin {tok} { + # TokenOrigin -> 17 + return [GetTokenInformation $tok 17] +} + +# Return the source of an access token +proc twapi::get_token_source {tok} { + return [GetTokenInformation $tok 7]; # TokenSource +} + + +# Return the token type of an access token +proc twapi::get_token_type {tok} { + # TokenType -> 8 + set type [GetTokenInformation $tok 8] + if {$type == 1} { + return "primary" + } elseif {$type == 2} { + return "impersonation" + } else { + return $type + } +} + +# Return the token type of an access token +proc twapi::get_token_impersonation_level {tok} { + # TokenImpersonationLevel -> 9 + return [_map_impersonation_level [GetTokenInformation $tok 9]] +} + +# Return the linked token when a token is filtered +proc twapi::get_token_linked_token {tok} { + # TokenLinkedToken -> 19 + return [GetTokenInformation $tok 19] +} + +# Return token statistics +proc twapi::get_token_statistics {tok} { + array set stats {} + set labels {luid authluid expiration type impersonationlevel + dynamiccharged dynamicavailable groupcount + privilegecount modificationluid} + # TokenStatistics -> 10 + set statinfo [GetTokenInformation $tok 10] + foreach label $labels val $statinfo { + set stats($label) $val + } + set stats(type) [expr {$stats(type) == 1 ? "primary" : "impersonation"}] + set stats(impersonationlevel) [_map_impersonation_level $stats(impersonationlevel)] + + return [array get stats] +} + + +# Enable the privilege state of a token. Generates an error if +# the specified privileges do not exist in the token (either +# disabled or enabled), or cannot be adjusted +proc twapi::enable_token_privileges {tok privs} { + set luid_attrs [list] + foreach priv $privs { + # SE_PRIVILEGE_ENABLED -> 2 + lappend luid_attrs [list [map_privilege_to_luid $priv] 2] + } + + set privs [list ] + foreach {item} [Twapi_AdjustTokenPrivileges $tok 0 $luid_attrs] { + lappend privs [map_luid_to_privilege [lindex $item 0] -mapunknown] + } + return $privs + + + +} + +# Disable the privilege state of a token. Generates an error if +# the specified privileges do not exist in the token (either +# disabled or enabled), or cannot be adjusted +proc twapi::disable_token_privileges {tok privs} { + set luid_attrs [list] + foreach priv $privs { + lappend luid_attrs [list [map_privilege_to_luid $priv] 0] + } + + set privs [list ] + foreach {item} [Twapi_AdjustTokenPrivileges $tok 0 $luid_attrs] { + lappend privs [map_luid_to_privilege [lindex $item 0] -mapunknown] + } + return $privs +} + +# Disable all privs in a token +proc twapi::disable_all_token_privileges {tok} { + set privs [list ] + foreach {item} [Twapi_AdjustTokenPrivileges $tok 1 [list ]] { + lappend privs [map_luid_to_privilege [lindex $item 0] -mapunknown] + } + return $privs +} + + +# Map a privilege given as a LUID +proc twapi::map_luid_to_privilege {luid args} { + variable _luid_to_privilege_map + + array set opts [parseargs args [list system.arg mapunknown] -nulldefault] + + if {[dict exists $_luid_to_privilege_map $opts(system) $luid]} { + return [dict get $_luid_to_privilege_map $opts(system) $luid] + } + + # luid may in fact be a privilege name. Check for this + if {[is_valid_luid_syntax $luid]} { + trap { + set name [LookupPrivilegeName $opts(system) $luid] + dict set _luid_to_privilege_map $opts(system) $luid $name + } onerror {TWAPI_WIN32 1313} { + if {! $opts(mapunknown)} { + rethrow + } + set name "Privilege-$luid" + # Do not put in cache as privilege name might change? + } + } else { + # Not a valid LUID syntax. Check if it's a privilege name + if {[catch {map_privilege_to_luid $luid -system $opts(system)}]} { + error "Invalid LUID '$luid'" + } + return $luid; # $luid is itself a priv name + } + + return $name +} + + +# Map a privilege to a LUID +proc twapi::map_privilege_to_luid {priv args} { + variable _privilege_to_luid_map + + array set opts [parseargs args [list system.arg] -nulldefault] + + if {[dict exists $_privilege_to_luid_map $opts(system) $priv]} { + return [dict get $_privilege_to_luid_map $opts(system) $priv] + } + + # First check for privilege names we might have generated + if {[string match "Privilege-*" $priv]} { + set priv [string range $priv 10 end] + } + + # If already a LUID format, return as is, else look it up + if {[is_valid_luid_syntax $priv]} { + return $priv + } + + set luid [LookupPrivilegeValue $opts(system) $priv] + # This is an expensive call so stash it unless cache too big + if {[dict size $_privilege_to_luid_map] < 100} { + dict set _privilege_to_luid_map $opts(system) $priv $luid + } + + return $luid +} + + +# Return 1/0 if in LUID format +proc twapi::is_valid_luid_syntax {luid} { + return [regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$} $luid] +} + + +################################################################ +# Functions related to ACE's and ACL's + +# Create a new ACE +proc twapi::new_ace {type account rights args} { + array set opts [parseargs args { + {self.bool 1} + {recursecontainers.bool 0 2} + {recurseobjects.bool 0 1} + {recurseonelevelonly.bool 0 4} + {auditsuccess.bool 1 0x40} + {auditfailure.bool 1 0x80} + }] + + set sid [map_account_to_sid $account] + + set access_mask [_access_rights_to_mask $rights] + + switch -exact -- $type { + mandatory_label - + allow - + deny - + audit { + set typecode [_ace_type_symbol_to_code $type] + } + default { + error "Invalid or unsupported ACE type '$type'" + } + } + + set inherit_flags [expr {$opts(recursecontainers) | $opts(recurseobjects) | + $opts(recurseonelevelonly)}] + if {! $opts(self)} { + incr inherit_flags 8; #INHERIT_ONLY_ACE + } + + if {$type eq "audit"} { + set inherit_flags [expr {$inherit_flags | $opts(auditsuccess) | $opts(auditfailure)}] + } + + return [list $typecode $inherit_flags $access_mask $sid] +} + +# Get the ace type (allow, deny etc.) +proc twapi::get_ace_type {ace} { + return [_ace_type_code_to_symbol [lindex $ace 0]] +} + + +# Set the ace type (allow, deny etc.) +proc twapi::set_ace_type {ace type} { + return [lreplace $ace 0 0 [_ace_type_symbol_to_code $type]] +} + +# Get the access rights in an ACE +proc twapi::get_ace_rights {ace args} { + array set opts [parseargs args { + {type.arg ""} + resourcetype.arg + raw + } -maxleftover 0] + + if {$opts(raw)} { + return [format 0x%x [lindex $ace 2]] + } + + if {[lindex $ace 0] == 0x11} { + # MANDATORY_LABEL -> 0x11 + # Resource type is immaterial + return [_access_mask_to_rights [lindex $ace 2] mandatory_label] + } + + # Backward compatibility - in 2.x -type was documented instead + # of -resourcetype + if {[info exists opts(resourcetype)]} { + return [_access_mask_to_rights [lindex $ace 2] $opts(resourcetype)] + } else { + return [_access_mask_to_rights [lindex $ace 2] $opts(type)] + } +} + +# Set the access rights in an ACE +proc twapi::set_ace_rights {ace rights} { + return [lreplace $ace 2 2 [_access_rights_to_mask $rights]] +} + + +# Get the ACE sid +proc twapi::get_ace_sid {ace} { + return [lindex $ace 3] +} + +# Set the ACE sid +proc twapi::set_ace_sid {ace account} { + return [lreplace $ace 3 3 [map_account_to_sid $account]] +} + + +# Get audit flags - TBD document and test +proc twapi::get_ace_audit {ace} { + set audit {} + set mask [lindex $ace 1] + if {$mask & 0x40} { + lappend audit "success" + } + if {$mask & 0x80} { + lappend audit "failure" + } + return $audit +} + +# Get the inheritance options +proc twapi::get_ace_inheritance {ace} { + + set inherit_opts [list ] + set inherit_mask [lindex $ace 1] + + lappend inherit_opts -self \ + [expr {($inherit_mask & 8) == 0}] + lappend inherit_opts -recursecontainers \ + [expr {($inherit_mask & 2) != 0}] + lappend inherit_opts -recurseobjects \ + [expr {($inherit_mask & 1) != 0}] + lappend inherit_opts -recurseonelevelonly \ + [expr {($inherit_mask & 4) != 0}] + lappend inherit_opts -inherited \ + [expr {($inherit_mask & 16) != 0}] + + return $inherit_opts +} + +# Set the inheritance options. Unspecified options are not set +proc twapi::set_ace_inheritance {ace args} { + + array set opts [parseargs args { + self.bool + recursecontainers.bool + recurseobjects.bool + recurseonelevelonly.bool + }] + + set inherit_flags [lindex $ace 1] + if {[info exists opts(self)]} { + if {$opts(self)} { + resetbits inherit_flags 0x8; #INHERIT_ONLY_ACE -> 0x8 + } else { + setbits inherit_flags 0x8; #INHERIT_ONLY_ACE -> 0x8 + } + } + + foreach { + opt mask + } { + recursecontainers 2 + recurseobjects 1 + recurseonelevelonly 4 + } { + if {[info exists opts($opt)]} { + if {$opts($opt)} { + setbits inherit_flags $mask + } else { + resetbits inherit_flags $mask + } + } + } + + return [lreplace $ace 1 1 $inherit_flags] +} + + +# Sort ACE's in the standard recommended Win2K order +proc twapi::sort_aces {aces} { + + _init_ace_type_symbol_to_code_map + + foreach type [array names ::twapi::_ace_type_symbol_to_code_map] { + set direct_aces($type) [list ] + set inherited_aces($type) [list ] + } + + # Sort order is as follows: all direct (non-inherited) ACEs come + # before all inherited ACEs. Within these groups, the order should be + # access denied ACEs, access denied ACEs for objects/properties, + # access allowed ACEs, access allowed ACEs for objects/properties, + # TBD - check this ordering against http://msdn.microsoft.com/en-us/library/windows/desktop/aa379298%28v=vs.85%29.aspx + foreach ace $aces { + set type [get_ace_type $ace] + # INHERITED_ACE -> 0x10 + if {[lindex $ace 1] & 0x10} { + lappend inherited_aces($type) $ace + } else { + lappend direct_aces($type) $ace + } + } + + # TBD - check this order ACE's, especially audit and mandatory label + return [concat \ + $direct_aces(deny) \ + $direct_aces(deny_object) \ + $direct_aces(deny_callback) \ + $direct_aces(deny_callback_object) \ + $direct_aces(allow) \ + $direct_aces(allow_object) \ + $direct_aces(allow_compound) \ + $direct_aces(allow_callback) \ + $direct_aces(allow_callback_object) \ + $direct_aces(audit) \ + $direct_aces(audit_object) \ + $direct_aces(audit_callback) \ + $direct_aces(audit_callback_object) \ + $direct_aces(mandatory_label) \ + $direct_aces(alarm) \ + $direct_aces(alarm_object) \ + $direct_aces(alarm_callback) \ + $direct_aces(alarm_callback_object) \ + $inherited_aces(deny) \ + $inherited_aces(deny_object) \ + $inherited_aces(deny_callback) \ + $inherited_aces(deny_callback_object) \ + $inherited_aces(allow) \ + $inherited_aces(allow_object) \ + $inherited_aces(allow_compound) \ + $inherited_aces(allow_callback) \ + $inherited_aces(allow_callback_object) \ + $inherited_aces(audit) \ + $inherited_aces(audit_object) \ + $inherited_aces(audit_callback) \ + $inherited_aces(audit_callback_object) \ + $inherited_aces(mandatory_label) \ + $inherited_aces(alarm) \ + $inherited_aces(alarm_object) \ + $inherited_aces(alarm_callback) \ + $inherited_aces(alarm_callback_object)] +} + +# Pretty print an ACL +proc twapi::get_acl_text {acl args} { + array set opts [parseargs args { + {resourcetype.arg raw} + {offset.arg ""} + } -maxleftover 0] + + set count 0 + set result "$opts(offset)Rev: [get_acl_rev $acl]\n" + foreach ace [get_acl_aces $acl] { + append result "$opts(offset)ACE #[incr count]\n" + append result [get_ace_text $ace -offset "$opts(offset) " -resourcetype $opts(resourcetype)] + } + return $result +} + +# Pretty print an ACE +proc twapi::get_ace_text {ace args} { + array set opts [parseargs args { + {resourcetype.arg raw} + {offset.arg ""} + } -maxleftover 0] + + if {$ace eq "null"} { + return "Null" + } + + set offset $opts(offset) + array set bools {0 No 1 Yes} + array set inherit_flags [get_ace_inheritance $ace] + append inherit_text "${offset}Inherited: $bools($inherit_flags(-inherited))\n" + append inherit_text "${offset}Include self: $bools($inherit_flags(-self))\n" + append inherit_text "${offset}Recurse containers: $bools($inherit_flags(-recursecontainers))\n" + append inherit_text "${offset}Recurse objects: $bools($inherit_flags(-recurseobjects))\n" + append inherit_text "${offset}Recurse single level only: $bools($inherit_flags(-recurseonelevelonly))\n" + + set rights [get_ace_rights $ace -type $opts(resourcetype)] + if {[lsearch -glob $rights *_all_access] >= 0} { + set rights "All" + } else { + set rights [join $rights ", "] + } + + set acetype [get_ace_type $ace] + append result "${offset}Type: [string totitle $acetype]\n" + set user [get_ace_sid $ace] + catch {append user " ([map_account_to_name [get_ace_sid $ace]])"} + append result "${offset}User: $user\n" + append result "${offset}Rights: $rights\n" + if {$acetype eq "audit"} { + append result "${offset}Audit conditions: [join [get_ace_audit $ace] {, }]\n" + } + append result $inherit_text + + return $result +} + +# Create a new ACL +proc twapi::new_acl {{aces ""}} { + # NOTE: we ALWAYS set aclrev to 2. This may not be correct for the + # supplied ACEs but that's ok. The C level code calculates the correct + # acl rev level and overwrites anyways. + return [list 2 $aces] +} + +# Creates an ACL that gives the specified rights to specified trustees +proc twapi::new_restricted_dacl {accounts rights args} { + set access_mask [_access_rights_to_mask $rights] + + set aces {} + foreach account $accounts { + lappend aces [new_ace allow $account $access_mask {*}$args] + } + + return [new_acl $aces] + +} + +# Return the list of ACE's in an ACL +proc twapi::get_acl_aces {acl} { + return [lindex $acl 1] +} + +# Set the ACE's in an ACL +proc twapi::set_acl_aces {acl aces} { + # Note, we call new_acl since when ACEs change, the rev may also change + return [new_acl $aces] +} + +# Append to the ACE's in an ACL +proc twapi::append_acl_aces {acl aces} { + return [set_acl_aces $acl [concat [get_acl_aces $acl] $aces]] +} + +# Prepend to the ACE's in an ACL +proc twapi::prepend_acl_aces {acl aces} { + return [set_acl_aces $acl [concat $aces [get_acl_aces $acl]]] +} + +# Arrange the ACE's in an ACL in a standard order +proc twapi::sort_acl_aces {acl} { + return [set_acl_aces $acl [sort_aces [get_acl_aces $acl]]] +} + +# Return the ACL revision of an ACL +proc twapi::get_acl_rev {acl} { + return [lindex $acl 0] +} + + +# Create a new security descriptor +proc twapi::new_security_descriptor {args} { + array set opts [parseargs args { + owner.arg + group.arg + dacl.arg + sacl.arg + } -maxleftover 0] + + set secd [Twapi_InitializeSecurityDescriptor] + + # TBD - where are the control bits set? THe set_security_descrip[tor_* + # don't seem to set the control bits for related fields either. + foreach field {owner group dacl sacl} { + if {[info exists opts($field)]} { + set secd [set_security_descriptor_$field $secd $opts($field)] + } + } + + return $secd +} + +# Return the control bits in a security descriptor +# TBD - update for new Windows versions +proc twapi::get_security_descriptor_control {secd} { + if {[_null_secd $secd]} { + error "Attempt to get control field from NULL security descriptor." + } + + set control [lindex $secd 0] + + set retval [list ] + if {$control & 0x0001} { + lappend retval owner_defaulted + } + if {$control & 0x0002} { + lappend retval group_defaulted + } + if {$control & 0x0004} { + lappend retval dacl_present + } + if {$control & 0x0008} { + lappend retval dacl_defaulted + } + if {$control & 0x0010} { + lappend retval sacl_present + } + if {$control & 0x0020} { + lappend retval sacl_defaulted + } + if {$control & 0x0100} { + # Not documented because should not actually appear when reading a secd + lappend retval dacl_auto_inherit_req + } + if {$control & 0x0200} { + # Not documented because should not actually appear when reading a secd + lappend retval sacl_auto_inherit_req + } + if {$control & 0x0400} { + lappend retval dacl_auto_inherited + } + if {$control & 0x0800} { + lappend retval sacl_auto_inherited + } + if {$control & 0x1000} { + lappend retval dacl_protected + } + if {$control & 0x2000} { + lappend retval sacl_protected + } + if {$control & 0x4000} { + lappend retval rm_control_valid + } + if {$control & 0x8000} { + lappend retval self_relative + } + return $retval +} + +# Return the owner in a security descriptor +proc twapi::get_security_descriptor_owner {secd} { + if {[_null_secd $secd]} { + win32_error 87 "Attempt to get owner field from NULL security descriptor." + } + return [lindex $secd 1] +} + +# Set the owner in a security descriptor +proc twapi::set_security_descriptor_owner {secd account {defaulted 0}} { + if {[_null_secd $secd]} { + set secd [new_security_descriptor] + } + lassign $secd control - group dacl sacl + set sid [map_account_to_sid $account] + if {$defaulted} { + set control [expr {$control | 0x1}]; # SE_OWNER_DEFAULTED + } else { + set control [expr {$control & ~0x1}]; # ! SE_OWNER_DEFAULTED + } + return [list $control $sid $group $dacl $sacl] +} + +# Return the group in a security descriptor +proc twapi::get_security_descriptor_group {secd} { + if {[_null_secd $secd]} { + win32_error 87 "Attempt to get group field from NULL security descriptor." + } + return [lindex $secd 2] +} + +# Set the group in a security descriptor +proc twapi::set_security_descriptor_group {secd account {defaulted 0}} { + if {[_null_secd $secd]} { + set secd [new_security_descriptor] + } + lassign $secd control owner - dacl sacl + set sid [map_account_to_sid $account] + if {$defaulted} { + set control [expr {$control | 0x2}]; # SE_GROUP_DEFAULTED + } else { + set control [expr {$control & ~0x2}]; # ! SE_GROUP_DEFAULTED + } + return [list $control $owner $sid $dacl $sacl] +} + +# Return the DACL in a security descriptor +proc twapi::get_security_descriptor_dacl {secd} { + if {[_null_secd $secd]} { + win32_error 87 "Attempt to get DACL field from NULL security descriptor." + } + return [lindex $secd 3] +} + +# Set the dacl in a security descriptor +proc twapi::set_security_descriptor_dacl {secd acl {defaulted 0}} { + if {![_is_valid_acl $acl]} { + error "Invalid ACL <$acl>." + } + if {[_null_secd $secd]} { + set secd [new_security_descriptor] + } + lassign $secd control owner group - sacl + if {$acl eq "null"} { + set control [expr {$control & ~0x4}]; # ! SE_DACL_PRESENT + } else { + set control [expr {$control | 0x4}]; # SE_DACL_PRESENT + } + if {$defaulted} { + set control [expr {$control | 0x8}]; # SE_DACL_DEFAULTED + } else { + set control [expr {$control & ~0x8}]; # ! SE_DACL_DEFAULTED + } + return [list $control $owner $group $acl $sacl] +} + +proc twapi::protect_security_descriptor_dacl {secd} { + lassign $secd control owner group dacl sacl + set control [expr {$control | 0x1000}]; # SE_DACL_PROTECTED + return [list $control $owner $group $dacl $sacl] +} + +# Return the SACL in a security descriptor +proc twapi::get_security_descriptor_sacl {secd} { + if {[_null_secd $secd]} { + win32_error 87 "Attempt to get SACL field from NULL security descriptor." + } + return [lindex $secd 4] +} + +# Set the sacl in a security descriptor +proc twapi::set_security_descriptor_sacl {secd acl {defaulted 0}} { + if {![_is_valid_acl $acl]} { + error "Invalid ACL <$acl>." + } + if {[_null_secd $secd]} { + set secd [new_security_descriptor] + } + lassign $secd control owner group dacl - + if {$acl eq "null"} { + set control [expr {$control & ~0x10}]; # ! SE_SACL_PRESENT + } else { + set control [expr {$control | 0x10}]; # SE_SACL_PRESENT + } + if {$defaulted} { + set control [expr {$control | 0x20}]; # SE_SACL_DEFAULTED + } else { + set control [expr {$control & ~0x20}]; # ! SE_SACL_DEFAULTED + } + return [list $control $owner $group $dacl $acl] +} + +# Get the specified security information for the given object +proc twapi::get_resource_security_descriptor {restype name args} { + + # -mandatory_label field is not documented. Should we ? TBD + array set opts [parseargs args { + owner + group + dacl + sacl + mandatory_label + all + handle + }] + + set wanted 0 + + # OWNER_SECURITY_INFORMATION 1 + # GROUP_SECURITY_INFORMATION 2 + # DACL_SECURITY_INFORMATION 4 + # SACL_SECURITY_INFORMATION 8 + foreach {field mask} {owner 1 group 2 dacl 4 sacl 8} { + if {$opts($field) || $opts(all)} { + incr wanted $mask; # Equivalent to OR operation + } + } + + # LABEL_SECURITY_INFORMATION 0x10 + if {[min_os_version 6]} { + if {$opts(mandatory_label) || $opts(all)} { + incr wanted 16; # OR with 0x10 + } + } + + # Note if no options specified, we ask for everything except + # SACL's which require special privileges + if {! $wanted} { + set wanted 0x7 + if {[min_os_version 6]} { + incr wanted 0x10 + } + } + + if {$opts(handle)} { + set restype [_map_resource_symbol_to_type $restype false] + if {$restype == 5} { + # GetSecurityInfo crashes if a handles is passed in for + # SE_LMSHARE (even erroneously). It expects a string name + # even though the prototype says HANDLE. Protect against this. + error "Share resource type (share or 5) cannot be used with -handle option" + } + set secd [GetSecurityInfo \ + [CastToHANDLE $name] \ + $restype \ + $wanted] + } else { + # GetNamedSecurityInfo seems to fail with a overlapped i/o + # in progress error under some conditions. If this happens + # try getting with resource-specific API's if possible. + trap { + set secd [GetNamedSecurityInfo \ + $name \ + [_map_resource_symbol_to_type $restype true] \ + $wanted] + } onerror {} { + # TBD - see what other resource-specific API's there are + if {$restype eq "share"} { + set secd [lindex [get_share_info $name -secd] 1] + } else { + # Throw the same error + rethrow + } + } + } + + return $secd +} + + +# Set the specified security information for the given object +# See http://search.cpan.org/src/TEVERETT/Win32-Security-0.50/README +# for a good discussion even though that applies to Perl +proc twapi::set_resource_security_descriptor {restype name secd args} { + + # PROTECTED_DACL_SECURITY_INFORMATION 0x80000000 + # PROTECTED_SACL_SECURITY_INFORMATION 0x40000000 + # UNPROTECTED_DACL_SECURITY_INFORMATION 0x20000000 + # UNPROTECTED_SACL_SECURITY_INFORMATION 0x10000000 + array set opts [parseargs args { + all + handle + owner + group + dacl + sacl + mandatory_label + {protect_dacl {} 0x80000000} + {unprotect_dacl {} 0x20000000} + {protect_sacl {} 0x40000000} + {unprotect_sacl {} 0x10000000} + }] + + + if {![min_os_version 6]} { + if {$opts(mandatory_label)} { + error "Option -mandatory_label not supported by this version of Windows" + } + } + + if {$opts(protect_dacl) && $opts(unprotect_dacl)} { + error "Cannot specify both -protect_dacl and -unprotect_dacl." + } + + if {$opts(protect_sacl) && $opts(unprotect_sacl)} { + error "Cannot specify both -protect_sacl and -unprotect_sacl." + } + + set mask [expr {$opts(protect_dacl) | $opts(unprotect_dacl) | + $opts(protect_sacl) | $opts(unprotect_sacl)}] + + if {$opts(owner) || $opts(all)} { + set opts(owner) [get_security_descriptor_owner $secd] + setbits mask 1; # OWNER_SECURITY_INFORMATION + } else { + set opts(owner) "" + } + + if {$opts(group) || $opts(all)} { + set opts(group) [get_security_descriptor_group $secd] + setbits mask 2; # GROUP_SECURITY_INFORMATION + } else { + set opts(group) "" + } + + if {$opts(dacl) || $opts(all)} { + set opts(dacl) [get_security_descriptor_dacl $secd] + setbits mask 4; # DACL_SECURITY_INFORMATION + } else { + set opts(dacl) null + } + + if {$opts(sacl) || $opts(mandatory_label) || $opts(all)} { + set sacl [get_security_descriptor_sacl $secd] + if {$opts(sacl) || $opts(all)} { + setbits mask 0x8; # SACL_SECURITY_INFORMATION + } + if {[min_os_version 6]} { + if {$opts(mandatory_label) || $opts(all)} { + setbits mask 0x10; # LABEL_SECURITY_INFORMATION + } + } + set opts(sacl) $sacl + } else { + set opts(sacl) null + } + + if {$mask == 0} { + error "Must specify at least one of the options -all, -dacl, -sacl, -owner, -group or -mandatory_label" + } + + if {$opts(handle)} { + set restype [_map_resource_symbol_to_type $restype false] + if {$restype == 5} { + # GetSecurityInfo crashes if a handles is passed in for + # SE_LMSHARE (even erroneously). It expects a string name + # even though the prototype says HANDLE. Protect against this. + error "Share resource type (share or 5) cannot be used with -handle option" + } + + SetSecurityInfo \ + [CastToHANDLE $name] \ + [_map_resource_symbol_to_type $restype false] \ + $mask \ + $opts(owner) \ + $opts(group) \ + $opts(dacl) \ + $opts(sacl) + } else { + SetNamedSecurityInfo \ + $name \ + [_map_resource_symbol_to_type $restype true] \ + $mask \ + $opts(owner) \ + $opts(group) \ + $opts(dacl) \ + $opts(sacl) + } +} + +# Get integrity level from a security descriptor +proc twapi::get_security_descriptor_integrity {secd args} { + if {[min_os_version 6]} { + foreach ace [get_acl_aces [get_security_descriptor_sacl $secd]] { + if {[get_ace_type $ace] eq "mandatory_label"} { + if {! [dict get [get_ace_inheritance $ace] -self]} continue; # Does not apply to itself + set integrity [_sid_to_integrity [get_ace_sid $ace] {*}$args] + set rights [get_ace_rights $ace -resourcetype mandatory_label] + return [list $integrity $rights] + } + } + } + return {} +} + +# Get integrity level for a resource +proc twapi::get_resource_integrity {restype name args} { + # Note label and raw options are simply passed on + + if {![min_os_version 6]} { + return "" + } + set saved_args $args + array set opts [parseargs args { + label + raw + handle + }] + + if {$opts(handle)} { + set secd [get_resource_security_descriptor $restype $name -mandatory_label -handle] + } else { + set secd [get_resource_security_descriptor $restype $name -mandatory_label] + } + + return [get_security_descriptor_integrity $secd {*}$saved_args] +} + + +proc twapi::set_security_descriptor_integrity {secd integrity rights args} { + # Not clear from docs whether this can + # be done without interfering with SACL fields. Nevertheless + # we provide this proc because we might want to set the + # integrity level on new objects create thru CreateFile etc. + # TBD - need to test under vista and win 7 + + array set opts [parseargs args { + {recursecontainers.bool 0} + {recurseobjects.bool 0} + } -maxleftover 0] + + # We preserve any non-integrity aces in the sacl. + set sacl [get_security_descriptor_sacl $secd] + set aces {} + foreach ace [get_acl_aces $sacl] { + if {[get_ace_type $ace] ne "mandatory_label"} { + lappend aces $ace + } + } + + # Now create and attach an integrity ace. Note placement does not + # matter + lappend aces [new_ace mandatory_label \ + [_integrity_to_sid $integrity] \ + [_access_rights_to_mask $rights] \ + -self 1 \ + -recursecontainers $opts(recursecontainers) \ + -recurseobjects $opts(recurseobjects)] + + return [set_security_descriptor_sacl $secd [new_acl $aces]] +} + +proc twapi::set_resource_integrity {restype name integrity rights args} { + array set opts [parseargs args { + {recursecontainers.bool 0} + {recurseobjects.bool 0} + handle + } -maxleftover 0] + + set secd [set_security_descriptor_integrity \ + [new_security_descriptor] \ + $integrity \ + $rights \ + -recurseobjects $opts(recurseobjects) \ + -recursecontainers $opts(recursecontainers)] + + if {$opts(handle)} { + set_resource_security_descriptor $restype $name $secd -mandatory_label -handle + } else { + set_resource_security_descriptor $restype $name $secd -mandatory_label + } +} + + +# Convert a security descriptor to SDDL format +proc twapi::security_descriptor_to_sddl {secd} { + return [twapi::ConvertSecurityDescriptorToStringSecurityDescriptor $secd 1 0x1f] +} + +# Convert SDDL to a security descriptor +proc twapi::sddl_to_security_descriptor {sddl} { + return [twapi::ConvertStringSecurityDescriptorToSecurityDescriptor $sddl 1] +} + +# Return the text for a security descriptor +proc twapi::get_security_descriptor_text {secd args} { + if {[_null_secd $secd]} { + return "null" + } + + array set opts [parseargs args { + {resourcetype.arg raw} + } -maxleftover 0] + + append result "Flags:\t[get_security_descriptor_control $secd]\n" + set name [get_security_descriptor_owner $secd] + if {$name eq ""} { + set name Undefined + } else { + catch {set name [map_account_to_name $name]} + } + append result "Owner:\t$name\n" + set name [get_security_descriptor_group $secd] + if {$name eq ""} { + set name Undefined + } else { + catch {set name [map_account_to_name $name]} + } + append result "Group:\t$name\n" + + if {0} { + set acl [get_security_descriptor_dacl $secd] + append result "DACL Rev: [get_acl_rev $acl]\n" + set index 0 + foreach ace [get_acl_aces $acl] { + append result "\tDACL Entry [incr index]\n" + append result "[get_ace_text $ace -offset "\t " -resourcetype $opts(resourcetype)]" + } + set acl [get_security_descriptor_sacl $secd] + append result "SACL Rev: [get_acl_rev $acl]\n" + set index 0 + foreach ace [get_acl_aces $acl] { + append result "\tSACL Entry $index\n" + append result [get_ace_text $ace -offset "\t " -resourcetype $opts(resourcetype)] + } + } else { + append result "DACL:\n" + append result [get_acl_text [get_security_descriptor_dacl $secd] -offset " " -resourcetype $opts(resourcetype)] + append result "SACL:\n" + append result [get_acl_text [get_security_descriptor_sacl $secd] -offset " " -resourcetype $opts(resourcetype)] + } + + return $result +} + + +# Log off +proc twapi::logoff {args} { + array set opts [parseargs args { + {force {} 0x4} + {forceifhung {} 0x10} + } -maxleftover 0] + ExitWindowsEx [expr {$opts(force) | $opts(forceifhung)}] 0 +} + +# Lock the workstation +proc twapi::lock_workstation {} { + LockWorkStation +} + + +# Get a new LUID +proc twapi::new_luid {} { + return [AllocateLocallyUniqueId] +} + + +# Get the description of a privilege +proc twapi::get_privilege_description {priv} { + if {[catch {LookupPrivilegeDisplayName "" $priv} desc]} { + # The above function will only return descriptions for + # privileges, not account rights. Hard code descriptions + # for some account rights + set desc [dict* { + SeBatchLogonRight "Log on as a batch job" + SeDenyBatchLogonRight "Deny logon as a batch job" + SeDenyInteractiveLogonRight "Deny interactive logon" + SeDenyNetworkLogonRight "Deny access to this computer from the network" + SeRemoteInteractiveLogonRight "Remote interactive logon" + SeDenyRemoteInteractiveLogonRight "Deny interactive remote logon" + SeDenyServiceLogonRight "Deny logon as a service" + SeInteractiveLogonRight "Log on locally" + SeNetworkLogonRight "Access this computer from the network" + SeServiceLogonRight "Log on as a service" + } $priv] + } + return $desc +} + + + +# For backward compatibility, emulate GetUserName using GetUserNameEx +proc twapi::GetUserName {} { + return [file tail [GetUserNameEx 2]] +} + + +################################################################ +# Utility and helper functions + + + +# Returns an sid field from a token +proc twapi::_get_token_sid_field {tok field options} { + array set opts [parseargs options {name}] + set owner [GetTokenInformation $tok $field] + if {$opts(name)} { + set owner [lookup_account_sid $owner] + } + return $owner +} + +# Map token group attributes +# TBD - write a test for this +proc twapi::map_token_group_attr {attr} { + # SE_GROUP_MANDATORY 0x00000001 + # SE_GROUP_ENABLED_BY_DEFAULT 0x00000002 + # SE_GROUP_ENABLED 0x00000004 + # SE_GROUP_OWNER 0x00000008 + # SE_GROUP_USE_FOR_DENY_ONLY 0x00000010 + # SE_GROUP_LOGON_ID 0xC0000000 + # SE_GROUP_RESOURCE 0x20000000 + # SE_GROUP_INTEGRITY 0x00000020 + # SE_GROUP_INTEGRITY_ENABLED 0x00000040 + + return [_make_symbolic_bitmask $attr { + mandatory 0x00000001 + enabled_by_default 0x00000002 + enabled 0x00000004 + owner 0x00000008 + use_for_deny_only 0x00000010 + logon_id 0xC0000000 + resource 0x20000000 + integrity 0x00000020 + integrity_enabled 0x00000040 + }] +} + +# Map token privilege attributes +# TBD - write a test for this +proc twapi::map_token_privilege_attr {attr} { + # SE_PRIVILEGE_ENABLED_BY_DEFAULT 0x00000001 + # SE_PRIVILEGE_ENABLED 0x00000002 + # SE_PRIVILEGE_USED_FOR_ACCESS 0x80000000 + + return [_make_symbolic_bitmask $attr { + enabled_by_default 0x00000001 + enabled 0x00000002 + used_for_access 0x80000000 + }] +} + + + +# Map an ace type symbol (eg. allow) to the underlying ACE type code +proc twapi::_ace_type_symbol_to_code {type} { + _init_ace_type_symbol_to_code_map + return $::twapi::_ace_type_symbol_to_code_map($type) +} + + +# Map an ace type code to an ACE type symbol +proc twapi::_ace_type_code_to_symbol {type} { + _init_ace_type_symbol_to_code_map + return $::twapi::_ace_type_code_to_symbol_map($type) +} + + +# Init the arrays used for mapping ACE type symbols to codes and back +proc twapi::_init_ace_type_symbol_to_code_map {} { + + if {[info exists ::twapi::_ace_type_symbol_to_code_map]} { + return + } + + # ACCESS_ALLOWED_ACE_TYPE 0x0 + # ACCESS_DENIED_ACE_TYPE 0x1 + # SYSTEM_AUDIT_ACE_TYPE 0x2 + # SYSTEM_ALARM_ACE_TYPE 0x3 + # ACCESS_ALLOWED_COMPOUND_ACE_TYPE 0x4 + # ACCESS_ALLOWED_OBJECT_ACE_TYPE 0x5 + # ACCESS_DENIED_OBJECT_ACE_TYPE 0x6 + # SYSTEM_AUDIT_OBJECT_ACE_TYPE 0x7 + # SYSTEM_ALARM_OBJECT_ACE_TYPE 0x8 + # ACCESS_ALLOWED_CALLBACK_ACE_TYPE 0x9 + # ACCESS_DENIED_CALLBACK_ACE_TYPE 0xA + # ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE 0xB + # ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE 0xC + # SYSTEM_AUDIT_CALLBACK_ACE_TYPE 0xD + # SYSTEM_ALARM_CALLBACK_ACE_TYPE 0xE + # SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE 0xF + # SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE 0x10 + # SYSTEM_MANDATORY_LABEL_ACE_TYPE 0x11 + + # Define the array. + array set ::twapi::_ace_type_symbol_to_code_map { + allow 0 deny 1 audit 2 alarm 3 allow_compound 4 + allow_object 5 deny_object 6 audit_object 7 + alarm_object 8 allow_callback 9 deny_callback 10 + allow_callback_object 11 deny_callback_object 12 + audit_callback 13 alarm_callback 14 audit_callback_object 15 + alarm_callback_object 16 mandatory_label 17 + } + + # Now define the array in the other direction + foreach {sym code} [array get ::twapi::_ace_type_symbol_to_code_map] { + set ::twapi::_ace_type_code_to_symbol_map($code) $sym + } +} + +# Map a resource symbol type to value +proc twapi::_map_resource_symbol_to_type {sym {named true}} { + if {[string is integer -strict $sym]} { + return $sym + } + + # Note "window" is not here because window stations and desktops + # do not have unique names and cannot be used with Get/SetNamedSecurityInfo + switch -exact -- $sym { + file { return 1 } + service { return 2 } + printer { return 3 } + registry { return 4 } + share { return 5 } + kernelobj { return 6 } + } + if {$named} { + error "Resource type '$sym' not valid for named resources." + } + + switch -exact -- $sym { + windowstation { return 7 } + directoryservice { return 8 } + directoryserviceall { return 9 } + providerdefined { return 10 } + wmiguid { return 11 } + registrywow6432key { return 12 } + } + + error "Resource type '$sym' not valid" +} + +# Valid LUID syntax +proc twapi::_is_valid_luid_syntax luid { + return [regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$} $luid] +} + + +# Delete rights for an account +proc twapi::_delete_rights {account system} { + # Remove the user from the LSA rights database. Ignore any errors + catch { + remove_account_rights $account {} -all -system $system + + # On Win2k SP1 and SP2, we need to delay a bit for notifications + # to complete before deleting the account. + # See http://support.microsoft.com/?id=316827 + lassign [get_os_version] major minor sp dontcare + if {($major == 5) && ($minor == 0) && ($sp < 3)} { + after 1000 + } + } +} + + +# Get a token for a user +proc twapi::open_user_token {username password args} { + + array set opts [parseargs args { + domain.arg + {type.arg batch {interactive network batch service unlock network_cleartext new_credentials}} + {provider.arg default {default winnt35 winnt40 winnt50}} + } -nulldefault] + + # LOGON32_LOGON_INTERACTIVE 2 + # LOGON32_LOGON_NETWORK 3 + # LOGON32_LOGON_BATCH 4 + # LOGON32_LOGON_SERVICE 5 + # LOGON32_LOGON_UNLOCK 7 + # LOGON32_LOGON_NETWORK_CLEARTEXT 8 + # LOGON32_LOGON_NEW_CREDENTIALS 9 + set type [dict get {interactive 2 network 3 batch 4 service 5 + unlock 7 network_cleartext 8 new_credentials 9} $opts(type)] + + # LOGON32_PROVIDER_DEFAULT 0 + # LOGON32_PROVIDER_WINNT35 1 + # LOGON32_PROVIDER_WINNT40 2 + # LOGON32_PROVIDER_WINNT50 3 + set provider [dict get {default 0 winnt35 1 winnt40 2 winnt50 3} $opts(provider)] + + # If username is of the form user@domain, then domain must not be specified + # If username is not of the form user@domain, then domain is set to "." + # if it is empty + if {[regexp {^([^@]+)@(.+)} $username dummy user domain]} { + if {[string length $opts(domain)] != 0} { + error "The -domain option must not be specified when the username is in UPN format (user@domain)" + } + } else { + if {[string length $opts(domain)] == 0} { + set opts(domain) "." + } + } + + return [LogonUser $username $opts(domain) $password $type $provider] +} + + +# Impersonate a user given a token +proc twapi::impersonate_token {token} { + ImpersonateLoggedOnUser $token +} + + +# Impersonate a user +proc twapi::impersonate_user {args} { + set token [open_user_token {*}$args] + trap { + impersonate_token $token + } finally { + close_token $token + } +} + +# Impersonate self +proc twapi::impersonate_self {level} { + switch -exact -- $level { + anonymous { set level 0 } + identification { set level 1 } + impersonation { set level 2 } + delegation { set level 3 } + default { + error "Invalid impersonation level $level" + } + } + ImpersonateSelf $level +} + +# Set a thread token - currently only for current thread +proc twapi::set_thread_token {token} { + SetThreadToken NULL $token +} + +# Reset a thread token - currently only for current thread +proc twapi::reset_thread_token {} { + SetThreadToken NULL NULL +} + +proc twapi::_cred_cook {cred} { + set rec [twine {flags type target comment lastwritten credblob persist attributes targetalias username} $cred] + dict with rec { + set type [dict* { + 1 generic 2 domain_password 3 domain_certificate 4 domain_visible_password 5 generic_certificate 6 domain_extended} $type] + set persist [dict* { + 1 session 2 local_machine 3 enterprise + } $persist] + } + return $rec +} + +proc twapi::credentials {{pattern {}}} { + trap { + set raw [CredEnumerate $pattern 0] + } onerror {TWAPI_WIN32 1168} { + # Not found / no entries + return {} + } + + return [lmap cred $raw { _cred_cook $cred }] +} + +proc twapi::cred_delete {target {type generic}} { + if {[string is integer -strict $type]} { + set type_flags $type + } else { + set type_flags [dict get { + generic 1 + domain_password 2 + domain_certificate 3 + domain_visible_password 4 + generic_certificate 5 + domain_extended 6 + } $type] + } + CredDelete $target $type_flags 0 + return +} + +proc twapi::cred_get {target {type generic}} { + if {[string is integer -strict $type]} { + set type_flags $type + } else { + set type_flags [dict get { + generic 1 + domain_password 2 + domain_certificate 3 + domain_visible_password 4 + generic_certificate 5 + domain_extended 6 + } $type] + } + return [_cred_cook [CredRead $target $type_flags 0]] +} + + +# TBD - document after implementing AuditQuerySystemPolicy and friends +# for Vista & later +proc twapi::get_audit_policy {lsah} { + lassign [LsaQueryInformationPolicy $lsah 2] enabled audit_masks + set settings {} + foreach name { + system logon object_access privilege_use detailed_tracking + policy_change account_management directory_service_access + account_logon + } mask $audit_masks { + # Copied from the Perl Win32 book. + set setting {} + if {$mask == 0 || ($mask & 4)} { + set setting {} + } elseif {$mask & 3} { + if {$mask & 1} { lappend setting log_on_success } + if {$mask & 2} { lappend setting log_on_failure } + } else { + error "Unexpected audit mask value $mask" + } + lappend settings $name $setting + } + + return [list $enabled $settings] +} + + +# TBD - document after implementing AuditQuerySystemPolicy and friends +# for Vista & later +proc twapi::set_audit_policy {lsah enable settings} { + set audit_masks {} + # NOTE: the order here MUST match the enum definition for + # POLICY_AUDIT_EVENT_TYPE (see SDK docs) + foreach name { + system logon object_access privilege_use detailed_tracking + policy_change account_management directory_service_access + account_logon + } { + set mask 0; # POLICY_AUDIT_EVENT_UNCHANGED + if {[dict exists $settings $name]} { + set setting [dict get $settings $name] + # 4 -> POLICY_AUDIT_EVENT_NONE resets existing FAILURE|SUCCESS + set mask 4 + if {"log_on_success" in $setting} { + set mask [expr {$mask | 1}]; # POLICY_AUDIT_EVENT_SUCCESS + } + if {"log_on_failure" in $setting} { + set mask [expr {$mask | 2}]; # POLICY_AUDIT_EVENT_FAILURE + } + } + lappend audit_masks $mask + } + + Twapi_LsaSetInformationPolicy_AuditEvents $lsah $enable $audit_masks +} + +# Returns true if null security descriptor +proc twapi::_null_secd {secd} { + if {[llength $secd] == 0} { + return 1 + } else { + return 0 + } +} + +# Returns true if a valid ACL +proc twapi::_is_valid_acl {acl} { + if {$acl eq "null"} { + return 1 + } else { + return [IsValidAcl $acl] + } +} + +# Returns true if a valid ACL +proc twapi::_is_valid_security_descriptor {secd} { + if {[_null_secd $secd]} { + return 1 + } else { + return [IsValidSecurityDescriptor $secd] + } +} + +# Maps a integrity SID to integer or label +proc twapi::_sid_to_integrity {sid args} { + # Note - to make it simpler for callers, additional options are ignored + array set opts [parseargs args { + label + raw + }] + + if {$opts(raw) && $opts(label)} { + error "Options -raw and -label may not be specified together." + } + + if {![string equal -length 7 S-1-16-* $sid]} { + error "Unexpected integrity level value '$sid' returned by GetTokenInformation." + } + + if {$opts(raw)} { + return $sid + } + + set integrity [string range $sid 7 end] + + if {! $opts(label)} { + # Return integer level + return $integrity + } + + # Map to a label + if {$integrity < 4096} { + return untrusted + } elseif {$integrity < 8192} { + return low + } elseif {$integrity < 8448} { + return medium + } elseif {$integrity < 12288} { + return mediumplus + } elseif {$integrity < 16384} { + return high + } else { + return system + } + +} + +proc twapi::_integrity_to_sid {integrity} { + # Integrity level must be either a number < 65536 or a valid string + # or a SID. Check for the first two and convert to SID. Anything else + # will be trapped by the actual call as an invalid format. + if {[string is integer -strict $integrity]} { + set integrity S-1-16-[format %d $integrity]; # In case in hex + } else { + switch -glob -- $integrity { + untrusted { set integrity S-1-16-0 } + low { set integrity S-1-16-4096 } + medium { set integrity S-1-16-8192 } + mediumplus { set integrity S-1-16-8448 } + high { set integrity S-1-16-12288 } + system { set integrity S-1-16-16384 } + S-1-16-* { + if {![string is integer -strict [string range $integrity 7 end]]} { + error "Invalid integrity level '$integrity'" + } + # Format in case level component was in hex/octal + set integrity S-1-16-[format %d [string range $integrity 7 end]] + } + default { + error "Invalid integrity level '$integrity'" + } + } + } + return $integrity +} + +proc twapi::_map_luids_and_attrs_to_privileges {luids_and_attrs} { + set enabled_privs [list ] + set disabled_privs [list ] + foreach item $luids_and_attrs { + set priv [map_luid_to_privilege [lindex $item 0] -mapunknown] + # SE_PRIVILEGE_ENABLED -> 0x2 + if {[lindex $item 1] & 2} { + lappend enabled_privs $priv + } else { + lappend disabled_privs $priv + } + } + + return [list $enabled_privs $disabled_privs] +} + +# Map impersonation level to symbol +proc twapi::_map_impersonation_level ilevel { + set map { + 0 anonymous + 1 identification + 2 impersonation + 3 delegation + } + if {[dict exists $map [incr ilevel 0]]} { + return [dict get $map $ilevel] + } else { + return $ilevel + } +} + +proc twapi::_map_well_known_sid_name {sidname} { + if {[string is integer -strict $sidname]} { + return $sidname + } + + set sidname [string tolower $sidname] + set sidname [dict* { + administrator accountadministrator + {cert publishers} accountcertadmins + {domain computers} accountcomputers + {domain controllers} accountcontrollers + {domain admins} accountdomainadmins + {domain guests} accountdomainguests + {domain users} accountdomainusers + {enterprise admins} accountenterpriseadmins + guest accountguest + krbtgt accountkrbtgt + {read-only domain controllers} accountreadonlycontrollers + {schema admins} accountschemaadmins + {anonymous logon} anonymous + {authenticated users} authenticateduser + batch batch + administrators builtinadministrators + {all application packages} builtinanypackage + {backup operators} builtinbackupoperators + {distributed com users} builtindcomusers + builtin builtindomain + {event log readers} builtineventlogreadersgroup + guests builtinguests + {performance log users} builtinperfloggingusers + {performance monitor users} builtinperfmonitoringusers + {power users} builtinpowerusers + {remote desktop users} builtinremotedesktopusers + replicator builtinreplicator + users builtinusers + {console logon} consolelogon + {creator group} creatorgroup + {creator group server} creatorgroupserver + {creator owner} creatorowner + {owner rights} creatorownerrights + {creator owner server} creatorownerserver + dialup dialup + {digest authentication} digestauthentication + {enterprise domain controllers} enterprisecontrollers + {enterprise read-only domain controllers beta} enterprisereadonlycontrollers + {high mandatory level} highlabel + interactive interactive + local local + {local service} localservice + system localsystem + {low mandatory level} lowlabel + {medium mandatory level} mediumlabel + {medium plus mandatory level} mediumpluslabel + network network + {network service} networkservice + {enterprise read-only domain controllers} newenterprisereadonlycontrollers + {ntlm authentication} ntlmauthentication + {null sid} null + proxy proxy + {remote interactive logon} remotelogonid + restricted restrictedcode + {schannel authentication} schannelauthentication + self self + service service + {system mandatory level} systemlabel + {terminal server user} terminalserver + {untrusted mandatory level} untrustedlabel + everyone world + {write restricted} writerestrictedcode + } $sidname] + + return [dict! { + null 0 + world 1 + local 2 + creatorowner 3 + creatorgroup 4 + creatorownerserver 5 + creatorgroupserver 6 + ntauthority 7 + dialup 8 + network 9 + batch 10 + interactive 11 + service 12 + anonymous 13 + proxy 14 + enterprisecontrollers 15 + self 16 + authenticateduser 17 + restrictedcode 18 + terminalserver 19 + remotelogonid 20 + logonids 21 + localsystem 22 + localservice 23 + networkservice 24 + builtindomain 25 + builtinadministrators 26 + builtinusers 27 + builtinguests 28 + builtinpowerusers 29 + builtinaccountoperators 30 + builtinsystemoperators 31 + builtinprintoperators 32 + builtinbackupoperators 33 + builtinreplicator 34 + builtinprewindows2000compatibleaccess 35 + builtinremotedesktopusers 36 + builtinnetworkconfigurationoperators 37 + accountadministrator 38 + accountguest 39 + accountkrbtgt 40 + accountdomainadmins 41 + accountdomainusers 42 + accountdomainguests 43 + accountcomputers 44 + accountcontrollers 45 + accountcertadmins 46 + accountschemaadmins 47 + accountenterpriseadmins 48 + accountpolicyadmins 49 + accountrasandiasservers 50 + ntlmauthentication 51 + digestauthentication 52 + schannelauthentication 53 + thisorganization 54 + otherorganization 55 + builtinincomingforesttrustbuilders 56 + builtinperfmonitoringusers 57 + builtinperfloggingusers 58 + builtinauthorizationaccess 59 + builtinterminalserverlicenseservers 60 + builtindcomusers 61 + builtiniusers 62 + iuser 63 + builtincryptooperators 64 + untrustedlabel 65 + lowlabel 66 + mediumlabel 67 + highlabel 68 + systemlabel 69 + writerestrictedcode 70 + creatorownerrights 71 + cacheableprincipalsgroup 72 + noncacheableprincipalsgroup 73 + enterprisereadonlycontrollers 74 + accountreadonlycontrollers 75 + builtineventlogreadersgroup 76 + newenterprisereadonlycontrollers 77 + builtincertsvcdcomaccessgroup 78 + mediumpluslabel 79 + locallogon 80 + consolelogon 81 + thisorganizationcertificate 82 + applicationpackageauthority 83 + builtinanypackage 84 + capabilityinternetclient 85 + capabilityinternetclientserver 86 + capabilityprivatenetworkclientserver 87 + capabilitypictureslibrary 88 + capabilityvideoslibrary 89 + capabilitymusiclibrary 90 + capabilitydocumentslibrary 91 + capabilitysharedusercertificates 92 + capabilityenterpriseauthentication 93 + capabilityremovablestorage 94 + } $sidname] +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/service.tcl b/src/vendorlib_tcl8/twapi-5.0b1/service.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/service.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/service.tcl index 649b480f..f21b790f 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/service.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/service.tcl @@ -1,1187 +1,1187 @@ -# -# Copyright (c) 2003-2007, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - # When the process hosts Windows services, service_state - # is used to keep state of each service. The variable - # is indexed by NAME,FIELD where NAME is the name - # of the service and FIELD is one of "state", "script", - # "checkpoint", "waithint", "exitcode", "servicecode", - # "seq", "seqack" - variable service_state - - # Map service state names to integers - variable service_state_values - array set service_state_values { - stopped 1 - start_pending 2 - stop_pending 3 - running 4 - continue_pending 5 - pause_pending 6 - paused 7 - } -} - - -# Return 1/0 depending on whether the given service exists -# $name may be either the internal or display name -proc twapi::service_exists {name args} { - array set opts [parseargs args {system.arg database.arg} -nulldefault] - # 0x00020000 -> STANDARD_RIGHTS_READ - set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] - - trap { - GetServiceKeyName $scm $name - set exists 1 - } onerror {TWAPI_WIN32 1060} { - # "no such service" error for internal name. - # Try display name - trap { - GetServiceDisplayName $scm $name - set exists 1 - } onerror {TWAPI_WIN32 1060} { - set exists 0 - } - } finally { - CloseServiceHandle $scm - } - - return $exists -} - - -# Create a service of the specified name -proc twapi::create_service {name command args} { - array set opts [parseargs args { - displayname.arg - {servicetype.arg win32_own_process {win32_own_process win32_share_process file_system_driver kernel_driver}} - {interactive.bool 0} - {starttype.arg auto_start {auto_start boot_start demand_start disabled system_start}} - {errorcontrol.arg normal {ignore normal severe critical}} - loadordergroup.arg - dependencies.arg - account.arg - password.arg - system.arg - database.arg - } -nulldefault] - - - if {[string length $opts(displayname)] == 0} { - set opts(displayname) $name - } - - if {[string length $command] == 0} { - error "The executable path must not be null when creating a service" - } - set opts(command) $command - - switch -exact -- $opts(servicetype) { - file_system_driver - - kernel_driver { - if {$opts(interactive)} { - error "Option -interactive cannot be specified when -servicetype is $opts(servicetype)." - } - } - default { - if {$opts(interactive) && [string length $opts(account)]} { - error "Option -interactive cannot be specified with the -account option as interactive services must run under the LocalSystem account." - } - if {[string equal $opts(starttype) "boot_start"] - || [string equal $opts(starttype) "system_start"]} { - error "Option -starttype value must be one of auto_start, demand_start or disabled when -servicetype is '$opts(servicetype)'." - } - } - } - - # Map keywords to integer values - set opts(servicetype) [_map_servicetype_sym $opts(servicetype)] - set opts(starttype) [_map_starttype_sym $opts(starttype)] - set opts(errorcontrol) [_map_errorcontrol_sym $opts(errorcontrol)] - - # If interactive, add the flag to the service type - if {$opts(interactive)} { - setbits opts(servicetype) 0x100; # SERVICE_INTERACTIVE_PROCESS - } - - # Ignore password if username not specified - if {[string length $opts(account)] == 0} { - set opts(password) "" - } else { - # If domain/system not specified, tack on ".\" for local system - if {[string first \\ $opts(account)] < 0} { - set opts(account) ".\\$opts(account)" - } - } - - # 2 -> SC_MANAGER_CREATE_SERVICE - set scm [OpenSCManager $opts(system) $opts(database) 2] - trap { - # 0x000F01FF -> SERVICE_ALL_ACCESS - set svch [CreateService \ - $scm \ - $name \ - $opts(displayname) \ - 0x000F01FF \ - $opts(servicetype) \ - $opts(starttype) \ - $opts(errorcontrol) \ - $opts(command) \ - $opts(loadordergroup) \ - "" \ - $opts(dependencies) \ - $opts(account) \ - $opts(password)] - - CloseServiceHandle $svch - - } finally { - CloseServiceHandle $scm - } - - return -} - - -# Delete the given service -proc twapi::delete_service {name args} { - - array set opts [parseargs args {system.arg database.arg} -nulldefault] - - # 0x00010000 -> DELETE access - set opts(scm_priv) 0x00010000 - set opts(svc_priv) 0x00010000 - set opts(proc) twapi::DeleteService - - _service_fn_wrapper $name opts - - return -} - - -# Get the internal name of a service -proc twapi::get_service_internal_name {name args} { - array set opts [parseargs args {system.arg database.arg} -nulldefault] - # 0x00020000 -> STANDARD_RIGHTS_READ - set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] - - trap { - if {[catch {GetServiceKeyName $scm $name} internal_name]} { - # Maybe this is an internal name itself - GetServiceDisplayName $scm $name; # Will throw an error if not internal name - set internal_name $name - } - } finally { - CloseServiceHandle $scm - } - - return $internal_name -} - -proc twapi::get_service_display_name {name args} { - array set opts [parseargs args {system.arg database.arg} -nulldefault] - # 0x00020000 -> STANDARD_RIGHTS_READ - set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] - - trap { - if {[catch {GetServiceDisplayName $scm $name} display_name]} { - # Maybe this is an display name itself - GetServiceKeyName $scm $name; # Will throw an error if not display name - set display_name $name - } - } finally { - CloseServiceHandle $scm - } - - return $display_name -} - -proc twapi::start_service {name args} { - array set opts [parseargs args { - system.arg - database.arg - params.arg - wait.int - } -nulldefault] - set opts(svc_priv) 0x10; # SERVICE_START - set opts(proc) twapi::StartService - set opts(args) [list $opts(params)] - unset opts(params) - - trap { - _service_fn_wrapper $name opts - } onerror {TWAPI_WIN32 1056} { - # Error 1056 means service already running - } - - return [wait {twapi::get_service_state $name -system $opts(system) -database $opts(database)} running $opts(wait)] -} - -# TBD - test -proc twapi::notify_service {name code args} { - array set opts [parseargs args { - system.arg - database.arg - ignorecodes.arg - } -nulldefault] - - if {[string is integer -strict $code] && $code >= 128 && $code <= 255} { - # 0x100 -> SERVICE_USER_DEFINED_CONTROL - set access 0x100 - } elseif {$code eq "paramchange"} { - # 0x40 -> SERVICE_PAUSE_CONTINUE - set access 0x40 - set code 6; # PARAMCHANGE - } else { - badargs! "Invalid service notification code \"$code\"." - } - - set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] - trap { - set svch [OpenService $scm $name $access] - } finally { - CloseServiceHandle $scm - } - - trap { - ControlService $svch $code - } onerror {TWAPI_WIN32} { - if {[lsearch -exact -integer $opts(ignorecodes) [lindex $::errorCode 1]] < 0} { - # Not one of the error codes we can ignore. - rethrow - } - } finally { - CloseServiceHandle $svch - } - return -} - -proc twapi::control_service {name code access finalstate args} { - array set opts [parseargs args { - system.arg - database.arg - ignorecodes.arg - wait.int - } -nulldefault] - # 0x00020000 -> STANDARD_RIGHTS_READ - set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] - trap { - set svch [OpenService $scm $name $access] - } finally { - CloseServiceHandle $scm - } - - trap { - ControlService $svch $code - } onerror {TWAPI_WIN32} { - if {[lsearch -exact -integer $opts(ignorecodes) [lindex $::errorCode 1]] < 0} { - # Not one of the error codes we can ignore. - rethrow - } - } finally { - CloseServiceHandle $svch - } - - if {[string length $finalstate]} { - # Wait until service is in specified state - return [wait {twapi::get_service_state $name -system $opts(system) -database $opts(database)} $finalstate $opts(wait)] - } else { - return 0 - } -} - -proc twapi::stop_service {name args} { - # 1 -> SERVICE_CONTROL_STOP - # 0x20 -> SERVICE_STOP - control_service $name 1 0x20 stopped -ignorecodes 1062 {*}$args -} - -proc twapi::pause_service {name args} { - # 2 -> SERVICE_CONTROL_PAUSE - # 0x40 -> SERVICE_PAUSE_CONTINUE - control_service $name 2 0x40 paused {*}$args -} - -proc twapi::continue_service {name args} { - # 3 -> SERVICE_CONTROL_CONTINUE - # 0x40 -> SERVICE_PAUSE_CONTINUE - control_service $name 3 0x40 running {*}$args -} - -proc twapi::interrogate_service {name args} { - # 4 -> SERVICE_CONTROL_INTERROGATE - # 0x80 -> SERVICE_INTERROGATE - control_service $name 4 0x80 "" {*}$args - return -} - - -# Retrieve status information for a service -proc twapi::get_service_status {name args} { - array set opts [parseargs args {system.arg database.arg} -nulldefault] - # 0x00020000 -> STANDARD_RIGHTS_READ - set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] - trap { - # 4 -> SERVICE_QUERY_STATUS - set svch [OpenService $scm $name 4] - } finally { - # Do not need SCM anymore - CloseServiceHandle $scm - } - - trap { - return [QueryServiceStatusEx $svch 0] - } finally { - CloseServiceHandle $svch - } -} - - -# Get the state of the service -proc twapi::get_service_state {name args} { - return [kl_get [get_service_status $name {*}$args] state] -} - - -# Get the current configuration for a service -proc twapi::get_service_configuration {name args} { - array set opts [parseargs args { - system.arg - database.arg - all - servicetype - interactive - errorcontrol - starttype - command - loadordergroup - account - displayname - dependencies - description - scm_handle.arg - tagid - failureactions - delayedstart - } -nulldefault -hyphenated] - - if {$opts(-scm_handle) eq ""} { - # Use 0x00020000 -> STANDARD_RIGHTS_READ for SCM - set scmh [OpenSCManager $opts(-system) $opts(-database) 0x00020000] - trap { - set svch [OpenService $scmh $name 1]; # 1 -> SERVICE_QUERY_CONFIG - } finally { - CloseServiceHandle $scmh - } - } else { - set svch [OpenService $opts(-scm_handle) $name 1]; # 1 -> SERVICE_QUERY_CONFIG - } - - trap { - set result [QueryServiceConfig $svch] - if {$opts(-all) || $opts(-description)} { - dict set result -description {} - # For backwards compatibility, ignore errors if description - # cannot be obtained - catch { - dict set result -description [QueryServiceConfig2 $svch 1]; # 1 -> SERVICE_CONFIG_DESCRIPTION - } - } - - if {$opts(-all) || $opts(-failureactions)} { - # 2 -> SERVICE_CONFIG_FAILURE_ACTIONS - lassign [QueryServiceConfig2 $svch 2] resetperiod rebootmsg command failure_actions - set actions {} - foreach action $failure_actions { - lappend actions [list [dict* {0 none 1 restart 2 reboot 3 run} [lindex $action 0]] [lindex $action 1]] - } - dict set result -failureactions [list -resetperiod $resetperiod -rebootmsg $rebootmsg -command $command -actions $actions] - } - if {$opts(-all) || $opts(-delayedstart)} { - if {[min_os_version 6]} { - # 3 -> SERVICE_CONFIG_DELAYED_AUTO_START_INFO - dict set result -delayedstart [QueryServiceConfig2 $svch 3] - } else { - dict set result -delayedstart 0 - } - } - } finally { - CloseServiceHandle $svch - } - - if {! $opts(-all)} { - set result [dict filter $result script {k val} {set opts($k)}] - } - - if {[dict exists $result -errorcontrol]} { - dict set result -errorcontrol [_map_errorcontrol_code [dict get $result -errorcontrol]] - } - - if {[dict exists $result -starttype]} { - dict set result -starttype [_map_starttype_code [dict get $result -starttype]] - } - - return $result -} - -# Sets a service configuration -proc twapi::set_service_configuration {name args} { - # Get the current values - we will need these for validation - # with the new values - array set current [get_service_configuration $name -all] - set current(-password) ""; # This is not returned by get_service_configuration - - # Now parse arguments, filling in defaults - array set opts [parseargs args { - displayname.arg - servicetype.arg - interactive.bool - starttype.arg - errorcontrol.arg - command.arg - loadordergroup.arg - dependencies.arg - account.arg - password.arg - {system.arg ""} - {database.arg ""} - }] - - if {[info exists opts(account)] && ! [info exists opts(password)]} { - error "Option -password must also be specified when -account is specified." - } - - # Merge current configuration with specified options - foreach opt { - displayname - servicetype - interactive - starttype - errorcontrol - command - loadordergroup - dependencies - account - password - } { - if {[info exists opts($opt)]} { - set winparams($opt) $opts($opt) - } else { - set winparams($opt) $current(-$opt) - } - } - - # Validate the new configuration - switch -exact -- $winparams(servicetype) { - file_system_driver - - kernel_driver { - if {$winparams(interactive)} { - error "Option -interactive cannot be specified when -servicetype is $winparams(servicetype)." - } - } - default { - if {$winparams(interactive) && - [string length $winparams(account)] && - [string compare -nocase $winparams(account) "LocalSystem"] - } { - error "Option -interactive cannot be specified with the -account option as interactive services must run under the LocalSystem account." - } - if {[string equal $winparams(starttype) "boot_start"] - || [string equal $winparams(starttype) "system_start"]} { - error "Option -starttype value must be one of auto_start, demand_start or disabled when -servicetype is '$winparams(servicetype)'." - } - } - } - - # Map keywords to integer values - set winparams(servicetype) [_map_servicetype_sym $winparams(servicetype)] - set winparams(starttype) [_map_starttype_sym $winparams(starttype)] - set winparams(errorcontrol) [_map_errorcontrol_sym $winparams(errorcontrol)] - - # Merge the interactive setting - # 0x100 -> SERVICE_INTERACTIVE_PROCESS - if {$winparams(interactive)} { - setbits winparams(servicetype) 0x100 - } else { - resetbits winparams(servicetype) 0x100 - } - - # If domain/system not specified, tack on ".\" for local system - if {[string length $winparams(account)]} { - if {[string first \\ $winparams(account)] < 0} { - set winparams(account) ".\\$winparams(account)" - } - } - - # Now replace any options that were not specified with "no change" - # tokens. - foreach opt {servicetype starttype errorcontrol} { - if {![info exists opts($opt)]} { - set winparams($opt) 0xffffffff; # SERVICE_NO_CHANGE - } - } - # -servicetype and -interactive go in same field - if {![info exists opts(servicetype)] && ![info exists opts(interactive)]} { - set winparams(servicetype) 0xffffffff; # SERVICE_NO_CHANGE - } - - foreach opt {command loadordergroup dependencies account password displayname} { - if {![info exists opts($opt)]} { - set winparams($opt) $twapi::nullptr - } - } - - set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ - set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG - - set opts(proc) twapi::ChangeServiceConfig - set opts(args) \ - [list \ - $winparams(servicetype) \ - $winparams(starttype) \ - $winparams(errorcontrol) \ - $winparams(command) \ - $winparams(loadordergroup) \ - "" \ - $winparams(dependencies) \ - $winparams(account) \ - $winparams(password) \ - $winparams(displayname)] - - _service_fn_wrapper $name opts - - return -} - -proc twapi::set_service_delayed_start {name delay args} { - array set opts [parseargs args { - {system.arg ""} - {database.arg ""} - } -maxleftover 0] - - set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ - set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG - - set opts(proc) twapi::ChangeServiceConfig2 - set opts(args) [list 3 $delay] - - _service_fn_wrapper $name opts - return -} - -proc twapi::set_service_description {name description args} { - array set opts [parseargs args { - {system.arg ""} - {database.arg ""} - } -maxleftover 0] - - set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ - set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG - - set opts(proc) twapi::ChangeServiceConfig2 - set opts(args) [list 1 $description] - - _service_fn_wrapper $name opts - return -} - -proc twapi::set_service_failure_actions {name args} { - array set opts [parseargs args { - {system.arg ""} - {database.arg ""} - resetperiod.arg - {rebootmsg.arg __null__} - {command.arg __null__} - actions.arg - } -maxleftover 0] - - set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ - set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG - - # If option actions is not specified, actions for the service - # are left unchanged. - if {[info exists opts(actions)]} { - set actions {} - foreach action $opts(actions) { - if {[llength $action] != 2} { - error "Invalid format for failure action" - } - set action_code [dict* {none 0 restart 1 reboot 2 run 3} [lindex $action 0]] - if {$action_code == 1} { - # Also need SERVICE_START access right for restart action - set opts(svc_priv) [expr {$opts(svc_priv) | 0x10}] - } - lappend actions [list $action_code [lindex $action 1]] - } - if {![info exists opts(resetperiod)] || $opts(resetperiod) eq "infinite"} { - set opts(resetperiod) 0xffffffff - } - set fail_params [list $opts(resetperiod) $opts(rebootmsg) $opts(command) $actions] - } else { - if {[info exists opts(resetperiod)]} { - badargs! "Option -resetperiod can only be used if the -actions option is also specified." - } - set fail_params [list 0 $opts(rebootmsg) $opts(command)] - } - - set opts(proc) twapi::ChangeServiceConfig2 - set opts(args) [list 2 $fail_params]; # 2 -> SERVICE_CONFIG_FAILURE_ACTIONS - _service_fn_wrapper $name opts - return -} - -# Get status for the specified service types -proc twapi::get_multiple_service_status {args} { - set service_types [list \ - kernel_driver \ - file_system_driver \ - adapter \ - recognizer_driver \ - user_own_process \ - user_share_process \ - win32_own_process \ - win32_share_process] - set switches [concat $service_types \ - [list active inactive] \ - [list system.arg database.arg]] - array set opts [parseargs args $switches -nulldefault] - - set servicetype 0 - foreach type $service_types { - if {$opts($type)} { - set servicetype [expr { $servicetype | [_map_servicetype_sym $type]}] - } - } - if {$servicetype == 0} { - # No type specified, return all - set servicetype 0x3f - } - - set servicestate 0 - if {$opts(active)} { - set servicestate 1; # 1 -> SERVICE_ACTIVE - } - if {$opts(inactive)} { - set servicestate [expr {$servicestate | 2}]; # 2 -> SERVICE_INACTIVE - } - if {$servicestate == 0} { - # No state specified, include all - set servicestate 3 - } - - # 4 -> SC_MANAGER_ENUMERATE_SERVICE - set scm [OpenSCManager $opts(system) $opts(database) 4] - trap { - set fields { - servicetype state controls_accepted exitcode service_code - checkpoint wait_hint pid serviceflags name displayname interactive - } - return [list $fields [EnumServicesStatusEx $scm 0 $servicetype $servicestate __null__]] - } finally { - CloseServiceHandle $scm - } -} - - -# Get status for the dependents of the specified service -proc twapi::get_dependent_service_status {name args} { - array set opts [parseargs args \ - [list active inactive system.arg database.arg] \ - -nulldefault] - - set servicestate 0 - if {$opts(active)} { - set servicestate 1; # 1 -> SERVICE_ACTIVE - } - if {$opts(inactive)} { - set servicestate [expr {$servicestate | 2}]; # SERVICE_INACTIVE - } - if {$servicestate == 0} { - # No state specified, include all - set servicestate 3 - } - - set opts(svc_priv) 8; # SERVICE_ENUMERATE_DEPENDENTS - set opts(proc) twapi::EnumDependentServices - set opts(args) [list $servicestate] - - set fields { - servicetype state controls_accepted exitcode service_code - checkpoint wait_hint name displayname interactive - } - - return [list $fields [_service_fn_wrapper $name opts]] - - -} - - -################################################################ -# Commands for running as a service - -proc twapi::run_as_service {services args} { - variable service_state - - if {[llength $services] == 0} { - win32_error 87 "No services specified" - } - - array set opts [parseargs args { - interactive.bool - {controls.arg {stop shutdown}} - } -nulldefault -maxleftover 0] - - # Currently service controls are per process, not per service and - # are fixed for the duration of the process. - # TBD - C code actually allows for per service controls. Expose? - set service_state(controls) [_parse_service_accept_controls $opts(controls)] - if {![min_os_version 5 1]} { - # Not accepted on Win2k - if {$service_state(controls) & 0x80} { - error "Service control type 'sessionchange' is not valid on this platform" - } - } - - if {[llength $services] == 1} { - set type 0x10; # WIN32_OWN_PROCESS - } else { - set type 0x20; # WIN32_SHARE_PROCESS - } - if {$opts(interactive)} { - setbits type 0x100; # INTERACTIVE_PROCESS - } - - set service_defs [list ] - foreach service $services { - lassign $service name script - set name [string tolower $name] - lappend service_defs [list $name $service_state(controls)] - set service_state($name,state) stopped - set service_state($name,script) $script - set service_state($name,checkpoint) 0 - set service_state($name,waithint) 2000; # 2 seconds - set service_state($name,exitcode) 0 - set service_state($name,servicecode) 0 - set service_state($name,seq) 0 - set service_state($name,seqack) 0 - } - - twapi::Twapi_BecomeAService $type {*}$service_defs - - # Turn off console events by installing our own handler, - # else tclsh will exit when a user logs off even if it is running - # as a service - # COMMENTED OUT because now done in C code itself - # proc ::twapi::_service_console_handler args { return 1 } - # set_console_control_handler ::twapi::_service_console_handler - - # Redefine ourselves as we should not be called again - proc ::twapi::run_as_service args { - error "Already running as a service" - } -} - - -# Callback that handles requests from the service control manager -proc twapi::_service_handler {name service_status_handle control args} { - # TBD - should we catch the error or let the C code see it ? - if {[catch { - _service_handler_unsafe $name $service_status_handle $control $args - } msg]} { - # TBD - log error message - catch {eventlog_log "Error in service handler for service $name. $msg Stack: $::errorInfo" -type error} - } -} - -# Can raise an error -proc twapi::_service_handler_unsafe {name service_status_handle control extra_args} { - variable service_state - - set name [string tolower $name] - - # The service handler will receive control codes from the service - # control manager and modify the state of a service accordingly. - # It also calls the script registered by the application for - # the service. The caller is expected to complete the state change - # by calling service_change_state_complete either inside the - # callback or at some later point. - - set tell_app true; # Does app need to be notified ? - set report_status true; # Whether we should update status - set need_response true; # App should report status back - - switch -glob -- "$service_state($name,state),$control" { - stopped,start { - set service_state($name,state) start_pending - set service_state($name,checkpoint) 1 - } - start_pending,shutdown - - paused,shutdown - - pause_pending,shutdown - - continue_pending,shutdown - - running,shutdown - - start_pending,stop - - paused,stop - - pause_pending,stop - - continue_pending,stop - - running,stop { - set service_state($name,state) stop_pending - set service_state($name,checkpoint) 1 - } - running,pause { - set service_state($name,state) pause_pending - set service_state($name,checkpoint) 1 - } - pause_pending,continue - - paused,continue { - set service_state($name,state) continue_pending - set service_state($name,checkpoint) 1 - } - *,interrogate { - # No state change, we will simply report status below - set tell_app false; # No need to bother the application - } - *,userdefined - - *,paramchange - - *,netbindadd - - *,netbindremove - - *,netbindenable - - *,netbinddisable - - *,deviceevent - - *,hardwareprofilechange - - *,powerevent - - *,sessionchange { - # Notifications, should not report status. - set report_status false - set need_response false - } - default { - # All other cases are no-ops (e.g. paused,pause) or - # don't make logical sense (e.g. stop_pending,continue) - # For now, we simply ignore them but not sure - # if we should just update service status anyways - return - } - } - - if {$report_status} { - _report_service_status $name - } - - set result 0 - if {$tell_app} { - if {[catch { - if {$need_response} { - set seq [incr service_state($name,seq)] - } else { - set seq -1 - } - set result [uplevel #0 [linsert $service_state($name,script) end $control $name $seq {*}$extra_args]] - # Note that if the above script may call back into us, - # via update_service_status for example, the service - # state may be updated at this point - } msg]} { - # TBD - report if the script throws errors - } - } - - if {$result eq "allow"} { - set result 0 - } elseif {$result eq "deny"} { - set result 0x424D5144; # BROADCAST_QUERY_DENY - } - - return $result -} - -# Called by the application to update it's status -# status should be one of "running", "paused" or "stopped" -# seq is 0 or the sequence number of a previous callback to -# the application to which this is the response. -proc twapi::update_service_status {name seq state args} { - variable service_state - - if {$state ni {running paused stopped}} { - error "Invalid state token $state" - } - - if {$seq == -1} { - # This was a notification. App should not have responded. - # Just ignore it - return ignored - } - - array set opts [parseargs args { - exitcode.int - servicecode.int - waithint.int - } -maxleftover 0] - - set name [string tolower $name] - - # Depending on the current state of the application, - # we may or may not be able to change state. For - # example, if the current state is "running" and - # the new state is "stopped", that is ok. But the - # converse is not allowed since we cannot - # transition from stopped to running unless - # the SCM has sent us a start signal. - - # If the seq is greater than the last one we sent, bug somewhere - if {$service_state($name,seq) < $seq} { - error "Invalid sequence number $seq (too large) for service status update." - } - - # If we have a request outstanding (to the app) that the app - # has not yet responded to, then all calls from the app with - # no seq number (i.e. 0) or calls with an older sequence number - # are ignored. - if {($service_state($name,seq) > $service_state($name,seqack)) && - ($seq == 0 || $seq < $service_state($name,seq))} { - # Ignore this request - return ignored - } - - set service_state($name,seqack) $seq; # last responded sequence number - - # If state specified as stopped, store the exit codes - if {$state eq "stopped"} { - if {[info exists opts(exitcode)]} { - set service_state($name,exitcode) $opts(exitcode) - } - if {[info exists opts(servicecode)]} { - set service_state($name,servicecode) $opts(servicecode) - } - } - - upvar 0 service_state($name,state) current_state - - # If there is no state change, nothing to do - if {$state eq $current_state} { - return nochange - } - - switch -exact -- $state { - stopped { - # Application can stop at any time from any other state. - # No questions asked. - } - running { - if {$current_state eq "stopped" || $current_state eq "paused"} { - # This should not happen if all the rules are followed by the - # application code. - #error "Service $name attempted to transition directly from stopped or paused state to running state without an intermediate pending state" - return invalidchange - } - } - paused { - if {$current_state ne "pause_pending" && - $current_state ne "continue_pending"} { - # This should not happen if all the rules are followed by the - # application code. - #error "Service $name attempted to transition from $current_state state to paused state" - return invalidchange - } - } - } - - set current_state $state - _report_service_status $name - - if {$state eq "stopped"} { - # If all services have stopped, tell the app - set all_stopped true - foreach {entry val} [array get service_state *,state] { - if {$val ne "stopped"} { - set all_stopped false - break - } - } - if {$all_stopped} { - uplevel #0 [linsert $service_state($name,script) end all_stopped $name 0] - } - } - - return changed; # State changed -} - - -# Report the status of a service back to the SCM -proc twapi::_report_service_status {name} { - variable service_state - upvar 0 service_state($name,state) current_state - - # If the state is a pending state, then make sure we - # increment the checkpoint value - if {[string match *pending $current_state]} { - incr service_state($name,checkpoint) - set waithint $service_state($name,waithint) - } else { - set service_state($name,checkpoint) 0 - set waithint 0 - } - - # Currently service controls are per process, not per service and - # are fixed for the duration of the process. So we always pass - # service_state(controls). Applications has to ensure it can handle - # all control signals in all states (ignoring them as desired) - if {[catch { - Twapi_SetServiceStatus $name $::twapi::service_state_values($current_state) $service_state($name,exitcode) $service_state($name,servicecode) $service_state($name,checkpoint) $waithint $service_state(controls) - } msg]} { - # TBD - report error - but how ? bgerror? - catch {twapi::eventlog_log "Error setting service status: $msg"} - } - - # If we had supplied a wait hint, we are telling the SCM, we will call - # it back within that period of time, so schedule ourselves. - if {$waithint} { - set delay [expr {($waithint*3)/4}] - after $delay ::twapi::_call_scm_within_waithint $name $current_state $service_state($name,checkpoint) - } - - return -} - - -# Queued to regularly update the SCM when we are in any of the pending states -proc ::twapi::_call_scm_within_waithint {name orig_state orig_checkpoint} { - variable service_state - - # We only call to update staus if the state and checkpoint have - # not changed since the routine was queued - if {($service_state($name,state) eq $orig_state) && - ($service_state($name,checkpoint) == $orig_checkpoint)} { - _report_service_status $name - } -} - - -################################################################ -# Utility procedures - -# Map an integer service type code into a list consisting of -# {SERVICETYPESYMBOL BOOLEAN}. If there is not symbolic service type -# for the service, just the integer code is returned. The BOOLEAN -# is 1/0 depending on whether the service type code is interactive -proc twapi::_map_servicetype_code {servicetype} { - # 0x100 -> SERVICE_INTERACTIVE_PROCESS - set interactive [expr {($servicetype & 0x100) != 0}] - set servicetype [expr {$servicetype & (~ 0x100)}] - set servicetype [kl_get [list \ - 16 win32_own_process \ - 32 win32_share_process \ - 80 user_own_process \ - 96 user_share_process \ - 1 kernel_driver \ - 2 file_system_driver \ - 4 adapter \ - 8 recognizer_driver \ - ] $servicetype $servicetype] - return [list $servicetype $interactive] -} - -# Map service type sym to int code -proc twapi::_map_servicetype_sym {sym} { - return [dict get {kernel_driver 1 file_system_driver 2 adapter 4 recognizer_driver 8 win32_own_process 16 win32_share_process 32 user_own_process 80 user_share_process 96} $sym] -} - -# Map a start type code into a symbol. Returns the integer code if -# no mapping possible -proc twapi::_map_starttype_code {code} { - incr code 0; # Make canonical int - set type [lindex {boot_start system_start auto_start demand_start disabled} $code] - if {$type eq ""} { - return $code - } else { - return $type - } -} - -# Map starttype sym to int code -proc twapi::_map_starttype_sym {sym} { - return [dict get {boot_start 0 system_start 1 auto_start 2 demand_start 3 disabled 4} $sym] -} - -# Map a error control code into a symbol. Returns the integer code if -# no mapping possible -proc twapi::_map_errorcontrol_code {code} { - incr code 0; # Make canonical int - set error [lindex {ignore normal severe critical} $code] - if {$error eq ""} { - return $code - } else { - return $error - } -} - -# Map error control sym to int code -proc twapi::_map_errorcontrol_sym {sym} { - return [dict get {ignore 0 normal 1 severe 2 critical 3} $sym] -} - -# Standard template for calling a service function. v_opts should refer -# to an array with the following elements: -# opts(system) - target system. Must be specified -# opts(database) - target database. Must be specified -# opts(scm_priv) - requested privilege when opening SCM. STANDARD_RIGHTS_READ -# is used if unspecified. Not used if scm_handle is specified -# opts(scm_handle) - handle to service control manager. Optional -# opts(svc_priv) - requested privilege when opening service. Must be present -# opts(proc) - proc/function to call. The first arg is the service handle -# opts(args) - additional arguments to pass to the function. -# Empty if unspecified -proc twapi::_service_fn_wrapper {name v_opts} { - upvar $v_opts opts - - # Use 0x00020000 -> STANDARD_RIGHTS_READ for SCM if not specified - set scm_priv [expr {[info exists opts(scm_priv)] ? $opts(scm_priv) : 0x00020000}] - - if {[info exists opts(scm_handle)] && - $opts(scm_handle) ne ""} { - set scm $opts(scm_handle) - } else { - set scm [OpenSCManager $opts(system) $opts(database) $scm_priv] } - trap { - set svch [OpenService $scm $name $opts(svc_priv)] - } finally { - # No need for scm handle anymore. Close it unless it was - # passed to us - if {(![info exists opts(scm_handle)]) || - ($opts(scm_handle) eq "")} { - CloseServiceHandle $scm - } - } - - set proc_args [expr {[info exists opts(args)] ? $opts(args) : ""}] - trap { - set results [eval [list $opts(proc) $svch] $proc_args] - } finally { - CloseServiceHandle $svch - } - - return $results -} - -# Called back for reporting background errors. Note this is called -# from the C++ services code, not from scripts. -proc twapi::_service_background_error {winerror msg} { - twapi::win32_error $winerror $msg -} - -# Parse symbols for controls accepted by a service -proc twapi::_parse_service_accept_controls {controls} { - return [_parse_symbolic_bitmask $controls { - stop 0x00000001 - pause_continue 0x00000002 - shutdown 0x00000004 - paramchange 0x00000008 - netbindchange 0x00000010 - hardwareprofilechange 0x00000020 - powerevent 0x00000040 - sessionchange 0x00000080 - }] -} +# +# Copyright (c) 2003-2007, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + # When the process hosts Windows services, service_state + # is used to keep state of each service. The variable + # is indexed by NAME,FIELD where NAME is the name + # of the service and FIELD is one of "state", "script", + # "checkpoint", "waithint", "exitcode", "servicecode", + # "seq", "seqack" + variable service_state + + # Map service state names to integers + variable service_state_values + array set service_state_values { + stopped 1 + start_pending 2 + stop_pending 3 + running 4 + continue_pending 5 + pause_pending 6 + paused 7 + } +} + + +# Return 1/0 depending on whether the given service exists +# $name may be either the internal or display name +proc twapi::service_exists {name args} { + array set opts [parseargs args {system.arg database.arg} -nulldefault] + # 0x00020000 -> STANDARD_RIGHTS_READ + set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] + + trap { + GetServiceKeyName $scm $name + set exists 1 + } onerror {TWAPI_WIN32 1060} { + # "no such service" error for internal name. + # Try display name + trap { + GetServiceDisplayName $scm $name + set exists 1 + } onerror {TWAPI_WIN32 1060} { + set exists 0 + } + } finally { + CloseServiceHandle $scm + } + + return $exists +} + + +# Create a service of the specified name +proc twapi::create_service {name command args} { + array set opts [parseargs args { + displayname.arg + {servicetype.arg win32_own_process {win32_own_process win32_share_process file_system_driver kernel_driver}} + {interactive.bool 0} + {starttype.arg auto_start {auto_start boot_start demand_start disabled system_start}} + {errorcontrol.arg normal {ignore normal severe critical}} + loadordergroup.arg + dependencies.arg + account.arg + password.arg + system.arg + database.arg + } -nulldefault] + + + if {[string length $opts(displayname)] == 0} { + set opts(displayname) $name + } + + if {[string length $command] == 0} { + error "The executable path must not be null when creating a service" + } + set opts(command) $command + + switch -exact -- $opts(servicetype) { + file_system_driver - + kernel_driver { + if {$opts(interactive)} { + error "Option -interactive cannot be specified when -servicetype is $opts(servicetype)." + } + } + default { + if {$opts(interactive) && [string length $opts(account)]} { + error "Option -interactive cannot be specified with the -account option as interactive services must run under the LocalSystem account." + } + if {[string equal $opts(starttype) "boot_start"] + || [string equal $opts(starttype) "system_start"]} { + error "Option -starttype value must be one of auto_start, demand_start or disabled when -servicetype is '$opts(servicetype)'." + } + } + } + + # Map keywords to integer values + set opts(servicetype) [_map_servicetype_sym $opts(servicetype)] + set opts(starttype) [_map_starttype_sym $opts(starttype)] + set opts(errorcontrol) [_map_errorcontrol_sym $opts(errorcontrol)] + + # If interactive, add the flag to the service type + if {$opts(interactive)} { + setbits opts(servicetype) 0x100; # SERVICE_INTERACTIVE_PROCESS + } + + # Ignore password if username not specified + if {[string length $opts(account)] == 0} { + set opts(password) "" + } else { + # If domain/system not specified, tack on ".\" for local system + if {[string first \\ $opts(account)] < 0} { + set opts(account) ".\\$opts(account)" + } + } + + # 2 -> SC_MANAGER_CREATE_SERVICE + set scm [OpenSCManager $opts(system) $opts(database) 2] + trap { + # 0x000F01FF -> SERVICE_ALL_ACCESS + set svch [CreateService \ + $scm \ + $name \ + $opts(displayname) \ + 0x000F01FF \ + $opts(servicetype) \ + $opts(starttype) \ + $opts(errorcontrol) \ + $opts(command) \ + $opts(loadordergroup) \ + "" \ + $opts(dependencies) \ + $opts(account) \ + $opts(password)] + + CloseServiceHandle $svch + + } finally { + CloseServiceHandle $scm + } + + return +} + + +# Delete the given service +proc twapi::delete_service {name args} { + + array set opts [parseargs args {system.arg database.arg} -nulldefault] + + # 0x00010000 -> DELETE access + set opts(scm_priv) 0x00010000 + set opts(svc_priv) 0x00010000 + set opts(proc) twapi::DeleteService + + _service_fn_wrapper $name opts + + return +} + + +# Get the internal name of a service +proc twapi::get_service_internal_name {name args} { + array set opts [parseargs args {system.arg database.arg} -nulldefault] + # 0x00020000 -> STANDARD_RIGHTS_READ + set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] + + trap { + if {[catch {GetServiceKeyName $scm $name} internal_name]} { + # Maybe this is an internal name itself + GetServiceDisplayName $scm $name; # Will throw an error if not internal name + set internal_name $name + } + } finally { + CloseServiceHandle $scm + } + + return $internal_name +} + +proc twapi::get_service_display_name {name args} { + array set opts [parseargs args {system.arg database.arg} -nulldefault] + # 0x00020000 -> STANDARD_RIGHTS_READ + set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] + + trap { + if {[catch {GetServiceDisplayName $scm $name} display_name]} { + # Maybe this is an display name itself + GetServiceKeyName $scm $name; # Will throw an error if not display name + set display_name $name + } + } finally { + CloseServiceHandle $scm + } + + return $display_name +} + +proc twapi::start_service {name args} { + array set opts [parseargs args { + system.arg + database.arg + params.arg + wait.int + } -nulldefault] + set opts(svc_priv) 0x10; # SERVICE_START + set opts(proc) twapi::StartService + set opts(args) [list $opts(params)] + unset opts(params) + + trap { + _service_fn_wrapper $name opts + } onerror {TWAPI_WIN32 1056} { + # Error 1056 means service already running + } + + return [wait {twapi::get_service_state $name -system $opts(system) -database $opts(database)} running $opts(wait)] +} + +# TBD - test +proc twapi::notify_service {name code args} { + array set opts [parseargs args { + system.arg + database.arg + ignorecodes.arg + } -nulldefault] + + if {[string is integer -strict $code] && $code >= 128 && $code <= 255} { + # 0x100 -> SERVICE_USER_DEFINED_CONTROL + set access 0x100 + } elseif {$code eq "paramchange"} { + # 0x40 -> SERVICE_PAUSE_CONTINUE + set access 0x40 + set code 6; # PARAMCHANGE + } else { + badargs! "Invalid service notification code \"$code\"." + } + + set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] + trap { + set svch [OpenService $scm $name $access] + } finally { + CloseServiceHandle $scm + } + + trap { + ControlService $svch $code + } onerror {TWAPI_WIN32} { + if {[lsearch -exact -integer $opts(ignorecodes) [lindex $::errorCode 1]] < 0} { + # Not one of the error codes we can ignore. + rethrow + } + } finally { + CloseServiceHandle $svch + } + return +} + +proc twapi::control_service {name code access finalstate args} { + array set opts [parseargs args { + system.arg + database.arg + ignorecodes.arg + wait.int + } -nulldefault] + # 0x00020000 -> STANDARD_RIGHTS_READ + set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] + trap { + set svch [OpenService $scm $name $access] + } finally { + CloseServiceHandle $scm + } + + trap { + ControlService $svch $code + } onerror {TWAPI_WIN32} { + if {[lsearch -exact -integer $opts(ignorecodes) [lindex $::errorCode 1]] < 0} { + # Not one of the error codes we can ignore. + rethrow + } + } finally { + CloseServiceHandle $svch + } + + if {[string length $finalstate]} { + # Wait until service is in specified state + return [wait {twapi::get_service_state $name -system $opts(system) -database $opts(database)} $finalstate $opts(wait)] + } else { + return 0 + } +} + +proc twapi::stop_service {name args} { + # 1 -> SERVICE_CONTROL_STOP + # 0x20 -> SERVICE_STOP + control_service $name 1 0x20 stopped -ignorecodes 1062 {*}$args +} + +proc twapi::pause_service {name args} { + # 2 -> SERVICE_CONTROL_PAUSE + # 0x40 -> SERVICE_PAUSE_CONTINUE + control_service $name 2 0x40 paused {*}$args +} + +proc twapi::continue_service {name args} { + # 3 -> SERVICE_CONTROL_CONTINUE + # 0x40 -> SERVICE_PAUSE_CONTINUE + control_service $name 3 0x40 running {*}$args +} + +proc twapi::interrogate_service {name args} { + # 4 -> SERVICE_CONTROL_INTERROGATE + # 0x80 -> SERVICE_INTERROGATE + control_service $name 4 0x80 "" {*}$args + return +} + + +# Retrieve status information for a service +proc twapi::get_service_status {name args} { + array set opts [parseargs args {system.arg database.arg} -nulldefault] + # 0x00020000 -> STANDARD_RIGHTS_READ + set scm [OpenSCManager $opts(system) $opts(database) 0x00020000] + trap { + # 4 -> SERVICE_QUERY_STATUS + set svch [OpenService $scm $name 4] + } finally { + # Do not need SCM anymore + CloseServiceHandle $scm + } + + trap { + return [QueryServiceStatusEx $svch 0] + } finally { + CloseServiceHandle $svch + } +} + + +# Get the state of the service +proc twapi::get_service_state {name args} { + return [kl_get [get_service_status $name {*}$args] state] +} + + +# Get the current configuration for a service +proc twapi::get_service_configuration {name args} { + array set opts [parseargs args { + system.arg + database.arg + all + servicetype + interactive + errorcontrol + starttype + command + loadordergroup + account + displayname + dependencies + description + scm_handle.arg + tagid + failureactions + delayedstart + } -nulldefault -hyphenated] + + if {$opts(-scm_handle) eq ""} { + # Use 0x00020000 -> STANDARD_RIGHTS_READ for SCM + set scmh [OpenSCManager $opts(-system) $opts(-database) 0x00020000] + trap { + set svch [OpenService $scmh $name 1]; # 1 -> SERVICE_QUERY_CONFIG + } finally { + CloseServiceHandle $scmh + } + } else { + set svch [OpenService $opts(-scm_handle) $name 1]; # 1 -> SERVICE_QUERY_CONFIG + } + + trap { + set result [QueryServiceConfig $svch] + if {$opts(-all) || $opts(-description)} { + dict set result -description {} + # For backwards compatibility, ignore errors if description + # cannot be obtained + catch { + dict set result -description [QueryServiceConfig2 $svch 1]; # 1 -> SERVICE_CONFIG_DESCRIPTION + } + } + + if {$opts(-all) || $opts(-failureactions)} { + # 2 -> SERVICE_CONFIG_FAILURE_ACTIONS + lassign [QueryServiceConfig2 $svch 2] resetperiod rebootmsg command failure_actions + set actions {} + foreach action $failure_actions { + lappend actions [list [dict* {0 none 1 restart 2 reboot 3 run} [lindex $action 0]] [lindex $action 1]] + } + dict set result -failureactions [list -resetperiod $resetperiod -rebootmsg $rebootmsg -command $command -actions $actions] + } + if {$opts(-all) || $opts(-delayedstart)} { + if {[min_os_version 6]} { + # 3 -> SERVICE_CONFIG_DELAYED_AUTO_START_INFO + dict set result -delayedstart [QueryServiceConfig2 $svch 3] + } else { + dict set result -delayedstart 0 + } + } + } finally { + CloseServiceHandle $svch + } + + if {! $opts(-all)} { + set result [dict filter $result script {k val} {set opts($k)}] + } + + if {[dict exists $result -errorcontrol]} { + dict set result -errorcontrol [_map_errorcontrol_code [dict get $result -errorcontrol]] + } + + if {[dict exists $result -starttype]} { + dict set result -starttype [_map_starttype_code [dict get $result -starttype]] + } + + return $result +} + +# Sets a service configuration +proc twapi::set_service_configuration {name args} { + # Get the current values - we will need these for validation + # with the new values + array set current [get_service_configuration $name -all] + set current(-password) ""; # This is not returned by get_service_configuration + + # Now parse arguments, filling in defaults + array set opts [parseargs args { + displayname.arg + servicetype.arg + interactive.bool + starttype.arg + errorcontrol.arg + command.arg + loadordergroup.arg + dependencies.arg + account.arg + password.arg + {system.arg ""} + {database.arg ""} + }] + + if {[info exists opts(account)] && ! [info exists opts(password)]} { + error "Option -password must also be specified when -account is specified." + } + + # Merge current configuration with specified options + foreach opt { + displayname + servicetype + interactive + starttype + errorcontrol + command + loadordergroup + dependencies + account + password + } { + if {[info exists opts($opt)]} { + set winparams($opt) $opts($opt) + } else { + set winparams($opt) $current(-$opt) + } + } + + # Validate the new configuration + switch -exact -- $winparams(servicetype) { + file_system_driver - + kernel_driver { + if {$winparams(interactive)} { + error "Option -interactive cannot be specified when -servicetype is $winparams(servicetype)." + } + } + default { + if {$winparams(interactive) && + [string length $winparams(account)] && + [string compare -nocase $winparams(account) "LocalSystem"] + } { + error "Option -interactive cannot be specified with the -account option as interactive services must run under the LocalSystem account." + } + if {[string equal $winparams(starttype) "boot_start"] + || [string equal $winparams(starttype) "system_start"]} { + error "Option -starttype value must be one of auto_start, demand_start or disabled when -servicetype is '$winparams(servicetype)'." + } + } + } + + # Map keywords to integer values + set winparams(servicetype) [_map_servicetype_sym $winparams(servicetype)] + set winparams(starttype) [_map_starttype_sym $winparams(starttype)] + set winparams(errorcontrol) [_map_errorcontrol_sym $winparams(errorcontrol)] + + # Merge the interactive setting + # 0x100 -> SERVICE_INTERACTIVE_PROCESS + if {$winparams(interactive)} { + setbits winparams(servicetype) 0x100 + } else { + resetbits winparams(servicetype) 0x100 + } + + # If domain/system not specified, tack on ".\" for local system + if {[string length $winparams(account)]} { + if {[string first \\ $winparams(account)] < 0} { + set winparams(account) ".\\$winparams(account)" + } + } + + # Now replace any options that were not specified with "no change" + # tokens. + foreach opt {servicetype starttype errorcontrol} { + if {![info exists opts($opt)]} { + set winparams($opt) 0xffffffff; # SERVICE_NO_CHANGE + } + } + # -servicetype and -interactive go in same field + if {![info exists opts(servicetype)] && ![info exists opts(interactive)]} { + set winparams(servicetype) 0xffffffff; # SERVICE_NO_CHANGE + } + + foreach opt {command loadordergroup dependencies account password displayname} { + if {![info exists opts($opt)]} { + set winparams($opt) $::twapi::nullptr + } + } + + set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ + set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG + + set opts(proc) twapi::ChangeServiceConfig + set opts(args) \ + [list \ + $winparams(servicetype) \ + $winparams(starttype) \ + $winparams(errorcontrol) \ + $winparams(command) \ + $winparams(loadordergroup) \ + "" \ + $winparams(dependencies) \ + $winparams(account) \ + $winparams(password) \ + $winparams(displayname)] + + _service_fn_wrapper $name opts + + return +} + +proc twapi::set_service_delayed_start {name delay args} { + array set opts [parseargs args { + {system.arg ""} + {database.arg ""} + } -maxleftover 0] + + set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ + set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG + + set opts(proc) twapi::ChangeServiceConfig2 + set opts(args) [list 3 $delay] + + _service_fn_wrapper $name opts + return +} + +proc twapi::set_service_description {name description args} { + array set opts [parseargs args { + {system.arg ""} + {database.arg ""} + } -maxleftover 0] + + set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ + set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG + + set opts(proc) twapi::ChangeServiceConfig2 + set opts(args) [list 1 $description] + + _service_fn_wrapper $name opts + return +} + +proc twapi::set_service_failure_actions {name args} { + array set opts [parseargs args { + {system.arg ""} + {database.arg ""} + resetperiod.arg + {rebootmsg.arg __null__} + {command.arg __null__} + actions.arg + } -maxleftover 0] + + set opts(scm_priv) 0x00020000; # 0x00020000 -> STANDARD_RIGHTS_READ + set opts(svc_priv) 2; # 2 -> SERVICE_CHANGE_CONFIG + + # If option actions is not specified, actions for the service + # are left unchanged. + if {[info exists opts(actions)]} { + set actions {} + foreach action $opts(actions) { + if {[llength $action] != 2} { + error "Invalid format for failure action" + } + set action_code [dict* {none 0 restart 1 reboot 2 run 3} [lindex $action 0]] + if {$action_code == 1} { + # Also need SERVICE_START access right for restart action + set opts(svc_priv) [expr {$opts(svc_priv) | 0x10}] + } + lappend actions [list $action_code [lindex $action 1]] + } + if {![info exists opts(resetperiod)] || $opts(resetperiod) eq "infinite"} { + set opts(resetperiod) 0xffffffff + } + set fail_params [list $opts(resetperiod) $opts(rebootmsg) $opts(command) $actions] + } else { + if {[info exists opts(resetperiod)]} { + badargs! "Option -resetperiod can only be used if the -actions option is also specified." + } + set fail_params [list 0 $opts(rebootmsg) $opts(command)] + } + + set opts(proc) twapi::ChangeServiceConfig2 + set opts(args) [list 2 $fail_params]; # 2 -> SERVICE_CONFIG_FAILURE_ACTIONS + _service_fn_wrapper $name opts + return +} + +# Get status for the specified service types +proc twapi::get_multiple_service_status {args} { + set service_types [list \ + kernel_driver \ + file_system_driver \ + adapter \ + recognizer_driver \ + user_own_process \ + user_share_process \ + win32_own_process \ + win32_share_process] + set switches [concat $service_types \ + [list active inactive] \ + [list system.arg database.arg]] + array set opts [parseargs args $switches -nulldefault] + + set servicetype 0 + foreach type $service_types { + if {$opts($type)} { + set servicetype [expr { $servicetype | [_map_servicetype_sym $type]}] + } + } + if {$servicetype == 0} { + # No type specified, return all + set servicetype 0x3f + } + + set servicestate 0 + if {$opts(active)} { + set servicestate 1; # 1 -> SERVICE_ACTIVE + } + if {$opts(inactive)} { + set servicestate [expr {$servicestate | 2}]; # 2 -> SERVICE_INACTIVE + } + if {$servicestate == 0} { + # No state specified, include all + set servicestate 3 + } + + # 4 -> SC_MANAGER_ENUMERATE_SERVICE + set scm [OpenSCManager $opts(system) $opts(database) 4] + trap { + set fields { + servicetype state controls_accepted exitcode service_code + checkpoint wait_hint pid serviceflags name displayname interactive + } + return [list $fields [EnumServicesStatusEx $scm 0 $servicetype $servicestate __null__]] + } finally { + CloseServiceHandle $scm + } +} + + +# Get status for the dependents of the specified service +proc twapi::get_dependent_service_status {name args} { + array set opts [parseargs args \ + [list active inactive system.arg database.arg] \ + -nulldefault] + + set servicestate 0 + if {$opts(active)} { + set servicestate 1; # 1 -> SERVICE_ACTIVE + } + if {$opts(inactive)} { + set servicestate [expr {$servicestate | 2}]; # SERVICE_INACTIVE + } + if {$servicestate == 0} { + # No state specified, include all + set servicestate 3 + } + + set opts(svc_priv) 8; # SERVICE_ENUMERATE_DEPENDENTS + set opts(proc) twapi::EnumDependentServices + set opts(args) [list $servicestate] + + set fields { + servicetype state controls_accepted exitcode service_code + checkpoint wait_hint name displayname interactive + } + + return [list $fields [_service_fn_wrapper $name opts]] + + +} + + +################################################################ +# Commands for running as a service + +proc twapi::run_as_service {services args} { + variable service_state + + if {[llength $services] == 0} { + win32_error 87 "No services specified" + } + + array set opts [parseargs args { + interactive.bool + {controls.arg {stop shutdown}} + } -nulldefault -maxleftover 0] + + # Currently service controls are per process, not per service and + # are fixed for the duration of the process. + # TBD - C code actually allows for per service controls. Expose? + set service_state(controls) [_parse_service_accept_controls $opts(controls)] + if {![min_os_version 5 1]} { + # Not accepted on Win2k + if {$service_state(controls) & 0x80} { + error "Service control type 'sessionchange' is not valid on this platform" + } + } + + if {[llength $services] == 1} { + set type 0x10; # WIN32_OWN_PROCESS + } else { + set type 0x20; # WIN32_SHARE_PROCESS + } + if {$opts(interactive)} { + setbits type 0x100; # INTERACTIVE_PROCESS + } + + set service_defs [list ] + foreach service $services { + lassign $service name script + set name [string tolower $name] + lappend service_defs [list $name $service_state(controls)] + set service_state($name,state) stopped + set service_state($name,script) $script + set service_state($name,checkpoint) 0 + set service_state($name,waithint) 2000; # 2 seconds + set service_state($name,exitcode) 0 + set service_state($name,servicecode) 0 + set service_state($name,seq) 0 + set service_state($name,seqack) 0 + } + + twapi::Twapi_BecomeAService $type {*}$service_defs + + # Turn off console events by installing our own handler, + # else tclsh will exit when a user logs off even if it is running + # as a service + # COMMENTED OUT because now done in C code itself + # proc ::twapi::_service_console_handler args { return 1 } + # set_console_control_handler ::twapi::_service_console_handler + + # Redefine ourselves as we should not be called again + proc ::twapi::run_as_service args { + error "Already running as a service" + } +} + + +# Callback that handles requests from the service control manager +proc twapi::_service_handler {name service_status_handle control args} { + # TBD - should we catch the error or let the C code see it ? + if {[catch { + _service_handler_unsafe $name $service_status_handle $control $args + } msg]} { + # TBD - log error message + catch {eventlog_log "Error in service handler for service $name. $msg Stack: $::errorInfo" -type error} + } +} + +# Can raise an error +proc twapi::_service_handler_unsafe {name service_status_handle control extra_args} { + variable service_state + + set name [string tolower $name] + + # The service handler will receive control codes from the service + # control manager and modify the state of a service accordingly. + # It also calls the script registered by the application for + # the service. The caller is expected to complete the state change + # by calling service_change_state_complete either inside the + # callback or at some later point. + + set tell_app true; # Does app need to be notified ? + set report_status true; # Whether we should update status + set need_response true; # App should report status back + + switch -glob -- "$service_state($name,state),$control" { + stopped,start { + set service_state($name,state) start_pending + set service_state($name,checkpoint) 1 + } + start_pending,shutdown - + paused,shutdown - + pause_pending,shutdown - + continue_pending,shutdown - + running,shutdown - + start_pending,stop - + paused,stop - + pause_pending,stop - + continue_pending,stop - + running,stop { + set service_state($name,state) stop_pending + set service_state($name,checkpoint) 1 + } + running,pause { + set service_state($name,state) pause_pending + set service_state($name,checkpoint) 1 + } + pause_pending,continue - + paused,continue { + set service_state($name,state) continue_pending + set service_state($name,checkpoint) 1 + } + *,interrogate { + # No state change, we will simply report status below + set tell_app false; # No need to bother the application + } + *,userdefined - + *,paramchange - + *,netbindadd - + *,netbindremove - + *,netbindenable - + *,netbinddisable - + *,deviceevent - + *,hardwareprofilechange - + *,powerevent - + *,sessionchange { + # Notifications, should not report status. + set report_status false + set need_response false + } + default { + # All other cases are no-ops (e.g. paused,pause) or + # don't make logical sense (e.g. stop_pending,continue) + # For now, we simply ignore them but not sure + # if we should just update service status anyways + return + } + } + + if {$report_status} { + _report_service_status $name + } + + set result 0 + if {$tell_app} { + if {[catch { + if {$need_response} { + set seq [incr service_state($name,seq)] + } else { + set seq -1 + } + set result [uplevel #0 [linsert $service_state($name,script) end $control $name $seq {*}$extra_args]] + # Note that if the above script may call back into us, + # via update_service_status for example, the service + # state may be updated at this point + } msg]} { + # TBD - report if the script throws errors + } + } + + if {$result eq "allow"} { + set result 0 + } elseif {$result eq "deny"} { + set result 0x424D5144; # BROADCAST_QUERY_DENY + } + + return $result +} + +# Called by the application to update it's status +# status should be one of "running", "paused" or "stopped" +# seq is 0 or the sequence number of a previous callback to +# the application to which this is the response. +proc twapi::update_service_status {name seq state args} { + variable service_state + + if {$state ni {running paused stopped}} { + error "Invalid state token $state" + } + + if {$seq == -1} { + # This was a notification. App should not have responded. + # Just ignore it + return ignored + } + + array set opts [parseargs args { + exitcode.int + servicecode.int + waithint.int + } -maxleftover 0] + + set name [string tolower $name] + + # Depending on the current state of the application, + # we may or may not be able to change state. For + # example, if the current state is "running" and + # the new state is "stopped", that is ok. But the + # converse is not allowed since we cannot + # transition from stopped to running unless + # the SCM has sent us a start signal. + + # If the seq is greater than the last one we sent, bug somewhere + if {$service_state($name,seq) < $seq} { + error "Invalid sequence number $seq (too large) for service status update." + } + + # If we have a request outstanding (to the app) that the app + # has not yet responded to, then all calls from the app with + # no seq number (i.e. 0) or calls with an older sequence number + # are ignored. + if {($service_state($name,seq) > $service_state($name,seqack)) && + ($seq == 0 || $seq < $service_state($name,seq))} { + # Ignore this request + return ignored + } + + set service_state($name,seqack) $seq; # last responded sequence number + + # If state specified as stopped, store the exit codes + if {$state eq "stopped"} { + if {[info exists opts(exitcode)]} { + set service_state($name,exitcode) $opts(exitcode) + } + if {[info exists opts(servicecode)]} { + set service_state($name,servicecode) $opts(servicecode) + } + } + + upvar 0 service_state($name,state) current_state + + # If there is no state change, nothing to do + if {$state eq $current_state} { + return nochange + } + + switch -exact -- $state { + stopped { + # Application can stop at any time from any other state. + # No questions asked. + } + running { + if {$current_state eq "stopped" || $current_state eq "paused"} { + # This should not happen if all the rules are followed by the + # application code. + #error "Service $name attempted to transition directly from stopped or paused state to running state without an intermediate pending state" + return invalidchange + } + } + paused { + if {$current_state ne "pause_pending" && + $current_state ne "continue_pending"} { + # This should not happen if all the rules are followed by the + # application code. + #error "Service $name attempted to transition from $current_state state to paused state" + return invalidchange + } + } + } + + set current_state $state + _report_service_status $name + + if {$state eq "stopped"} { + # If all services have stopped, tell the app + set all_stopped true + foreach {entry val} [array get service_state *,state] { + if {$val ne "stopped"} { + set all_stopped false + break + } + } + if {$all_stopped} { + uplevel #0 [linsert $service_state($name,script) end all_stopped $name 0] + } + } + + return changed; # State changed +} + + +# Report the status of a service back to the SCM +proc twapi::_report_service_status {name} { + variable service_state + upvar 0 service_state($name,state) current_state + + # If the state is a pending state, then make sure we + # increment the checkpoint value + if {[string match *pending $current_state]} { + incr service_state($name,checkpoint) + set waithint $service_state($name,waithint) + } else { + set service_state($name,checkpoint) 0 + set waithint 0 + } + + # Currently service controls are per process, not per service and + # are fixed for the duration of the process. So we always pass + # service_state(controls). Applications has to ensure it can handle + # all control signals in all states (ignoring them as desired) + if {[catch { + Twapi_SetServiceStatus $name $::twapi::service_state_values($current_state) $service_state($name,exitcode) $service_state($name,servicecode) $service_state($name,checkpoint) $waithint $service_state(controls) + } msg]} { + # TBD - report error - but how ? bgerror? + catch {twapi::eventlog_log "Error setting service status: $msg"} + } + + # If we had supplied a wait hint, we are telling the SCM, we will call + # it back within that period of time, so schedule ourselves. + if {$waithint} { + set delay [expr {($waithint*3)/4}] + after $delay ::twapi::_call_scm_within_waithint $name $current_state $service_state($name,checkpoint) + } + + return +} + + +# Queued to regularly update the SCM when we are in any of the pending states +proc ::twapi::_call_scm_within_waithint {name orig_state orig_checkpoint} { + variable service_state + + # We only call to update staus if the state and checkpoint have + # not changed since the routine was queued + if {($service_state($name,state) eq $orig_state) && + ($service_state($name,checkpoint) == $orig_checkpoint)} { + _report_service_status $name + } +} + + +################################################################ +# Utility procedures + +# Map an integer service type code into a list consisting of +# {SERVICETYPESYMBOL BOOLEAN}. If there is not symbolic service type +# for the service, just the integer code is returned. The BOOLEAN +# is 1/0 depending on whether the service type code is interactive +proc twapi::_map_servicetype_code {servicetype} { + # 0x100 -> SERVICE_INTERACTIVE_PROCESS + set interactive [expr {($servicetype & 0x100) != 0}] + set servicetype [expr {$servicetype & (~ 0x100)}] + set servicetype [kl_get [list \ + 16 win32_own_process \ + 32 win32_share_process \ + 80 user_own_process \ + 96 user_share_process \ + 1 kernel_driver \ + 2 file_system_driver \ + 4 adapter \ + 8 recognizer_driver \ + ] $servicetype $servicetype] + return [list $servicetype $interactive] +} + +# Map service type sym to int code +proc twapi::_map_servicetype_sym {sym} { + return [dict get {kernel_driver 1 file_system_driver 2 adapter 4 recognizer_driver 8 win32_own_process 16 win32_share_process 32 user_own_process 80 user_share_process 96} $sym] +} + +# Map a start type code into a symbol. Returns the integer code if +# no mapping possible +proc twapi::_map_starttype_code {code} { + incr code 0; # Make canonical int + set type [lindex {boot_start system_start auto_start demand_start disabled} $code] + if {$type eq ""} { + return $code + } else { + return $type + } +} + +# Map starttype sym to int code +proc twapi::_map_starttype_sym {sym} { + return [dict get {boot_start 0 system_start 1 auto_start 2 demand_start 3 disabled 4} $sym] +} + +# Map a error control code into a symbol. Returns the integer code if +# no mapping possible +proc twapi::_map_errorcontrol_code {code} { + incr code 0; # Make canonical int + set error [lindex {ignore normal severe critical} $code] + if {$error eq ""} { + return $code + } else { + return $error + } +} + +# Map error control sym to int code +proc twapi::_map_errorcontrol_sym {sym} { + return [dict get {ignore 0 normal 1 severe 2 critical 3} $sym] +} + +# Standard template for calling a service function. v_opts should refer +# to an array with the following elements: +# opts(system) - target system. Must be specified +# opts(database) - target database. Must be specified +# opts(scm_priv) - requested privilege when opening SCM. STANDARD_RIGHTS_READ +# is used if unspecified. Not used if scm_handle is specified +# opts(scm_handle) - handle to service control manager. Optional +# opts(svc_priv) - requested privilege when opening service. Must be present +# opts(proc) - proc/function to call. The first arg is the service handle +# opts(args) - additional arguments to pass to the function. +# Empty if unspecified +proc twapi::_service_fn_wrapper {name v_opts} { + upvar $v_opts opts + + # Use 0x00020000 -> STANDARD_RIGHTS_READ for SCM if not specified + set scm_priv [expr {[info exists opts(scm_priv)] ? $opts(scm_priv) : 0x00020000}] + + if {[info exists opts(scm_handle)] && + $opts(scm_handle) ne ""} { + set scm $opts(scm_handle) + } else { + set scm [OpenSCManager $opts(system) $opts(database) $scm_priv] } + trap { + set svch [OpenService $scm $name $opts(svc_priv)] + } finally { + # No need for scm handle anymore. Close it unless it was + # passed to us + if {(![info exists opts(scm_handle)]) || + ($opts(scm_handle) eq "")} { + CloseServiceHandle $scm + } + } + + set proc_args [expr {[info exists opts(args)] ? $opts(args) : ""}] + trap { + set results [eval [list $opts(proc) $svch] $proc_args] + } finally { + CloseServiceHandle $svch + } + + return $results +} + +# Called back for reporting background errors. Note this is called +# from the C++ services code, not from scripts. +proc twapi::_service_background_error {winerror msg} { + twapi::win32_error $winerror $msg +} + +# Parse symbols for controls accepted by a service +proc twapi::_parse_service_accept_controls {controls} { + return [_parse_symbolic_bitmask $controls { + stop 0x00000001 + pause_continue 0x00000002 + shutdown 0x00000004 + paramchange 0x00000008 + netbindchange 0x00000010 + hardwareprofilechange 0x00000020 + powerevent 0x00000040 + sessionchange 0x00000080 + }] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/share.tcl b/src/vendorlib_tcl8/twapi-5.0b1/share.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/share.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/share.tcl index 76809064..8f778682 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/share.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/share.tcl @@ -1,966 +1,966 @@ -# -# Copyright (c) 2003-2014, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - # Win SDK based structure definitions - - record SHARE_INFO_0 {-name} - record SHARE_INFO_1 {-name -type -comment} - record SHARE_INFO_2 {-name -type -comment -permissions -max_conn -current_conn -path -passwd} - record SHARE_INFO_502 {-name -type -comment -permissions -max_conn -current_conn -path -passwd -reserved -secd} - - record USE_INFO_0 {-localdevice -remoteshare} - record USE_INFO_1 {-localdevice -remoteshare -password -status -type -opencount -usecount} - record USE_INFO_2 {-localdevice -remoteshare -password -status -type -opencount -usecount -user -domain} - - record SESSION_INFO_0 {-clientname} - record SESSION_INFO_1 {-clientname -user -opencount -activeseconds -idleseconds -attrs} - record SESSION_INFO_2 {-clientname -user -opencount -activeseconds -idleseconds -attrs -clienttype} - record SESSION_INFO_502 {-clientname -user -opencount -activeseconds -idleseconds -attrs -clienttype -transport} - record SESSION_INFO_10 {-clientname -user -activeseconds -idleseconds} - - record FILE_INFO_2 {-id} - record FILE_INFO_3 {-id -permissions -lockcount -path -user} - - record CONNECTION_INFO_0 {-id} - record CONNECTION_INFO_1 {-id -type -opencount -usercount -activeseconds -user -netname} - - struct NETRESOURCE { - DWORD dwScope; - DWORD dwType; - DWORD dwDisplayType; - DWORD dwUsage; - LPCWSTR lpLocalName; - LPCWSTR lpRemoteName; - LPCWSTR lpComment; - LPCWSTR lpProvider; - }; - - struct NETINFOSTRUCT { - DWORD cbStructure; - DWORD dwProviderVersion; - DWORD dwStatus; - DWORD dwCharacteristics; - HANDLE dwHandle; - WORD wNetType; - DWORD dwPrinters; - DWORD dwDrives; - } -} - -# TBD - is there a Tcl wrapper around NetShareCheck? - -# Create a network share -proc twapi::new_share {sharename path args} { - array set opts [parseargs args { - {system.arg ""} - {type.arg "file"} - {comment.arg ""} - {max_conn.int -1} - secd.arg - } -maxleftover 0] - - # If no security descriptor specified, default to "Everyone, - # read permission". Levaing it empty will give everyone all permissions - # which is probably not a good idea! - if {![info exists opts(secd)]} { - set opts(secd) [new_security_descriptor -dacl [new_acl [list [new_ace allow S-1-1-0 1179817]]]] - } - - NetShareAdd $opts(system) \ - $sharename \ - [_share_type_symbols_to_code $opts(type)] \ - $opts(comment) \ - $opts(max_conn) \ - [file nativename $path] \ - $opts(secd) -} - -# Delete a network share -proc twapi::delete_share {sharename args} { - array set opts [parseargs args {system.arg} -nulldefault] - NetShareDel $opts(system) $sharename 0 -} - -# Enumerate network shares -proc twapi::get_shares {args} { - - array set opts [parseargs args { - {system.arg ""} - {type.arg ""} - excludespecial - level.int - } -maxleftover 0] - - if {$opts(type) != ""} { - set type_filter [_share_type_symbols_to_code $opts(type) 1] - } - - if {[info exists opts(level)] && $opts(level) > 0} { - set level $opts(level) - } else { - # Either -level not specified or specified as 0 - # We need at least level 1 to filter on type - set level 1 - } - - set record_proc SHARE_INFO_$level - set raw_data [_net_enum_helper NetShareEnum -system $opts(system) -level $level -fields [$record_proc]] - set recs [list ] - foreach rec [recordarray getlist $raw_data] { - # 0xC0000000 -> 0x80000000 (STYPE_SPECIAL), 0x40000000 (STYPE_TEMPORARY) - set special [expr {[$record_proc -type $rec] & 0xC0000000}] - if {$special && $opts(excludespecial)} { - continue - } - # We need the special cast to int because else operands get promoted - # to 64 bits as the hex is treated as an unsigned value - set share_type [$record_proc -type $rec] - if {[info exists type_filter] && [expr {int($share_type & ~ $special)}] != $type_filter} { - continue - } - set rec [$record_proc set $rec -type [_share_type_code_to_symbols $share_type]] - if {[info exists opts(level)]} { - lappend recs $rec - } else { - lappend recs [$record_proc -name $rec] - } - } - - if {[info exists opts(level)]} { - set ra [list [$record_proc] $recs] - if {$opts(level) == 0} { - # We actually need only a level 0 subset - return [recordarray get $ra -slice [SHARE_INFO_0]] - } - return $ra - } else { - return $recs - } -} - - -# Get details about a share -proc twapi::get_share_info {sharename args} { - array set opts [parseargs args { - system.arg - all - name - type - path - comment - max_conn - current_conn - secd - } -nulldefault -hyphenated] - - set level 0 - - if {$opts(-all) || $opts(-name) || $opts(-type) || $opts(-comment)} { - set level 1 - set record_proc SHARE_INFO_1 - } - - if {$opts(-all) || $opts(-max_conn) || $opts(-current_conn) || $opts(-path)} { - set level 2 - set record_proc SHARE_INFO_2 - } - - if {$opts(-all) || $opts(-secd)} { - set level 502 - set record_proc SHARE_INFO_502 - } - - if {! $level} { - return - } - - set rec [NetShareGetInfo $opts(-system) $sharename $level] - set result [list ] - foreach opt {-name -comment -max_conn -current_conn -path -secd} { - if {$opts(-all) || $opts($opt)} { - lappend result $opt [$record_proc $opt $rec] - } - } - if {$opts(-all) || $opts(-type)} { - lappend result -type [_share_type_code_to_symbols [$record_proc -type $rec]] - } - - return $result -} - - -# Set a share configuration -proc twapi::set_share_info {sharename args} { - array set opts [parseargs args { - {system.arg ""} - comment.arg - max_conn.int - secd.arg - }] - - # First get the current config so we can change specified fields - # and write back - array set shareinfo [get_share_info $sharename -system $opts(system) \ - -comment -max_conn -secd] - foreach field {comment max_conn secd} { - if {[info exists opts($field)]} { - set shareinfo(-$field) $opts($field) - } - } - - NetShareSetInfo $opts(system) $sharename $shareinfo(-comment) \ - $shareinfo(-max_conn) $shareinfo(-secd) -} - - -# Get list of remote shares -proc twapi::get_client_shares {args} { - array set opts [parseargs args { - {system.arg ""} - level.int - } -maxleftover 0] - - if {[info exists opts(level)]} { - set rec_proc USE_INFO_$opts(level) - set ra [_net_enum_helper NetUseEnum -system $opts(system) -level $opts(level) -fields [$rec_proc]] - set fields [$rec_proc] - set have_status [expr {"-status" in $fields}] - set have_type [expr {"-type" in $fields}] - if {! ($have_status || $have_type)} { - return $ra - } - set recs {} - foreach rec [recordarray getlist $ra] { - if {$have_status} { - set rec [$rec_proc set $rec -status [_map_useinfo_status [$rec_proc -status $rec]]] - } - if {$have_type} { - set rec [$rec_proc set $rec -type [_map_useinfo_type [$rec_proc -type $rec]]] - } - lappend recs $rec - } - return [list $fields $recs] - } - - # -level not specified. Just return a list of the remote share names - return [recordarray column [_net_enum_helper NetUseEnum -system $opts(system) -level 0 -fields [USE_INFO_0]] -remoteshare] -} - - -# Connect to a share -proc twapi::connect_share {remoteshare args} { - array set opts [parseargs args { - {type.arg "disk"} - localdevice.arg - provider.arg - password.arg - nopassword - defaultpassword - user.arg - {window.arg 0} - {interactive {} 0x8} - {prompt {} 0x10} - {updateprofile {} 0x1} - {commandline {} 0x800} - } -nulldefault] - - set flags 0 - - switch -exact -- $opts(type) { - "any" {set type 0} - "disk" - - "file" {set type 1} - "printer" {set type 2} - default { - error "Invalid network share type '$opts(type)'" - } - } - - # localdevice - "" means no local device, * means pick any, otherwise - # it's a local device to be mapped - if {$opts(localdevice) == "*"} { - set opts(localdevice) "" - setbits flags 0x80; # CONNECT_REDIRECT - } - - if {$opts(defaultpassword) && $opts(nopassword)} { - error "Options -defaultpassword and -nopassword may not be used together" - } - if {$opts(nopassword)} { - set opts(password) "" - set ignore_password 1 - } else { - set ignore_password 0 - if {$opts(defaultpassword)} { - set opts(password) "" - } - } - - set flags [expr {$flags | $opts(interactive) | $opts(prompt) | - $opts(updateprofile) | $opts(commandline)}] - - return [Twapi_WNetUseConnection $opts(window) $type $opts(localdevice) \ - $remoteshare $opts(provider) $opts(user) $ignore_password \ - $opts(password) $flags] -} - -# Disconnects an existing share -proc twapi::disconnect_share {sharename args} { - array set opts [parseargs args {updateprofile force}] - - set flags [expr {$opts(updateprofile) ? 0x1 : 0}] - WNetCancelConnection2 $sharename $flags $opts(force) -} - - -# Get information about a connected share -proc twapi::get_client_share_info {sharename args} { - if {$sharename eq ""} { - error "A share name cannot be the empty string" - } - - # We have to use a combination of NetUseGetInfo and - # WNetGetResourceInformation as neither gives us the full information - # THe former takes the local device name if there is one and will - # only accept a UNC if there is an entry for the UNC with - # no local device mapped. The latter - # always wants the UNC. So we need to figure out exactly if there - # is a local device mapped to the sharename or not - # TBD _ see if this is really the case. Also, NetUse only works with - # LANMAN, not WebDAV. So see if there is a way to only use WNet* - # variants - - # There may be multiple entries for the same UNC - # If there is an entry for the UNC with no device mapped, select - # that else select any of the local devices mapped to it - # TBD - any better way of finding out a mapping than calling - # get_client_shares? - # TBD - use wnet_connected_resources - foreach {elem_device elem_unc} [recordarray getlist [get_client_shares -level 0] -format flat] { - if {[string equal -nocase $sharename $elem_unc]} { - if {$elem_device eq ""} { - # Found an entry without a local device. Use it - set unc $elem_unc - unset -nocomplain local; # In case we found a match earlier - break - } else { - # Found a matching device - set local $elem_device - set unc $elem_unc - # Keep looping in case we find an entry with no local device - # (which we will prefer) - } - } else { - # See if the sharename is actually a local device name - if {[string equal -nocase [string trimright $elem_device :] [string trimright $sharename :]]} { - # Device name matches. Use it - set local $elem_device - set unc $elem_unc - break - } - } - } - - if {![info exists unc]} { - win32_error 2250 "Share '$sharename' not found." - } - - # At this point $unc is the UNC form of the share and - # $local is either undefined or the local mapped device if there is one - - array set opts [parseargs args { - user - localdevice - remoteshare - status - type - opencount - usecount - domain - provider - comment - all - } -maxleftover 0 -hyphenated] - - - # Call Twapi_NetGetInfo always to get status. If we are not connected, - # we will not call WNetGetResourceInformation as that will time out - if {[info exists local]} { - set share [NetUseGetInfo "" $local 2] - } else { - set share [NetUseGetInfo "" $unc 2] - } - array set shareinfo [USE_INFO_2 $share] - unset shareinfo(-password) - if {[info exists shareinfo(-status)]} { - set shareinfo(-status) [_map_useinfo_status $shareinfo(-status)] - } - if {[info exists shareinfo(-type)]} { - set shareinfo(-type) [_map_useinfo_type $shareinfo(-type)] - } - - if {$opts(-all) || $opts(-comment) || $opts(-provider)} { - # Only get this information if we are connected - if {$shareinfo(-status) eq "connected"} { - set wnetinfo [lindex [Twapi_WNetGetResourceInformation $unc "" 0] 0] - set shareinfo(-comment) [lindex $wnetinfo 6] - set shareinfo(-provider) [lindex $wnetinfo 7] - } else { - set shareinfo(-comment) "" - set shareinfo(-provider) "" - } - } - - if {$opts(-all)} { - return [array get shareinfo] - } - - # Get rid of unwanted fields - foreach opt { - -user - -localdevice - -remoteshare - -status - -type - -opencount - -usecount - -domain - -provider - -comment - } { - if {! $opts($opt)} { - unset -nocomplain shareinfo($opt) - } - } - - return [array get shareinfo] -} - - -# Enumerate sessions -proc twapi::find_lm_sessions args { - array set opts [parseargs args { - all - {matchclient.arg ""} - {system.arg ""} - {matchuser.arg ""} - transport - clientname - user - clienttype - opencount - idleseconds - activeseconds - attrs - } -maxleftover 0] - - set level [_calc_minimum_session_info_level opts] - - # On all platforms, client must be in UNC format - set opts(matchclient) [_make_unc_computername $opts(matchclient)] - - trap { - set sessions [_net_enum_helper NetSessionEnum -system $opts(system) -preargs [list $opts(matchclient) $opts(matchuser)] -level $level -fields [SESSION_INFO_$level]] - } onerror {TWAPI_WIN32 2312} { - # No session matching the specified client - set sessions {} - } onerror {TWAPI_WIN32 2221} { - # No session matching the user - set sessions {} - } - - return [_format_lm_sessions $sessions opts] -} - - -# Get information about a session -proc twapi::get_lm_session_info {client user args} { - array set opts [parseargs args { - all - {system.arg ""} - transport - clientname - user - clienttype - opencount - idleseconds - activeseconds - attrs - } -maxleftover 0] - - set level [_calc_minimum_session_info_level opts] - if {$level == -1} { - # No data requested so return empty list - return [list ] - } - - if {![min_os_version 5]} { - # System name is specified. If NT, make sure it is UNC form - set opts(system) [_make_unc_computername $opts(system)] - } - - # On all platforms, client must be in UNC format - set client [_make_unc_computername $client] - - # Note an error is generated if no matching session exists - set sess [NetSessionGetInfo $opts(system) $client $user $level] - - return [recordarray index [_format_lm_sessions [list [SESSION_INFO_$level] [list $sess]] opts] 0 -format dict] -} - -# Delete sessions -proc twapi::end_lm_sessions args { - array set opts [parseargs args { - {client.arg ""} - {system.arg ""} - {user.arg ""} - } -maxleftover 0] - - if {![min_os_version 5]} { - # System name is specified. If NT, make sure it is UNC form - set opts(system) [_make_unc_computername $opts(system)] - } - - if {$opts(client) eq "" && $opts(user) eq ""} { - win32_error 87 "At least one of -client and -user must be specified." - } - - # On all platforms, client must be in UNC format - set opts(client) [_make_unc_computername $opts(client)] - - trap { - NetSessionDel $opts(system) $opts(client) $opts(user) - } onerror {TWAPI_WIN32 2312} { - # No session matching the specified client - ignore error - } onerror {TWAPI_WIN32 2221} { - # No session matching the user - ignore error - } - return -} - -# Enumerate open files -proc twapi::find_lm_open_files args { - array set opts [parseargs args { - {basepath.arg ""} - {system.arg ""} - {matchuser.arg ""} - all - permissions - id - lockcount - path - user - } -maxleftover 0] - - set level 3 - if {! ($opts(all) || $opts(permissions) || $opts(lockcount) || - $opts(path) || $opts(user))} { - # Only id's required - set level 2 - } - - # TBD - change to use -resume option to _net_enum_helper as there - # might be a lot of files - trap { - set files [_net_enum_helper NetFileEnum -system $opts(system) -preargs [list [file nativename $opts(basepath)] $opts(matchuser)] -level $level -fields [FILE_INFO_$level]] - } onerror {TWAPI_WIN32 2221} { - # No files matching the user - set files [list [FILE_INFO_$level] {}] - } - - return [_format_lm_open_files $files opts] -} - -# Get information about an open LM file -proc twapi::get_lm_open_file_info {fid args} { - array set opts [parseargs args { - {system.arg ""} - all - permissions - id - lockcount - path - user - } -maxleftover 0] - - # System name is specified. If NT, make sure it is UNC form - if {![min_os_version 5]} { - set opts(system) [_make_unc_computername $opts(system)] - } - - set level 3 - if {! ($opts(all) || $opts(permissions) || $opts(lockcount) || - $opts(path) || $opts(user))} { - # Only id's required. We actually already have this but don't - # return it since we want to go ahead and make the call in case - # the id does not exist - set level 2 - } - - return [recordarray index [_format_lm_open_files [list [FILE_INFO_$level] [list [NetFileGetInfo $opts(system) $fid $level]]] opts] 0 -format dict] -} - -# Close an open LM file -proc twapi::close_lm_open_file {fid args} { - array set opts [parseargs args { - {system.arg ""} - } -maxleftover 0] - trap { - NetFileClose $opts(system) $fid - } onerror {TWAPI_WIN32 2314} { - # No such fid. Ignore, perhaps it was closed in the meanwhile - } -} - - -# Enumerate open connections -proc twapi::find_lm_connections args { - array set opts [parseargs args { - client.arg - {system.arg ""} - share.arg - all - id - type - opencount - usercount - activeseconds - user - clientname - sharename - } -maxleftover 0] - - if {! ([info exists opts(client)] || [info exists opts(share)])} { - win32_error 87 "Must specify either -client or -share option." - } - - if {[info exists opts(client)] && [info exists opts(share)]} { - win32_error 87 "Must not specify both -client and -share options." - } - - if {[info exists opts(client)]} { - set qualifier [_make_unc_computername $opts(client)] - } else { - set qualifier $opts(share) - } - - set level 0 - if {$opts(all) || $opts(type) || $opts(opencount) || - $opts(usercount) || $opts(user) || - $opts(activeseconds) || $opts(clientname) || $opts(sharename)} { - set level 1 - } - - # TBD - change to use -resume option to _net_enum_helper since - # there might be a log of connections - set conns [_net_enum_helper NetConnectionEnum -system $opts(system) -preargs [list $qualifier] -level $level -fields [CONNECTION_INFO_$level]] - - # NOTE fields MUST BE IN SAME ORDER AS VALUES BELOW - if {! $opts(all)} { - set fields {} - foreach opt {id opencount usercount activeseconds user type} { - if {$opts(all) || $opts($opt)} { - lappend fields -$opt - } - } - if {$opts(all) || $opts(clientname) || $opts(sharename)} { - lappend fields -netname - } - set conns [recordarray get $conns -slice $fields] - } - set fields [recordarray fields $conns] - if {"-type" in $fields} { - set type_enum [enum $fields -type] - } - if {"-netname" in $fields} { - set netname_enum [enum $fields -netname] - } - - if {! ([info exists type_enum] || [info exists netname_enum])} { - # No need to massage any data - return $conns - } - - set recs {} - foreach rec [recordarray getlist $conns] { - if {[info exists type_enum]} { - lset rec $type_enum [_share_type_code_to_symbols [lindex $rec $type_enum]] - } - if {[info exists netname_enum]} { - # What's returned in the netname field depends on what we - # passed as the qualifier - if {[info exists opts(client)]} { - set sharename [lindex $rec $netname_enum] - set clientname [_make_unc_computername $opts(client)] - } else { - set sharename $opts(share) - set clientname [_make_unc_computername [lindex $rec $netname_enum]] - } - if {$opts(all) || $opts(clientname)} { - lappend rec $clientname - } - if {$opts(all) || $opts(sharename)} { - lappend rec $sharename - } - } - lappend recs $rec - } - if {$opts(all) || $opts(clientname)} { - lappend fields -clientname - } - if {$opts(all) || $opts(sharename)} { - lappend fields -sharename - } - - return [list $fields $recs] -} - -proc twapi::wnet_connected_resources {args} { - # Accept both file/disk and print/printer for historical reasons - # file and printer are official to match get_client_share_info - parseargs args { - {type.sym any {any 0 file 1 disk 1 print 2 printer 2}} - } -maxleftover 0 -setvars - set h [WNetOpenEnum 1 $type 0 ""] - trap { - set resources {} - set structdef [twapi::NETRESOURCE] - while {[llength [set rs [WNetEnumResource $h 100 $structdef]]]} { - foreach r $rs { - lappend resources [lrange $r 4 5] - } - } - } finally { - WNetCloseEnum $h - } - return $resources -} - -################################################################ -# Utility functions - -# Common code to figure out what SESSION_INFO level is required -# for the specified set of requested fields. v_opts is name -# of array indicating which fields are required -proc twapi::_calc_minimum_session_info_level {v_opts} { - upvar $v_opts opts - - # Set the information level requested based on options specified. - # We set the level to the one that requires the lowest possible - # privilege level and still includes the data requested. - if {$opts(all) || $opts(transport)} { - return 502 - } elseif {$opts(clienttype)} { - return 2 - } elseif {$opts(opencount) || $opts(attrs)} { - return 1 - } elseif {$opts(clientname) || $opts(user) || - $opts(idleseconds) || $opts(activeseconds)} { - return 10 - } else { - return 0 - } -} - -# Common code to format a session record. v_opts is name of array -# that controls which fields are returned -# sessions is a record array -proc twapi::_format_lm_sessions {sessions v_opts} { - upvar $v_opts opts - - if {! $opts(all)} { - set fields {} - foreach opt { - transport user opencount idleseconds activeseconds - clienttype clientname attrs - } { - if {$opts(all) || $opts($opt)} { - lappend fields -$opt - } - } - set sessions [recordarray get $sessions -slice $fields] - } - - set fields [recordarray fields $sessions] - if {"-clientname" in $fields} { - set client_enum [enum $fields -clientname] - } - if {"-attrs" in $fields} { - set attrs_enum [enum $fields -attrs] - } - - if {! ([info exists client_enum] || [info exists attrs_enum])} { - return $sessions - } - - # Need to map client name and attrs fields - set recs {} - foreach rec [recordarray getlist $sessions] { - if {[info exists client_enum]} { - lset rec $client_enum [_make_unc_computername [lindex $rec $client_enum]] - } - if {[info exists attrs_enum]} { - set attrs {} - set flags [lindex $rec $attrs_enum] - if {$flags & 1} { - lappend attrs guest - } - if {$flags & 2} { - lappend attrs noencryption - } - lset rec $attrs_enum $attrs - } - lappend recs $rec - } - return [list $fields $recs] -} - -# Common code to format a lm open file record. v_opts is name of array -# that controls which fields are returned -proc twapi::_format_lm_open_files {files v_opts} { - upvar $v_opts opts - - if {! $opts(all)} { - set fields {} - foreach opt { - id lockcount path user permissions - } { - if {$opts(all) || $opts($opt)} { - lappend fields -$opt - } - } - set files [recordarray get $files -slice $fields] - } - - set fields [recordarray fields $files] - - if {"-permissions" ni $fields} { - return $files - } - - # Need to massage permissions - set enum [enum $fields -permissions] - - set recs {} - foreach rec [recordarray getlist $files] { - set permissions [list ] - set perms [lindex $rec $enum] - foreach {flag perm} {1 read 2 write 4 create} { - if {$perms & $flag} { - lappend permissions $perm - } - } - lset rec $enum $permissions - lappend recs $rec - } - - return [list $fields $recs] -} - -# NOTE: THIS ONLY MAPS FOR THE Net* functions, NOT THE WNet* -proc twapi::_share_type_symbols_to_code {typesyms {basetypeonly 0}} { - - # STYPE_DISKTREE 0 - # STYPE_PRINTQ 1 - # STYPE_DEVICE 2 - # STYPE_IPC 3 - switch -exact -- [lindex $typesyms 0] { - file { set code 0 } - printer { set code 1 } - device { set code 2 } - ipc { set code 3 } - default { - error "Unknown type network share type symbol [lindex $typesyms 0]" - } - } - - if {$basetypeonly} { - return $code - } - - # STYPE_TEMPORARY 0x40000000 - # STYPE_SPECIAL 0x80000000 - set special 0 - foreach sym [lrange $typesyms 1 end] { - switch -exact -- $sym { - special { setbits special 0x80000000 } - temporary { setbits special 0x40000000 } - file - - printer - - device - - ipc { - error "Base share type symbol '$sym' cannot be used as a share attribute type" - } - default { - error "Unknown type network share type symbol '$sym'" - } - } - } - - return [expr {$code | $special}] -} - - -# First element is always the base type of the share -# NOTE: THIS ONLY MAPS FOR THE Net* functions, NOT THE WNet* -proc twapi::_share_type_code_to_symbols {type} { - - # STYPE_DISKTREE 0 - # STYPE_PRINTQ 1 - # STYPE_DEVICE 2 - # STYPE_IPC 3 - # STYPE_TEMPORARY 0x40000000 - # STYPE_SPECIAL 0x80000000 - - set special [expr {$type & 0xC0000000}] - - # We need the special cast to int because else operands get promoted - # to 64 bits as the hex is treated as an unsigned value - switch -exact -- [expr {int($type & ~ $special)}] { - 0 {set sym "file"} - 1 {set sym "printer"} - 2 {set sym "device"} - 3 {set sym "ipc"} - default {set sym $type} - } - - set typesyms [list $sym] - - if {$special & 0x80000000} { - lappend typesyms special - } - - if {$special & 0x40000000} { - lappend typesyms temporary - } - - return $typesyms -} - -# Make sure a computer name is in unc format unless it is an empty -# string (local computer) -proc twapi::_make_unc_computername {name} { - if {$name eq ""} { - return "" - } else { - return "\\\\[string trimleft $name \\]" - } -} - -proc twapi::_map_useinfo_status {status} { - set sym [lindex {connected paused lostsession disconnected networkerror connecting reconnecting} $status] - if {$sym ne ""} { - return $sym - } else { - return $status - } -} - -proc twapi::_map_useinfo_type {type} { - # Note share type and use info types are different - return [_share_type_code_to_symbols [expr {$type & 0x3fffffff}]] -} +# +# Copyright (c) 2003-2014, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + # Win SDK based structure definitions + + record SHARE_INFO_0 {-name} + record SHARE_INFO_1 {-name -type -comment} + record SHARE_INFO_2 {-name -type -comment -permissions -max_conn -current_conn -path -passwd} + record SHARE_INFO_502 {-name -type -comment -permissions -max_conn -current_conn -path -passwd -reserved -secd} + + record USE_INFO_0 {-localdevice -remoteshare} + record USE_INFO_1 {-localdevice -remoteshare -password -status -type -opencount -usecount} + record USE_INFO_2 {-localdevice -remoteshare -password -status -type -opencount -usecount -user -domain} + + record SESSION_INFO_0 {-clientname} + record SESSION_INFO_1 {-clientname -user -opencount -activeseconds -idleseconds -attrs} + record SESSION_INFO_2 {-clientname -user -opencount -activeseconds -idleseconds -attrs -clienttype} + record SESSION_INFO_502 {-clientname -user -opencount -activeseconds -idleseconds -attrs -clienttype -transport} + record SESSION_INFO_10 {-clientname -user -activeseconds -idleseconds} + + record FILE_INFO_2 {-id} + record FILE_INFO_3 {-id -permissions -lockcount -path -user} + + record CONNECTION_INFO_0 {-id} + record CONNECTION_INFO_1 {-id -type -opencount -usercount -activeseconds -user -netname} + + struct NETRESOURCE { + DWORD dwScope; + DWORD dwType; + DWORD dwDisplayType; + DWORD dwUsage; + LPCWSTR lpLocalName; + LPCWSTR lpRemoteName; + LPCWSTR lpComment; + LPCWSTR lpProvider; + }; + + struct NETINFOSTRUCT { + DWORD cbStructure; + DWORD dwProviderVersion; + DWORD dwStatus; + DWORD dwCharacteristics; + HANDLE dwHandle; + WORD wNetType; + DWORD dwPrinters; + DWORD dwDrives; + } +} + +# TBD - is there a Tcl wrapper around NetShareCheck? + +# Create a network share +proc twapi::new_share {sharename path args} { + array set opts [parseargs args { + {system.arg ""} + {type.arg "file"} + {comment.arg ""} + {max_conn.int -1} + secd.arg + } -maxleftover 0] + + # If no security descriptor specified, default to "Everyone, + # read permission". Levaing it empty will give everyone all permissions + # which is probably not a good idea! + if {![info exists opts(secd)]} { + set opts(secd) [new_security_descriptor -dacl [new_acl [list [new_ace allow S-1-1-0 1179817]]]] + } + + NetShareAdd $opts(system) \ + $sharename \ + [_share_type_symbols_to_code $opts(type)] \ + $opts(comment) \ + $opts(max_conn) \ + [file nativename $path] \ + $opts(secd) +} + +# Delete a network share +proc twapi::delete_share {sharename args} { + array set opts [parseargs args {system.arg} -nulldefault] + NetShareDel $opts(system) $sharename 0 +} + +# Enumerate network shares +proc twapi::get_shares {args} { + + array set opts [parseargs args { + {system.arg ""} + {type.arg ""} + excludespecial + level.int + } -maxleftover 0] + + if {$opts(type) != ""} { + set type_filter [_share_type_symbols_to_code $opts(type) 1] + } + + if {[info exists opts(level)] && $opts(level) > 0} { + set level $opts(level) + } else { + # Either -level not specified or specified as 0 + # We need at least level 1 to filter on type + set level 1 + } + + set record_proc SHARE_INFO_$level + set raw_data [_net_enum_helper NetShareEnum -system $opts(system) -level $level -fields [$record_proc]] + set recs [list ] + foreach rec [recordarray getlist $raw_data] { + # 0xC0000000 -> 0x80000000 (STYPE_SPECIAL), 0x40000000 (STYPE_TEMPORARY) + set special [expr {[$record_proc -type $rec] & 0xC0000000}] + if {$special && $opts(excludespecial)} { + continue + } + # We need the special cast to int because else operands get promoted + # to 64 bits as the hex is treated as an unsigned value + set share_type [$record_proc -type $rec] + if {[info exists type_filter] && [expr {int($share_type & ~ $special)}] != $type_filter} { + continue + } + set rec [$record_proc set $rec -type [_share_type_code_to_symbols $share_type]] + if {[info exists opts(level)]} { + lappend recs $rec + } else { + lappend recs [$record_proc -name $rec] + } + } + + if {[info exists opts(level)]} { + set ra [list [$record_proc] $recs] + if {$opts(level) == 0} { + # We actually need only a level 0 subset + return [recordarray get $ra -slice [SHARE_INFO_0]] + } + return $ra + } else { + return $recs + } +} + + +# Get details about a share +proc twapi::get_share_info {sharename args} { + array set opts [parseargs args { + system.arg + all + name + type + path + comment + max_conn + current_conn + secd + } -nulldefault -hyphenated] + + set level 0 + + if {$opts(-all) || $opts(-name) || $opts(-type) || $opts(-comment)} { + set level 1 + set record_proc SHARE_INFO_1 + } + + if {$opts(-all) || $opts(-max_conn) || $opts(-current_conn) || $opts(-path)} { + set level 2 + set record_proc SHARE_INFO_2 + } + + if {$opts(-all) || $opts(-secd)} { + set level 502 + set record_proc SHARE_INFO_502 + } + + if {! $level} { + return + } + + set rec [NetShareGetInfo $opts(-system) $sharename $level] + set result [list ] + foreach opt {-name -comment -max_conn -current_conn -path -secd} { + if {$opts(-all) || $opts($opt)} { + lappend result $opt [$record_proc $opt $rec] + } + } + if {$opts(-all) || $opts(-type)} { + lappend result -type [_share_type_code_to_symbols [$record_proc -type $rec]] + } + + return $result +} + + +# Set a share configuration +proc twapi::set_share_info {sharename args} { + array set opts [parseargs args { + {system.arg ""} + comment.arg + max_conn.int + secd.arg + }] + + # First get the current config so we can change specified fields + # and write back + array set shareinfo [get_share_info $sharename -system $opts(system) \ + -comment -max_conn -secd] + foreach field {comment max_conn secd} { + if {[info exists opts($field)]} { + set shareinfo(-$field) $opts($field) + } + } + + NetShareSetInfo $opts(system) $sharename $shareinfo(-comment) \ + $shareinfo(-max_conn) $shareinfo(-secd) +} + + +# Get list of remote shares +proc twapi::get_client_shares {args} { + array set opts [parseargs args { + {system.arg ""} + level.int + } -maxleftover 0] + + if {[info exists opts(level)]} { + set rec_proc USE_INFO_$opts(level) + set ra [_net_enum_helper NetUseEnum -system $opts(system) -level $opts(level) -fields [$rec_proc]] + set fields [$rec_proc] + set have_status [expr {"-status" in $fields}] + set have_type [expr {"-type" in $fields}] + if {! ($have_status || $have_type)} { + return $ra + } + set recs {} + foreach rec [recordarray getlist $ra] { + if {$have_status} { + set rec [$rec_proc set $rec -status [_map_useinfo_status [$rec_proc -status $rec]]] + } + if {$have_type} { + set rec [$rec_proc set $rec -type [_map_useinfo_type [$rec_proc -type $rec]]] + } + lappend recs $rec + } + return [list $fields $recs] + } + + # -level not specified. Just return a list of the remote share names + return [recordarray column [_net_enum_helper NetUseEnum -system $opts(system) -level 0 -fields [USE_INFO_0]] -remoteshare] +} + + +# Connect to a share +proc twapi::connect_share {remoteshare args} { + array set opts [parseargs args { + {type.arg "disk"} + localdevice.arg + provider.arg + password.arg + nopassword + defaultpassword + user.arg + {window.arg 0} + {interactive {} 0x8} + {prompt {} 0x10} + {updateprofile {} 0x1} + {commandline {} 0x800} + } -nulldefault] + + set flags 0 + + switch -exact -- $opts(type) { + "any" {set type 0} + "disk" - + "file" {set type 1} + "printer" {set type 2} + default { + error "Invalid network share type '$opts(type)'" + } + } + + # localdevice - "" means no local device, * means pick any, otherwise + # it's a local device to be mapped + if {$opts(localdevice) == "*"} { + set opts(localdevice) "" + setbits flags 0x80; # CONNECT_REDIRECT + } + + if {$opts(defaultpassword) && $opts(nopassword)} { + error "Options -defaultpassword and -nopassword may not be used together" + } + if {$opts(nopassword)} { + set opts(password) "" + set ignore_password 1 + } else { + set ignore_password 0 + if {$opts(defaultpassword)} { + set opts(password) "" + } + } + + set flags [expr {$flags | $opts(interactive) | $opts(prompt) | + $opts(updateprofile) | $opts(commandline)}] + + return [Twapi_WNetUseConnection $opts(window) $type $opts(localdevice) \ + $remoteshare $opts(provider) $opts(user) $ignore_password \ + $opts(password) $flags] +} + +# Disconnects an existing share +proc twapi::disconnect_share {sharename args} { + array set opts [parseargs args {updateprofile force}] + + set flags [expr {$opts(updateprofile) ? 0x1 : 0}] + WNetCancelConnection2 $sharename $flags $opts(force) +} + + +# Get information about a connected share +proc twapi::get_client_share_info {sharename args} { + if {$sharename eq ""} { + error "A share name cannot be the empty string" + } + + # We have to use a combination of NetUseGetInfo and + # WNetGetResourceInformation as neither gives us the full information + # THe former takes the local device name if there is one and will + # only accept a UNC if there is an entry for the UNC with + # no local device mapped. The latter + # always wants the UNC. So we need to figure out exactly if there + # is a local device mapped to the sharename or not + # TBD _ see if this is really the case. Also, NetUse only works with + # LANMAN, not WebDAV. So see if there is a way to only use WNet* + # variants + + # There may be multiple entries for the same UNC + # If there is an entry for the UNC with no device mapped, select + # that else select any of the local devices mapped to it + # TBD - any better way of finding out a mapping than calling + # get_client_shares? + # TBD - use wnet_connected_resources + foreach {elem_device elem_unc} [recordarray getlist [get_client_shares -level 0] -format flat] { + if {[string equal -nocase $sharename $elem_unc]} { + if {$elem_device eq ""} { + # Found an entry without a local device. Use it + set unc $elem_unc + unset -nocomplain local; # In case we found a match earlier + break + } else { + # Found a matching device + set local $elem_device + set unc $elem_unc + # Keep looping in case we find an entry with no local device + # (which we will prefer) + } + } else { + # See if the sharename is actually a local device name + if {[string equal -nocase [string trimright $elem_device :] [string trimright $sharename :]]} { + # Device name matches. Use it + set local $elem_device + set unc $elem_unc + break + } + } + } + + if {![info exists unc]} { + win32_error 2250 "Share '$sharename' not found." + } + + # At this point $unc is the UNC form of the share and + # $local is either undefined or the local mapped device if there is one + + array set opts [parseargs args { + user + localdevice + remoteshare + status + type + opencount + usecount + domain + provider + comment + all + } -maxleftover 0 -hyphenated] + + + # Call Twapi_NetGetInfo always to get status. If we are not connected, + # we will not call WNetGetResourceInformation as that will time out + if {[info exists local]} { + set share [NetUseGetInfo "" $local 2] + } else { + set share [NetUseGetInfo "" $unc 2] + } + array set shareinfo [USE_INFO_2 $share] + unset shareinfo(-password) + if {[info exists shareinfo(-status)]} { + set shareinfo(-status) [_map_useinfo_status $shareinfo(-status)] + } + if {[info exists shareinfo(-type)]} { + set shareinfo(-type) [_map_useinfo_type $shareinfo(-type)] + } + + if {$opts(-all) || $opts(-comment) || $opts(-provider)} { + # Only get this information if we are connected + if {$shareinfo(-status) eq "connected"} { + set wnetinfo [lindex [Twapi_WNetGetResourceInformation $unc "" 0] 0] + set shareinfo(-comment) [lindex $wnetinfo 6] + set shareinfo(-provider) [lindex $wnetinfo 7] + } else { + set shareinfo(-comment) "" + set shareinfo(-provider) "" + } + } + + if {$opts(-all)} { + return [array get shareinfo] + } + + # Get rid of unwanted fields + foreach opt { + -user + -localdevice + -remoteshare + -status + -type + -opencount + -usecount + -domain + -provider + -comment + } { + if {! $opts($opt)} { + unset -nocomplain shareinfo($opt) + } + } + + return [array get shareinfo] +} + + +# Enumerate sessions +proc twapi::find_lm_sessions args { + array set opts [parseargs args { + all + {matchclient.arg ""} + {system.arg ""} + {matchuser.arg ""} + transport + clientname + user + clienttype + opencount + idleseconds + activeseconds + attrs + } -maxleftover 0] + + set level [_calc_minimum_session_info_level opts] + + # On all platforms, client must be in UNC format + set opts(matchclient) [_make_unc_computername $opts(matchclient)] + + trap { + set sessions [_net_enum_helper NetSessionEnum -system $opts(system) -preargs [list $opts(matchclient) $opts(matchuser)] -level $level -fields [SESSION_INFO_$level]] + } onerror {TWAPI_WIN32 2312} { + # No session matching the specified client + set sessions {} + } onerror {TWAPI_WIN32 2221} { + # No session matching the user + set sessions {} + } + + return [_format_lm_sessions $sessions opts] +} + + +# Get information about a session +proc twapi::get_lm_session_info {client user args} { + array set opts [parseargs args { + all + {system.arg ""} + transport + clientname + user + clienttype + opencount + idleseconds + activeseconds + attrs + } -maxleftover 0] + + set level [_calc_minimum_session_info_level opts] + if {$level == -1} { + # No data requested so return empty list + return [list ] + } + + if {![min_os_version 5]} { + # System name is specified. If NT, make sure it is UNC form + set opts(system) [_make_unc_computername $opts(system)] + } + + # On all platforms, client must be in UNC format + set client [_make_unc_computername $client] + + # Note an error is generated if no matching session exists + set sess [NetSessionGetInfo $opts(system) $client $user $level] + + return [recordarray index [_format_lm_sessions [list [SESSION_INFO_$level] [list $sess]] opts] 0 -format dict] +} + +# Delete sessions +proc twapi::end_lm_sessions args { + array set opts [parseargs args { + {client.arg ""} + {system.arg ""} + {user.arg ""} + } -maxleftover 0] + + if {![min_os_version 5]} { + # System name is specified. If NT, make sure it is UNC form + set opts(system) [_make_unc_computername $opts(system)] + } + + if {$opts(client) eq "" && $opts(user) eq ""} { + win32_error 87 "At least one of -client and -user must be specified." + } + + # On all platforms, client must be in UNC format + set opts(client) [_make_unc_computername $opts(client)] + + trap { + NetSessionDel $opts(system) $opts(client) $opts(user) + } onerror {TWAPI_WIN32 2312} { + # No session matching the specified client - ignore error + } onerror {TWAPI_WIN32 2221} { + # No session matching the user - ignore error + } + return +} + +# Enumerate open files +proc twapi::find_lm_open_files args { + array set opts [parseargs args { + {basepath.arg ""} + {system.arg ""} + {matchuser.arg ""} + all + permissions + id + lockcount + path + user + } -maxleftover 0] + + set level 3 + if {! ($opts(all) || $opts(permissions) || $opts(lockcount) || + $opts(path) || $opts(user))} { + # Only id's required + set level 2 + } + + # TBD - change to use -resume option to _net_enum_helper as there + # might be a lot of files + trap { + set files [_net_enum_helper NetFileEnum -system $opts(system) -preargs [list [file nativename $opts(basepath)] $opts(matchuser)] -level $level -fields [FILE_INFO_$level]] + } onerror {TWAPI_WIN32 2221} { + # No files matching the user + set files [list [FILE_INFO_$level] {}] + } + + return [_format_lm_open_files $files opts] +} + +# Get information about an open LM file +proc twapi::get_lm_open_file_info {fid args} { + array set opts [parseargs args { + {system.arg ""} + all + permissions + id + lockcount + path + user + } -maxleftover 0] + + # System name is specified. If NT, make sure it is UNC form + if {![min_os_version 5]} { + set opts(system) [_make_unc_computername $opts(system)] + } + + set level 3 + if {! ($opts(all) || $opts(permissions) || $opts(lockcount) || + $opts(path) || $opts(user))} { + # Only id's required. We actually already have this but don't + # return it since we want to go ahead and make the call in case + # the id does not exist + set level 2 + } + + return [recordarray index [_format_lm_open_files [list [FILE_INFO_$level] [list [NetFileGetInfo $opts(system) $fid $level]]] opts] 0 -format dict] +} + +# Close an open LM file +proc twapi::close_lm_open_file {fid args} { + array set opts [parseargs args { + {system.arg ""} + } -maxleftover 0] + trap { + NetFileClose $opts(system) $fid + } onerror {TWAPI_WIN32 2314} { + # No such fid. Ignore, perhaps it was closed in the meanwhile + } +} + + +# Enumerate open connections +proc twapi::find_lm_connections args { + array set opts [parseargs args { + client.arg + {system.arg ""} + share.arg + all + id + type + opencount + usercount + activeseconds + user + clientname + sharename + } -maxleftover 0] + + if {! ([info exists opts(client)] || [info exists opts(share)])} { + win32_error 87 "Must specify either -client or -share option." + } + + if {[info exists opts(client)] && [info exists opts(share)]} { + win32_error 87 "Must not specify both -client and -share options." + } + + if {[info exists opts(client)]} { + set qualifier [_make_unc_computername $opts(client)] + } else { + set qualifier $opts(share) + } + + set level 0 + if {$opts(all) || $opts(type) || $opts(opencount) || + $opts(usercount) || $opts(user) || + $opts(activeseconds) || $opts(clientname) || $opts(sharename)} { + set level 1 + } + + # TBD - change to use -resume option to _net_enum_helper since + # there might be a log of connections + set conns [_net_enum_helper NetConnectionEnum -system $opts(system) -preargs [list $qualifier] -level $level -fields [CONNECTION_INFO_$level]] + + # NOTE fields MUST BE IN SAME ORDER AS VALUES BELOW + if {! $opts(all)} { + set fields {} + foreach opt {id opencount usercount activeseconds user type} { + if {$opts(all) || $opts($opt)} { + lappend fields -$opt + } + } + if {$opts(all) || $opts(clientname) || $opts(sharename)} { + lappend fields -netname + } + set conns [recordarray get $conns -slice $fields] + } + set fields [recordarray fields $conns] + if {"-type" in $fields} { + set type_enum [enum $fields -type] + } + if {"-netname" in $fields} { + set netname_enum [enum $fields -netname] + } + + if {! ([info exists type_enum] || [info exists netname_enum])} { + # No need to massage any data + return $conns + } + + set recs {} + foreach rec [recordarray getlist $conns] { + if {[info exists type_enum]} { + lset rec $type_enum [_share_type_code_to_symbols [lindex $rec $type_enum]] + } + if {[info exists netname_enum]} { + # What's returned in the netname field depends on what we + # passed as the qualifier + if {[info exists opts(client)]} { + set sharename [lindex $rec $netname_enum] + set clientname [_make_unc_computername $opts(client)] + } else { + set sharename $opts(share) + set clientname [_make_unc_computername [lindex $rec $netname_enum]] + } + if {$opts(all) || $opts(clientname)} { + lappend rec $clientname + } + if {$opts(all) || $opts(sharename)} { + lappend rec $sharename + } + } + lappend recs $rec + } + if {$opts(all) || $opts(clientname)} { + lappend fields -clientname + } + if {$opts(all) || $opts(sharename)} { + lappend fields -sharename + } + + return [list $fields $recs] +} + +proc twapi::wnet_connected_resources {args} { + # Accept both file/disk and print/printer for historical reasons + # file and printer are official to match get_client_share_info + parseargs args { + {type.sym any {any 0 file 1 disk 1 print 2 printer 2}} + } -maxleftover 0 -setvars + set h [WNetOpenEnum 1 $type 0 ""] + trap { + set resources {} + set structdef [twapi::NETRESOURCE] + while {[llength [set rs [WNetEnumResource $h 100 $structdef]]]} { + foreach r $rs { + lappend resources [lrange $r 4 5] + } + } + } finally { + WNetCloseEnum $h + } + return $resources +} + +################################################################ +# Utility functions + +# Common code to figure out what SESSION_INFO level is required +# for the specified set of requested fields. v_opts is name +# of array indicating which fields are required +proc twapi::_calc_minimum_session_info_level {v_opts} { + upvar $v_opts opts + + # Set the information level requested based on options specified. + # We set the level to the one that requires the lowest possible + # privilege level and still includes the data requested. + if {$opts(all) || $opts(transport)} { + return 502 + } elseif {$opts(clienttype)} { + return 2 + } elseif {$opts(opencount) || $opts(attrs)} { + return 1 + } elseif {$opts(clientname) || $opts(user) || + $opts(idleseconds) || $opts(activeseconds)} { + return 10 + } else { + return 0 + } +} + +# Common code to format a session record. v_opts is name of array +# that controls which fields are returned +# sessions is a record array +proc twapi::_format_lm_sessions {sessions v_opts} { + upvar $v_opts opts + + if {! $opts(all)} { + set fields {} + foreach opt { + transport user opencount idleseconds activeseconds + clienttype clientname attrs + } { + if {$opts(all) || $opts($opt)} { + lappend fields -$opt + } + } + set sessions [recordarray get $sessions -slice $fields] + } + + set fields [recordarray fields $sessions] + if {"-clientname" in $fields} { + set client_enum [enum $fields -clientname] + } + if {"-attrs" in $fields} { + set attrs_enum [enum $fields -attrs] + } + + if {! ([info exists client_enum] || [info exists attrs_enum])} { + return $sessions + } + + # Need to map client name and attrs fields + set recs {} + foreach rec [recordarray getlist $sessions] { + if {[info exists client_enum]} { + lset rec $client_enum [_make_unc_computername [lindex $rec $client_enum]] + } + if {[info exists attrs_enum]} { + set attrs {} + set flags [lindex $rec $attrs_enum] + if {$flags & 1} { + lappend attrs guest + } + if {$flags & 2} { + lappend attrs noencryption + } + lset rec $attrs_enum $attrs + } + lappend recs $rec + } + return [list $fields $recs] +} + +# Common code to format a lm open file record. v_opts is name of array +# that controls which fields are returned +proc twapi::_format_lm_open_files {files v_opts} { + upvar $v_opts opts + + if {! $opts(all)} { + set fields {} + foreach opt { + id lockcount path user permissions + } { + if {$opts(all) || $opts($opt)} { + lappend fields -$opt + } + } + set files [recordarray get $files -slice $fields] + } + + set fields [recordarray fields $files] + + if {"-permissions" ni $fields} { + return $files + } + + # Need to massage permissions + set enum [enum $fields -permissions] + + set recs {} + foreach rec [recordarray getlist $files] { + set permissions [list ] + set perms [lindex $rec $enum] + foreach {flag perm} {1 read 2 write 4 create} { + if {$perms & $flag} { + lappend permissions $perm + } + } + lset rec $enum $permissions + lappend recs $rec + } + + return [list $fields $recs] +} + +# NOTE: THIS ONLY MAPS FOR THE Net* functions, NOT THE WNet* +proc twapi::_share_type_symbols_to_code {typesyms {basetypeonly 0}} { + + # STYPE_DISKTREE 0 + # STYPE_PRINTQ 1 + # STYPE_DEVICE 2 + # STYPE_IPC 3 + switch -exact -- [lindex $typesyms 0] { + file { set code 0 } + printer { set code 1 } + device { set code 2 } + ipc { set code 3 } + default { + error "Unknown type network share type symbol [lindex $typesyms 0]" + } + } + + if {$basetypeonly} { + return $code + } + + # STYPE_TEMPORARY 0x40000000 + # STYPE_SPECIAL 0x80000000 + set special 0 + foreach sym [lrange $typesyms 1 end] { + switch -exact -- $sym { + special { setbits special 0x80000000 } + temporary { setbits special 0x40000000 } + file - + printer - + device - + ipc { + error "Base share type symbol '$sym' cannot be used as a share attribute type" + } + default { + error "Unknown type network share type symbol '$sym'" + } + } + } + + return [expr {$code | $special}] +} + + +# First element is always the base type of the share +# NOTE: THIS ONLY MAPS FOR THE Net* functions, NOT THE WNet* +proc twapi::_share_type_code_to_symbols {type} { + + # STYPE_DISKTREE 0 + # STYPE_PRINTQ 1 + # STYPE_DEVICE 2 + # STYPE_IPC 3 + # STYPE_TEMPORARY 0x40000000 + # STYPE_SPECIAL 0x80000000 + + set special [expr {$type & 0xC0000000}] + + # We need the special cast to int because else operands get promoted + # to 64 bits as the hex is treated as an unsigned value + switch -exact -- [expr {int($type & ~ $special)}] { + 0 {set sym "file"} + 1 {set sym "printer"} + 2 {set sym "device"} + 3 {set sym "ipc"} + default {set sym $type} + } + + set typesyms [list $sym] + + if {$special & 0x80000000} { + lappend typesyms special + } + + if {$special & 0x40000000} { + lappend typesyms temporary + } + + return $typesyms +} + +# Make sure a computer name is in unc format unless it is an empty +# string (local computer) +proc twapi::_make_unc_computername {name} { + if {$name eq ""} { + return "" + } else { + return "\\\\[string trimleft $name \\]" + } +} + +proc twapi::_map_useinfo_status {status} { + set sym [lindex {connected paused lostsession disconnected networkerror connecting reconnecting} $status] + if {$sym ne ""} { + return $sym + } else { + return $status + } +} + +proc twapi::_map_useinfo_type {type} { + # Note share type and use info types are different + return [_share_type_code_to_symbols [expr {$type & 0x3fffffff}]] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/shell.tcl b/src/vendorlib_tcl8/twapi-5.0b1/shell.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/shell.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/shell.tcl index b471e2e7..5315af81 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/shell.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/shell.tcl @@ -1,627 +1,627 @@ -# -# Copyright (c) 2004-2011 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi {} - - -# Get the specified shell folder -proc twapi::get_shell_folder {csidl args} { - variable csidl_lookup - - array set opts [parseargs args {create} -maxleftover 0] - - # Following are left out because they refer to virtual folders - # and will return error if used here - # CSIDL_BITBUCKET - 0xa - if {![info exists csidl_lookup]} { - array set csidl_lookup { - CSIDL_ADMINTOOLS 0x30 - CSIDL_COMMON_ADMINTOOLS 0x2f - CSIDL_APPDATA 0x1a - CSIDL_COMMON_APPDATA 0x23 - CSIDL_COMMON_DESKTOPDIRECTORY 0x19 - CSIDL_COMMON_DOCUMENTS 0x2e - CSIDL_COMMON_FAVORITES 0x1f - CSIDL_COMMON_MUSIC 0x35 - CSIDL_COMMON_PICTURES 0x36 - CSIDL_COMMON_PROGRAMS 0x17 - CSIDL_COMMON_STARTMENU 0x16 - CSIDL_COMMON_STARTUP 0x18 - CSIDL_COMMON_TEMPLATES 0x2d - CSIDL_COMMON_VIDEO 0x37 - CSIDL_COOKIES 0x21 - CSIDL_DESKTOPDIRECTORY 0x10 - CSIDL_FAVORITES 0x6 - CSIDL_HISTORY 0x22 - CSIDL_INTERNET_CACHE 0x20 - CSIDL_LOCAL_APPDATA 0x1c - CSIDL_MYMUSIC 0xd - CSIDL_MYPICTURES 0x27 - CSIDL_MYVIDEO 0xe - CSIDL_NETHOOD 0x13 - CSIDL_PERSONAL 0x5 - CSIDL_PRINTHOOD 0x1b - CSIDL_PROFILE 0x28 - CSIDL_PROFILES 0x3e - CSIDL_PROGRAMS 0x2 - CSIDL_PROGRAM_FILES 0x26 - CSIDL_PROGRAM_FILES_COMMON 0x2b - CSIDL_RECENT 0x8 - CSIDL_SENDTO 0x9 - CSIDL_STARTMENU 0xb - CSIDL_STARTUP 0x7 - CSIDL_SYSTEM 0x25 - CSIDL_TEMPLATES 0x15 - CSIDL_WINDOWS 0x24 - CSIDL_CDBURN_AREA 0x3b - } - } - - if {![string is integer $csidl]} { - set csidl_key [string toupper $csidl] - if {![info exists csidl_lookup($csidl_key)]} { - # Try by adding a CSIDL prefix - set csidl_key "CSIDL_$csidl_key" - if {![info exists csidl_lookup($csidl_key)]} { - error "Invalid CSIDL value '$csidl'" - } - } - set csidl $csidl_lookup($csidl_key) - } - - trap { - set path [SHGetSpecialFolderPath 0 $csidl $opts(create)] - } onerror {} { - # Try some other way to get the information - switch -exact -- [format %x $csidl] { - 1a { catch {set path $::env(APPDATA)} } - 2b { catch {set path $::env(CommonProgramFiles)} } - 26 { catch {set path $::env(ProgramFiles)} } - 24 { catch {set path $::env(windir)} } - 25 { catch {set path [file join $::env(systemroot) system32]} } - } - if {![info exists path]} { - return "" - } - } - - return $path -} - -# Displays a shell property dialog for the given object -proc twapi::shell_object_properties_dialog {path args} { - array set opts [parseargs args { - {type.arg file {file printer volume}} - {hwin.int 0} - {page.arg ""} - } -maxleftover 0] - - - if {$opts(type) eq "file"} { - set path [file nativename [file normalize $path]] - } - - SHObjectProperties $opts(hwin) \ - [string map {printer 1 file 2 volume 4} $opts(type)] \ - $path \ - $opts(page) -} - -# Writes a shell shortcut -proc twapi::write_shortcut {link args} { - - array set opts [parseargs args { - path.arg - idl.arg - args.arg - desc.arg - hotkey.arg - iconpath.arg - iconindex.int - {showcmd.arg normal} - workdir.arg - relativepath.arg - runas.bool - } -nulldefault -maxleftover 0] - - # Map hot key to integer if needed - if {![string is integer -strict $opts(hotkey)]} { - if {$opts(hotkey) eq ""} { - set opts(hotkey) 0 - } else { - # Try treating it as symbolic - lassign [_hotkeysyms_to_vk $opts(hotkey)] modifiers vk - set opts(hotkey) $vk - if {$modifiers & 1} { - set opts(hotkey) [expr {$opts(hotkey) | (4<<8)}] - } - if {$modifiers & 2} { - set opts(hotkey) [expr {$opts(hotkey) | (2<<8)}] - } - if {$modifiers & 4} { - set opts(hotkey) [expr {$opts(hotkey) | (1<<8)}] - } - if {$modifiers & 8} { - set opts(hotkey) [expr {$opts(hotkey) | (8<<8)}] - } - } - } - - # IF a known symbol translate it. Note caller can pass integer - # values as well which will be kept as they are. Bogus valuse and - # symbols will generate an error on the actual call so we don't - # check here. - switch -exact -- $opts(showcmd) { - minimized { set opts(showcmd) 7 } - maximized { set opts(showcmd) 3 } - normal { set opts(showcmd) 1 } - } - - Twapi_WriteShortcut $link $opts(path) $opts(idl) $opts(args) \ - $opts(desc) $opts(hotkey) $opts(iconpath) $opts(iconindex) \ - $opts(relativepath) $opts(showcmd) $opts(workdir) $opts(runas) -} - - -# Read a shortcut -proc twapi::read_shortcut {link args} { - array set opts [parseargs args { - timeout.int - {hwin.int 0} - - {_comment {Path format flags}} - {shortnames {} 1} - {uncpath {} 2} - {rawpath {} 4} - - {_comment {Resolve flags}} - {install {} 128} - {nolinkinfo {} 64} - {notrack {} 32} - {nosearch {} 16} - {anymatch {} 2} - {noui {} 1} - } -maxleftover 0] - - set pathfmt [expr {$opts(shortnames) | $opts(uncpath) | $opts(rawpath)}] - - # 4 -> SLR_UPDATE - set resolve_flags [expr {4 | $opts(install) | $opts(nolinkinfo) | - $opts(notrack) | $opts(nosearch) | - $opts(anymatch) | $opts(noui)}] - - array set shortcut [twapi::Twapi_ReadShortcut $link $pathfmt $opts(hwin) $resolve_flags] - - switch -exact -- $shortcut(-showcmd) { - 1 { set shortcut(-showcmd) normal } - 3 { set shortcut(-showcmd) maximized } - 7 { set shortcut(-showcmd) minimized } - } - - return [array get shortcut] -} - - - -# Writes a url shortcut -proc twapi::write_url_shortcut {link url args} { - - array set opts [parseargs args { - {missingprotocol.arg 0} - } -nulldefault -maxleftover 0] - - switch -exact -- $opts(missingprotocol) { - guess { - set opts(missingprotocol) 1; # IURL_SETURL_FL_GUESS_PROTOCOL - } - usedefault { - # 3 -> IURL_SETURL_FL_GUESS_PROTOCOL | IURL_SETURL_FL_USE_DEFAULT_PROTOCOL - # The former must also be specified (based on experimentation) - set opts(missingprotocol) 3 - } - default { - if {![string is integer -strict $opts(missingprotocol)]} { - error "Invalid value '$opts(missingprotocol)' for -missingprotocol option." - } - } - } - - Twapi_WriteUrlShortcut $link $url $opts(missingprotocol) -} - -# Read a url shortcut -proc twapi::read_url_shortcut {link} { - return [Twapi_ReadUrlShortcut $link] -} - -# Invoke a url shortcut -proc twapi::invoke_url_shortcut {link args} { - - array set opts [parseargs args { - verb.arg - {hwin.int 0} - allowui - } -maxleftover 0] - - set flags 0 - if {$opts(allowui)} {setbits flags 1} - if {! [info exists opts(verb)]} { - setbits flags 2 - set opts(verb) "" - } - - Twapi_InvokeUrlShortcut $link $opts(verb) $flags $opts(hwin) -} - -# Send a file to the recycle bin -proc twapi::recycle_file {fn args} { - return [recycle_files [list $fn] {*}$args] -} - -# Send multiple files to the recycle bin - from Alexandru -# This is much faster than "recycle_file"! -proc twapi::recycle_files {fns args} { - array set opts [parseargs args { - confirm.bool - showerror.bool - } -maxleftover 0 -nulldefault] - - if {$opts(confirm)} { - set flags 0x40; # FOF_ALLOWUNDO - } else { - set flags 0x50; # FOF_ALLOWUNDO | FOF_NOCONFIRMATION - } - - if {! $opts(showerror)} { - set flags [expr {$flags | 0x0400}]; # FOF_NOERRORUI - } - - set fns [lmap fn $fns { - file nativename [file normalize $fn] - }] - - return [expr {[lindex [Twapi_SHFileOperation 0 3 $fns __null__ $flags ""] 0] ? false : true}] -} - -proc twapi::shell_execute args { - # TBD - Document following shell_execute options after testing. - # [opt_def [cmd -connect] [arg BOOLEAN]] - # [opt_def [cmd -hicon] [arg HANDLE]] - # [opt_def [cmd -hkeyclass] [arg BOOLEAN]] - # [opt_def [cmd -hotkey] [arg HOTKEY]] - # [opt_def [cmd -nozonechecks] [arg BOOLEAN]] - - array set opts [parseargs args { - class.arg - dir.arg - {hicon.arg NULL} - {hkeyclass.arg NULL} - {hmonitor.arg NULL} - hotkey.arg - hwin.int - idl.arg - params.arg - path.arg - {show.arg 1} - verb.arg - - {getprocesshandle.bool 0 0x00000040} - {connect.bool 0 0x00000080} - {wait.bool 0x00000100 0x00000100} - {substenv.bool 0 0x00000200} - {noui.bool 0 0x00000400} - {unicode.bool 0 0x00004000} - {noconsole.bool 0 0x00008000} - {asyncok.bool 0 0x00100000} - {nozonechecks.bool 0 0x00800000} - {waitforinputidle.bool 0 0x02000000} - {logusage.bool 0 0x04000000} - {invokeidlist.bool 0 0x0000000C} - } -maxleftover 0 -nulldefault] - - set fmask 0 - - foreach {opt mask} { - class 1 - idl 4 - } { - if {$opts($opt) ne ""} { - setbits fmask $mask - } - } - - if {$opts(hkeyclass) ne "NULL"} { - setbits fmask 3 - } - - set fmask [expr {$fmask | - $opts(getprocesshandle) | $opts(connect) | $opts(wait) | - $opts(substenv) | $opts(noui) | $opts(unicode) | - $opts(noconsole) | $opts(asyncok) | $opts(nozonechecks) | - $opts(waitforinputidle) | $opts(logusage) | - $opts(invokeidlist)}] - - if {$opts(hicon) ne "NULL" && $opts(hmonitor) ne "NULL"} { - error "Cannot specify -hicon and -hmonitor options together." - } - - set hiconormonitor NULL - if {$opts(hicon) ne "NULL"} { - set hiconormonitor $opts(hicon) - set flags [expr {$flags | 0x00000010}] - } elseif {$opts(hmonitor) ne "NULL"} { - set hiconormonitor $opts(hmonitor) - set flags [expr {$flags | 0x00200000}] - } - - if {![string is integer -strict $opts(show)]} { - set opts(show) [dict get { - hide 0 - shownormal 1 - normal 1 - showminimized 2 - showmaximized 3 - maximize 3 - shownoactivate 4 - show 5 - minimize 6 - showminnoactive 7 - showna 8 - restore 9 - showdefault 10 - forceminimize 11 - } $opts(show)] - } - - if {$opts(hotkey) eq ""} { - set hotkey 0 - } else { - lassign [_hotkeysyms_to_vk $opts(hotkey) { - shift 1 - ctrl 2 - control 2 - alt 4 - menu 4 - ext 8 - }] modifiers vk - set hotkey [expr {($modifiers << 16) | $vk}] - } - if {$hotkey != 0} { - setbits fmask 0x00000020 - } - return [Twapi_ShellExecuteEx \ - $fmask \ - $opts(hwin) \ - $opts(verb) \ - $opts(path) \ - $opts(params) \ - $opts(dir) \ - $opts(show) \ - $opts(idl) \ - $opts(class) \ - $opts(hkeyclass) \ - $hotkey \ - $hiconormonitor] -} - - -namespace eval twapi::systemtray { - - namespace path [namespace parent] - - # Dictionary mapping id->handler, hicon - variable _icondata - set _icondata [dict create] - - variable _icon_id_ctr - - variable _message_map - array set _message_map { - 123 contextmenu - 512 mousemove - 513 lbuttondown - 514 lbuttonup - 515 lbuttondblclk - 516 rbuttondown - 517 rbuttonup - 518 rbuttondblclk - 519 mbuttondown - 520 mbuttonup - 521 mbuttondblclk - 522 mousewheel - 523 xbuttondown - 524 xbuttonup - 525 xbuttondblclk - 1024 select - 1025 keyselect - 1026 balloonshow - 1027 balloonhide - 1028 balloontimeout - 1029 balloonuserclick - } - - proc _make_NOTIFYICONW {id args} { - # TBD - implement -hiddenicon and -sharedicon using - # dwState and dwStateMask - set state 0 - set statemask 0 - array set opts [parseargs args { - hicon.arg - tip.arg - balloon.arg - timeout.int - version.int - balloontitle.arg - {balloonicon.arg none {info warning error user none}} - {silent.bool 0} - } -maxleftover 0] - - set timeout_or_version 0 - if {[info exists opts(version)]} { - if {[info exists opts(timeout)]} { - error "Cannot simultaneously specify -timeout and -version." - } - set timeout_or_version $opts(version) - } else { - if {[info exists opts(timeout)]} { - set timeout_or_version $opts(timeout) - } - } - - set flags 0x1; # uCallbackMessage member is valid - if {[info exists opts(hicon)]} { - incr flags 0x2; # hIcon member is valid - } else { - set opts(hicon) NULL - } - - if {[info exists opts(tip)]} { - incr flags 0x4 - # Truncate if necessary to 127 chars - set opts(tip) [string range $opts(tip) 0 127] - } else { - set opts(tip) "" - } - - if {[info exists opts(balloon)] || [info exists opts(balloontitle)]} { - incr flags 0x10 - } - - if {[info exists opts(balloon)]} { - set opts(balloon) [string range $opts(balloon) 0 255] - } else { - set opts(balloon) "" - } - - if {[info exists opts(balloontitle)]} { - set opts(balloontitle) [string range $opts(balloontitle) 0 63] - } else { - set opts(balloontitle) "" - } - - # Calculate padding for text fields (in bytes so 2*num padchars) - set tip_padcount [expr {2*(128 - [string length $opts(tip)])}] - set balloon_padcount [expr {2*(256 - [string length $opts(balloon)])}] - set balloontitle_padcount [expr {2 * (64 - [string length $opts(balloontitle)])}] - if {$opts(balloonicon) eq "user"} { - if {![min_os_version 5 1 2]} { - # 'user' not supported before XP SP2 - set opts(balloonicon) none - } - } - - set balloonflags [dict get { - none 0 - info 1 - warning 2 - error 3 - user 4 - } $opts(balloonicon)] - - if {$balloonflags == 4} { - if {![info exists opts(hicon)]} { - error "Option -hicon must be specified if value of -balloonicon option is 'user'" - } - } - - if {$opts(silent)} { - incr balloonflags 0x10 - } - - if {$::tcl_platform(pointerSize) == 8} { - set addrfmt m - set alignment x4 - } else { - set addrfmt n - set alignment x0 - } - - set hwnd [pointer_to_address [Twapi_GetNotificationWindow]] - set opts(hicon) [pointer_to_address $opts(hicon)] - - set bin [binary format "${alignment}${addrfmt}nnn" $hwnd $id $flags [_get_script_wm NOTIFY_ICON_CALLBACK]] - append bin \ - [binary format ${alignment}${addrfmt} $opts(hicon)] \ - [encoding convertto unicode $opts(tip)] \ - [binary format "x${tip_padcount}nn" $state $statemask] \ - [encoding convertto unicode $opts(balloon)] \ - [binary format "x${balloon_padcount}n" $timeout_or_version] \ - [encoding convertto unicode $opts(balloontitle)] \ - [binary format "x${balloontitle_padcount}nx16" $balloonflags] - return "[binary format n [expr {4+[string length $bin]}]]$bin" - } - - proc addicon {hicon {cmdprefix ""}} { - variable _icon_id_ctr - variable _icondata - - _register_script_wm_handler [_get_script_wm NOTIFY_ICON_CALLBACK] [list [namespace current]::_icon_handler] 1 - _register_script_wm_handler [_get_script_wm TASKBAR_RESTART] [list [namespace current]::_taskbar_restart_handler] 1 - - set id [incr _icon_id_ctr] - - # 0 -> Add - Shell_NotifyIcon 0 [_make_NOTIFYICONW $id -hicon $hicon] - - # 4 -> set version (controls notification behaviour) to 3 (Win2K+) - if {[catch { - Shell_NotifyIcon 4 [_make_NOTIFYICONW $id -version 3] - } ermsg]} { - set ercode $::errorCode - set erinfo $::errorInfo - removeicon $id - error $ermsg $erinfo $ercode - } - - if {[llength $cmdprefix]} { - dict set _icondata $id handler $cmdprefix - } - dict set _icondata $id hicon $hicon - - return $id - } - - proc removeicon {id} { - variable _icondata - - # Ignore errors in case dup call - catch {Shell_NotifyIcon 2 [_make_NOTIFYICONW $id]} - dict unset _icondata $id - } - - proc modifyicon {id args} { - # TBD - do we need to [dict set _icondata hicon ...] ? - Shell_NotifyIcon 1 [_make_NOTIFYICONW $id {*}$args] - } - - proc _icon_handler {msg id notification msgpos ticks} { - variable _icondata - variable _message_map - - if {![dict exists $_icondata $id handler]} { - return; # Stale or no handler specified - } - - # Translate the notification into text - if {[info exists _message_map($notification)]} { - set notification $_message_map($notification) - } - - uplevel #0 [linsert [dict get $_icondata $id handler] end $id $notification $msgpos $ticks] - } - - proc _taskbar_restart_handler {args} { - variable _icondata - # Need to add icons back into taskbar - dict for {id icodata} $_icondata { - # 0 -> Add - Shell_NotifyIcon 0 [_make_NOTIFYICONW $id -hicon [dict get $icodata hicon]] - } - } - - namespace export addicon modifyicon removeicon - namespace ensemble create -} +# +# Copyright (c) 2004-2011 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi {} + + +# Get the specified shell folder +proc twapi::get_shell_folder {csidl args} { + variable csidl_lookup + + array set opts [parseargs args {create} -maxleftover 0] + + # Following are left out because they refer to virtual folders + # and will return error if used here + # CSIDL_BITBUCKET - 0xa + if {![info exists csidl_lookup]} { + array set csidl_lookup { + CSIDL_ADMINTOOLS 0x30 + CSIDL_COMMON_ADMINTOOLS 0x2f + CSIDL_APPDATA 0x1a + CSIDL_COMMON_APPDATA 0x23 + CSIDL_COMMON_DESKTOPDIRECTORY 0x19 + CSIDL_COMMON_DOCUMENTS 0x2e + CSIDL_COMMON_FAVORITES 0x1f + CSIDL_COMMON_MUSIC 0x35 + CSIDL_COMMON_PICTURES 0x36 + CSIDL_COMMON_PROGRAMS 0x17 + CSIDL_COMMON_STARTMENU 0x16 + CSIDL_COMMON_STARTUP 0x18 + CSIDL_COMMON_TEMPLATES 0x2d + CSIDL_COMMON_VIDEO 0x37 + CSIDL_COOKIES 0x21 + CSIDL_DESKTOPDIRECTORY 0x10 + CSIDL_FAVORITES 0x6 + CSIDL_HISTORY 0x22 + CSIDL_INTERNET_CACHE 0x20 + CSIDL_LOCAL_APPDATA 0x1c + CSIDL_MYMUSIC 0xd + CSIDL_MYPICTURES 0x27 + CSIDL_MYVIDEO 0xe + CSIDL_NETHOOD 0x13 + CSIDL_PERSONAL 0x5 + CSIDL_PRINTHOOD 0x1b + CSIDL_PROFILE 0x28 + CSIDL_PROFILES 0x3e + CSIDL_PROGRAMS 0x2 + CSIDL_PROGRAM_FILES 0x26 + CSIDL_PROGRAM_FILES_COMMON 0x2b + CSIDL_RECENT 0x8 + CSIDL_SENDTO 0x9 + CSIDL_STARTMENU 0xb + CSIDL_STARTUP 0x7 + CSIDL_SYSTEM 0x25 + CSIDL_TEMPLATES 0x15 + CSIDL_WINDOWS 0x24 + CSIDL_CDBURN_AREA 0x3b + } + } + + if {![string is integer -strict $csidl]} { + set csidl_key [string toupper $csidl] + if {![info exists csidl_lookup($csidl_key)]} { + # Try by adding a CSIDL prefix + set csidl_key "CSIDL_$csidl_key" + if {![info exists csidl_lookup($csidl_key)]} { + error "Invalid CSIDL value '$csidl'" + } + } + set csidl $csidl_lookup($csidl_key) + } + + trap { + set path [SHGetSpecialFolderPath 0 $csidl $opts(create)] + } onerror {} { + # Try some other way to get the information + switch -exact -- [format %x $csidl] { + 1a { catch {set path $::env(APPDATA)} } + 2b { catch {set path $::env(CommonProgramFiles)} } + 26 { catch {set path $::env(ProgramFiles)} } + 24 { catch {set path $::env(windir)} } + 25 { catch {set path [file join $::env(systemroot) system32]} } + } + if {![info exists path]} { + return "" + } + } + + return $path +} + +# Displays a shell property dialog for the given object +proc twapi::shell_object_properties_dialog {path args} { + array set opts [parseargs args { + {type.arg file {file printer volume}} + {hwin.int 0} + {page.arg ""} + } -maxleftover 0] + + + if {$opts(type) eq "file"} { + set path [file nativename [file normalize $path]] + } + + SHObjectProperties $opts(hwin) \ + [string map {printer 1 file 2 volume 4} $opts(type)] \ + $path \ + $opts(page) +} + +# Writes a shell shortcut +proc twapi::write_shortcut {link args} { + + array set opts [parseargs args { + path.arg + idl.arg + args.arg + desc.arg + hotkey.arg + iconpath.arg + iconindex.int + {showcmd.arg normal} + workdir.arg + relativepath.arg + runas.bool + } -nulldefault -maxleftover 0] + + # Map hot key to integer if needed + if {![string is integer -strict $opts(hotkey)]} { + if {$opts(hotkey) eq ""} { + set opts(hotkey) 0 + } else { + # Try treating it as symbolic + lassign [_hotkeysyms_to_vk $opts(hotkey)] modifiers vk + set opts(hotkey) $vk + if {$modifiers & 1} { + set opts(hotkey) [expr {$opts(hotkey) | (4<<8)}] + } + if {$modifiers & 2} { + set opts(hotkey) [expr {$opts(hotkey) | (2<<8)}] + } + if {$modifiers & 4} { + set opts(hotkey) [expr {$opts(hotkey) | (1<<8)}] + } + if {$modifiers & 8} { + set opts(hotkey) [expr {$opts(hotkey) | (8<<8)}] + } + } + } + + # IF a known symbol translate it. Note caller can pass integer + # values as well which will be kept as they are. Bogus valuse and + # symbols will generate an error on the actual call so we don't + # check here. + switch -exact -- $opts(showcmd) { + minimized { set opts(showcmd) 7 } + maximized { set opts(showcmd) 3 } + normal { set opts(showcmd) 1 } + } + + Twapi_WriteShortcut $link $opts(path) $opts(idl) $opts(args) \ + $opts(desc) $opts(hotkey) $opts(iconpath) $opts(iconindex) \ + $opts(relativepath) $opts(showcmd) $opts(workdir) $opts(runas) +} + + +# Read a shortcut +proc twapi::read_shortcut {link args} { + array set opts [parseargs args { + timeout.int + {hwin.int 0} + + {_comment {Path format flags}} + {shortnames {} 1} + {uncpath {} 2} + {rawpath {} 4} + + {_comment {Resolve flags}} + {install {} 128} + {nolinkinfo {} 64} + {notrack {} 32} + {nosearch {} 16} + {anymatch {} 2} + {noui {} 1} + } -maxleftover 0] + + set pathfmt [expr {$opts(shortnames) | $opts(uncpath) | $opts(rawpath)}] + + # 4 -> SLR_UPDATE + set resolve_flags [expr {4 | $opts(install) | $opts(nolinkinfo) | + $opts(notrack) | $opts(nosearch) | + $opts(anymatch) | $opts(noui)}] + + array set shortcut [twapi::Twapi_ReadShortcut $link $pathfmt $opts(hwin) $resolve_flags] + + switch -exact -- $shortcut(-showcmd) { + 1 { set shortcut(-showcmd) normal } + 3 { set shortcut(-showcmd) maximized } + 7 { set shortcut(-showcmd) minimized } + } + + return [array get shortcut] +} + + + +# Writes a url shortcut +proc twapi::write_url_shortcut {link url args} { + + array set opts [parseargs args { + {missingprotocol.arg 0} + } -nulldefault -maxleftover 0] + + switch -exact -- $opts(missingprotocol) { + guess { + set opts(missingprotocol) 1; # IURL_SETURL_FL_GUESS_PROTOCOL + } + usedefault { + # 3 -> IURL_SETURL_FL_GUESS_PROTOCOL | IURL_SETURL_FL_USE_DEFAULT_PROTOCOL + # The former must also be specified (based on experimentation) + set opts(missingprotocol) 3 + } + default { + if {![string is integer -strict $opts(missingprotocol)]} { + error "Invalid value '$opts(missingprotocol)' for -missingprotocol option." + } + } + } + + Twapi_WriteUrlShortcut $link $url $opts(missingprotocol) +} + +# Read a url shortcut +proc twapi::read_url_shortcut {link} { + return [Twapi_ReadUrlShortcut $link] +} + +# Invoke a url shortcut +proc twapi::invoke_url_shortcut {link args} { + + array set opts [parseargs args { + verb.arg + {hwin.int 0} + allowui + } -maxleftover 0] + + set flags 0 + if {$opts(allowui)} {setbits flags 1} + if {! [info exists opts(verb)]} { + setbits flags 2 + set opts(verb) "" + } + + Twapi_InvokeUrlShortcut $link $opts(verb) $flags $opts(hwin) +} + +# Send a file to the recycle bin +proc twapi::recycle_file {fn args} { + return [recycle_files [list $fn] {*}$args] +} + +# Send multiple files to the recycle bin - from Alexandru +# This is much faster than "recycle_file"! +proc twapi::recycle_files {fns args} { + array set opts [parseargs args { + confirm.bool + showerror.bool + } -maxleftover 0 -nulldefault] + + if {$opts(confirm)} { + set flags 0x40; # FOF_ALLOWUNDO + } else { + set flags 0x50; # FOF_ALLOWUNDO | FOF_NOCONFIRMATION + } + + if {! $opts(showerror)} { + set flags [expr {$flags | 0x0400}]; # FOF_NOERRORUI + } + + set fns [lmap fn $fns { + file nativename [file normalize $fn] + }] + + return [expr {[lindex [Twapi_SHFileOperation 0 3 $fns __null__ $flags ""] 0] ? false : true}] +} + +proc twapi::shell_execute args { + # TBD - Document following shell_execute options after testing. + # [opt_def [cmd -connect] [arg BOOLEAN]] + # [opt_def [cmd -hicon] [arg HANDLE]] + # [opt_def [cmd -hkeyclass] [arg BOOLEAN]] + # [opt_def [cmd -hotkey] [arg HOTKEY]] + # [opt_def [cmd -nozonechecks] [arg BOOLEAN]] + + array set opts [parseargs args { + class.arg + dir.arg + {hicon.arg NULL} + {hkeyclass.arg NULL} + {hmonitor.arg NULL} + hotkey.arg + hwin.int + idl.arg + params.arg + path.arg + {show.arg 1} + verb.arg + + {getprocesshandle.bool 0 0x00000040} + {connect.bool 0 0x00000080} + {wait.bool 0x00000100 0x00000100} + {substenv.bool 0 0x00000200} + {noui.bool 0 0x00000400} + {unicode.bool 0 0x00004000} + {noconsole.bool 0 0x00008000} + {asyncok.bool 0 0x00100000} + {nozonechecks.bool 0 0x00800000} + {waitforinputidle.bool 0 0x02000000} + {logusage.bool 0 0x04000000} + {invokeidlist.bool 0 0x0000000C} + } -maxleftover 0 -nulldefault] + + set fmask 0 + + foreach {opt mask} { + class 1 + idl 4 + } { + if {$opts($opt) ne ""} { + setbits fmask $mask + } + } + + if {$opts(hkeyclass) ne "NULL"} { + setbits fmask 3 + } + + set fmask [expr {$fmask | + $opts(getprocesshandle) | $opts(connect) | $opts(wait) | + $opts(substenv) | $opts(noui) | $opts(unicode) | + $opts(noconsole) | $opts(asyncok) | $opts(nozonechecks) | + $opts(waitforinputidle) | $opts(logusage) | + $opts(invokeidlist)}] + + if {$opts(hicon) ne "NULL" && $opts(hmonitor) ne "NULL"} { + error "Cannot specify -hicon and -hmonitor options together." + } + + set hiconormonitor NULL + if {$opts(hicon) ne "NULL"} { + set hiconormonitor $opts(hicon) + set flags [expr {$flags | 0x00000010}] + } elseif {$opts(hmonitor) ne "NULL"} { + set hiconormonitor $opts(hmonitor) + set flags [expr {$flags | 0x00200000}] + } + + if {![string is integer -strict $opts(show)]} { + set opts(show) [dict get { + hide 0 + shownormal 1 + normal 1 + showminimized 2 + showmaximized 3 + maximize 3 + shownoactivate 4 + show 5 + minimize 6 + showminnoactive 7 + showna 8 + restore 9 + showdefault 10 + forceminimize 11 + } $opts(show)] + } + + if {$opts(hotkey) eq ""} { + set hotkey 0 + } else { + lassign [_hotkeysyms_to_vk $opts(hotkey) { + shift 1 + ctrl 2 + control 2 + alt 4 + menu 4 + ext 8 + }] modifiers vk + set hotkey [expr {($modifiers << 16) | $vk}] + } + if {$hotkey != 0} { + setbits fmask 0x00000020 + } + return [Twapi_ShellExecuteEx \ + $fmask \ + $opts(hwin) \ + $opts(verb) \ + $opts(path) \ + $opts(params) \ + $opts(dir) \ + $opts(show) \ + $opts(idl) \ + $opts(class) \ + $opts(hkeyclass) \ + $hotkey \ + $hiconormonitor] +} + + +namespace eval twapi::systemtray { + + namespace path [namespace parent] + + # Dictionary mapping id->handler, hicon + variable _icondata + set _icondata [dict create] + + variable _icon_id_ctr + + variable _message_map + array set _message_map { + 123 contextmenu + 512 mousemove + 513 lbuttondown + 514 lbuttonup + 515 lbuttondblclk + 516 rbuttondown + 517 rbuttonup + 518 rbuttondblclk + 519 mbuttondown + 520 mbuttonup + 521 mbuttondblclk + 522 mousewheel + 523 xbuttondown + 524 xbuttonup + 525 xbuttondblclk + 1024 select + 1025 keyselect + 1026 balloonshow + 1027 balloonhide + 1028 balloontimeout + 1029 balloonuserclick + } + + proc _make_NOTIFYICONW {id args} { + # TBD - implement -hiddenicon and -sharedicon using + # dwState and dwStateMask + set state 0 + set statemask 0 + array set opts [parseargs args { + hicon.arg + tip.arg + balloon.arg + timeout.int + version.int + balloontitle.arg + {balloonicon.arg none {info warning error user none}} + {silent.bool 0} + } -maxleftover 0] + + set timeout_or_version 0 + if {[info exists opts(version)]} { + if {[info exists opts(timeout)]} { + error "Cannot simultaneously specify -timeout and -version." + } + set timeout_or_version $opts(version) + } else { + if {[info exists opts(timeout)]} { + set timeout_or_version $opts(timeout) + } + } + + set flags 0x1; # uCallbackMessage member is valid + if {[info exists opts(hicon)]} { + incr flags 0x2; # hIcon member is valid + } else { + set opts(hicon) NULL + } + + if {[info exists opts(tip)]} { + incr flags 0x4 + # Truncate if necessary to 127 chars + set opts(tip) [string range $opts(tip) 0 127] + } else { + set opts(tip) "" + } + + if {[info exists opts(balloon)] || [info exists opts(balloontitle)]} { + incr flags 0x10 + } + + if {[info exists opts(balloon)]} { + set opts(balloon) [string range $opts(balloon) 0 255] + } else { + set opts(balloon) "" + } + + if {[info exists opts(balloontitle)]} { + set opts(balloontitle) [string range $opts(balloontitle) 0 63] + } else { + set opts(balloontitle) "" + } + + # Calculate padding for text fields (in bytes so 2*num padchars) + set tip_padcount [expr {2*(128 - [string length $opts(tip)])}] + set balloon_padcount [expr {2*(256 - [string length $opts(balloon)])}] + set balloontitle_padcount [expr {2 * (64 - [string length $opts(balloontitle)])}] + if {$opts(balloonicon) eq "user"} { + if {![min_os_version 5 1 2]} { + # 'user' not supported before XP SP2 + set opts(balloonicon) none + } + } + + set balloonflags [dict get { + none 0 + info 1 + warning 2 + error 3 + user 4 + } $opts(balloonicon)] + + if {$balloonflags == 4} { + if {![info exists opts(hicon)]} { + error "Option -hicon must be specified if value of -balloonicon option is 'user'" + } + } + + if {$opts(silent)} { + incr balloonflags 0x10 + } + + if {$::tcl_platform(pointerSize) == 8} { + set addrfmt m + set alignment x4 + } else { + set addrfmt n + set alignment x0 + } + + set hwnd [pointer_to_address [Twapi_GetNotificationWindow]] + set opts(hicon) [pointer_to_address $opts(hicon)] + + set bin [binary format "${alignment}${addrfmt}nnn" $hwnd $id $flags [_get_script_wm NOTIFY_ICON_CALLBACK]] + append bin \ + [binary format ${alignment}${addrfmt} $opts(hicon)] \ + [encoding convertto unicode $opts(tip)] \ + [binary format "x${tip_padcount}nn" $state $statemask] \ + [encoding convertto unicode $opts(balloon)] \ + [binary format "x${balloon_padcount}n" $timeout_or_version] \ + [encoding convertto unicode $opts(balloontitle)] \ + [binary format "x${balloontitle_padcount}nx16" $balloonflags] + return "[binary format n [expr {4+[string length $bin]}]]$bin" + } + + proc addicon {hicon {cmdprefix ""}} { + variable _icon_id_ctr + variable _icondata + + _register_script_wm_handler [_get_script_wm NOTIFY_ICON_CALLBACK] [list [namespace current]::_icon_handler] 1 + _register_script_wm_handler [_get_script_wm TASKBAR_RESTART] [list [namespace current]::_taskbar_restart_handler] 1 + + set id [incr _icon_id_ctr] + + # 0 -> Add + Shell_NotifyIcon 0 [_make_NOTIFYICONW $id -hicon $hicon] + + # 4 -> set version (controls notification behaviour) to 3 (Win2K+) + if {[catch { + Shell_NotifyIcon 4 [_make_NOTIFYICONW $id -version 3] + } ermsg]} { + set ercode $::errorCode + set erinfo $::errorInfo + removeicon $id + error $ermsg $erinfo $ercode + } + + if {[llength $cmdprefix]} { + dict set _icondata $id handler $cmdprefix + } + dict set _icondata $id hicon $hicon + + return $id + } + + proc removeicon {id} { + variable _icondata + + # Ignore errors in case dup call + catch {Shell_NotifyIcon 2 [_make_NOTIFYICONW $id]} + dict unset _icondata $id + } + + proc modifyicon {id args} { + # TBD - do we need to [dict set _icondata hicon ...] ? + Shell_NotifyIcon 1 [_make_NOTIFYICONW $id {*}$args] + } + + proc _icon_handler {msg id notification msgpos ticks} { + variable _icondata + variable _message_map + + if {![dict exists $_icondata $id handler]} { + return; # Stale or no handler specified + } + + # Translate the notification into text + if {[info exists _message_map($notification)]} { + set notification $_message_map($notification) + } + + uplevel #0 [linsert [dict get $_icondata $id handler] end $id $notification $msgpos $ticks] + } + + proc _taskbar_restart_handler {args} { + variable _icondata + # Need to add icons back into taskbar + dict for {id icodata} $_icondata { + # 0 -> Add + Shell_NotifyIcon 0 [_make_NOTIFYICONW $id -hicon [dict get $icodata hicon]] + } + } + + namespace export addicon modifyicon removeicon + namespace ensemble create +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/sspi.tcl b/src/vendorlib_tcl8/twapi-5.0b1/sspi.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/sspi.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/sspi.tcl index aa4ec70e..60bbb8cb 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/sspi.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/sspi.tcl @@ -1,801 +1,801 @@ -# -# Copyright (c) 2007-2013, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -namespace eval twapi { - - - # Holds SSPI security contexts indexed by a handle - # Each element is a dict with the following keys: - # State - state of the security context - see sspi_step - # Handle - the Win32 SecHandle for the context - # Input - Pending input from remote end to be passed in to - # SSPI provider (only valid for streams) - # Output - list of SecBuffers that contain data to be sent - # to remote end during a SSPI negotiation - # Inattr - requested context attributes - # Outattr - context attributes returned from service provider - # (currently not used) - # Expiration - time when context will expire - # Ctxtype - client, server - # Target - - # Datarep - data representation format - # Credentials - handle for credentials to pass to sspi provider - variable _sspi_state - array set _sspi_state {} - - proc* _init_security_context_syms {} { - variable _server_security_context_syms - variable _client_security_context_syms - variable _secpkg_capability_syms - - - # Symbols used for mapping server security context flags - array set _server_security_context_syms { - confidentiality 0x10 - connection 0x800 - delegate 0x1 - extendederror 0x8000 - identify 0x80000 - integrity 0x20000 - mutualauth 0x2 - replaydetect 0x4 - sequencedetect 0x8 - stream 0x10000 - } - - # Symbols used for mapping client security context flags - array set _client_security_context_syms { - confidentiality 0x10 - connection 0x800 - delegate 0x1 - extendederror 0x4000 - identify 0x20000 - integrity 0x10000 - manualvalidation 0x80000 - mutualauth 0x2 - replaydetect 0x4 - sequencedetect 0x8 - stream 0x8000 - usesessionkey 0x20 - usesuppliedcreds 0x80 - } - - # Symbols used for mapping security package capabilities - array set _secpkg_capability_syms { - integrity 0x00000001 - privacy 0x00000002 - tokenonly 0x00000004 - datagram 0x00000008 - connection 0x00000010 - multirequired 0x00000020 - clientonly 0x00000040 - extendederror 0x00000080 - impersonation 0x00000100 - acceptwin32name 0x00000200 - stream 0x00000400 - negotiable 0x00000800 - gsscompatible 0x00001000 - logon 0x00002000 - asciibuffers 0x00004000 - fragment 0x00008000 - mutualauth 0x00010000 - delegation 0x00020000 - readonlywithchecksum 0x00040000 - restrictedtokens 0x00080000 - negoextender 0x00100000 - negotiable2 0x00200000 - appcontainerpassthrough 0x00400000 - appcontainerchecks 0x00800000 - } - } {} -} - -# Return list of security packages -proc twapi::sspi_enumerate_packages {args} { - set pkgs [EnumerateSecurityPackages] - if {[llength $args] == 0} { - set names [list ] - foreach pkg $pkgs { - lappend names [kl_get $pkg Name] - } - return $names - } - - # TBD - why is this hyphenated ? - array set opts [parseargs args { - all capabilities version rpcid maxtokensize name comment - } -maxleftover 0 -hyphenated] - - _init_security_context_syms - variable _secpkg_capability_syms - set retdata {} - foreach pkg $pkgs { - set rec {} - if {$opts(-all) || $opts(-capabilities)} { - lappend rec -capabilities [_make_symbolic_bitmask [kl_get $pkg fCapabilities] _secpkg_capability_syms] - } - foreach {opt field} {-version wVersion -rpcid wRPCID -maxtokensize cbMaxToken -name Name -comment Comment} { - if {$opts(-all) || $opts($opt)} { - lappend rec $opt [kl_get $pkg $field] - } - } - dict set recdata [kl_get $pkg Name] $rec - } - return $recdata -} - -proc twapi::sspi_schannel_credentials args { - # TBD - do all these options work ? Check before documenting - # since they seem to be duplicated in InitializeSecurityContext - parseargs args { - certificates.arg - {rootstore.arg NULL} - sessionlifespan.int - usedefaultclientcert.bool - {disablereconnects.bool 0 0x80} - {revocationcheck.arg none {full endonly excluderoot none}} - {ignoreerrorrevocationoffline.bool 0 0x1000} - {ignoreerrornorevocationcheck.bool 0 0x800} - {validateservercert.bool 1} - cipherstrength.arg - protocols.arg - } -setvars -nulldefault -maxleftover 0 - - set flags [expr {$disablereconnects | $ignoreerrornorevocationcheck | $ignoreerrorrevocationoffline}] - incr flags [dict get { - none 0 full 0x200 excluderoot 0x400 endonly 0x100 - } $revocationcheck] - - if {$validateservercert} { - incr flags 0x20; # SCH_CRED_AUTO_CRED_VALIDATION - } else { - incr flags 0x8; # SCH_CRED_MANUAL_CRED_VALIDATION - } - if {$usedefaultclientcert} { - incr flags 0x40; # SCH_CRED_USE_DEFAULT_CREDS - } else { - incr flags 0x10; # SCH_CRED_NO_DEFAULT_CREDS - } - - set protbits 0 - foreach prot $protocols { - set protbits [expr { - $protbits | [dict! { - ssl2 0xc ssl3 0x30 tls1 0xc0 tls1.1 0x300 tls1.2 0xc00 - } $prot] - }] - } - - switch [llength $cipherstrength] { - 0 { set minbits 0 ; set maxbits 0 } - 1 { set minbits [lindex $cipherstrength 0] ; set maxbits $minbits } - 2 { - set minbits [lindex $cipherstrength 0] - set maxbits [lindex $cipherstrength 1] - } - default { - error "Invalid value '$cipherstrength' for option -cipherstrength" - } - } - - # 4 -> SCHANNEL_CRED_VERSION - return [list 4 $certificates $rootstore {} {} $protbits $minbits $maxbits $sessionlifespan $flags 0] -} - -proc twapi::sspi_winnt_identity_credentials {user domain password} { - return [list $user $domain $password] -} - -proc twapi::sspi_acquire_credentials {args} { - parseargs args { - {credentials.arg {}} - principal.arg - {package.arg NTLM} - {role.arg both {client server inbound outbound both}} - getexpiration - } -maxleftover 0 -setvars -nulldefault - - set creds [AcquireCredentialsHandle $principal \ - [dict* { - unisp {Microsoft Unified Security Protocol Provider} - ssl {Microsoft Unified Security Protocol Provider} - tls {Microsoft Unified Security Protocol Provider} - } $package] \ - [kl_get {inbound 1 server 1 outbound 2 client 2 both 3} $role] \ - "" $credentials] - - if {$getexpiration} { - return [kl_create2 {-handle -expiration} $creds] - } else { - return [lindex $creds 0] - } -} - -# Frees credentials -proc twapi::sspi_free_credentials {cred} { - FreeCredentialsHandle $cred -} - -# Return a client context -proc twapi::sspi_client_context {cred args} { - _init_security_context_syms - variable _client_security_context_syms - - parseargs args { - target.arg - {datarep.arg network {native network}} - confidentiality.bool - connection.bool - delegate.bool - extendederror.bool - identify.bool - integrity.bool - manualvalidation.bool - mutualauth.bool - replaydetect.bool - sequencedetect.bool - stream.bool - usesessionkey.bool - usesuppliedcreds.bool - } -maxleftover 0 -nulldefault -setvars - - set context_flags 0 - foreach {opt flag} [array get _client_security_context_syms] { - if {[set $opt]} { - set context_flags [expr {$context_flags | $flag}] - } - } - - set drep [kl_get {native 0x10 network 0} $datarep] - return [_construct_sspi_security_context \ - sspiclient#[TwapiId] \ - [InitializeSecurityContext \ - $cred \ - "" \ - $target \ - $context_flags \ - 0 \ - $drep \ - [list ] \ - 0] \ - client \ - $context_flags \ - $target \ - $cred \ - $drep \ - ] -} - -# Delete a security context -proc twapi::sspi_delete_context {ctx} { - variable _sspi_state - set h [_sspi_context_handle $ctx] - if {[llength $h]} { - DeleteSecurityContext $h - } - unset _sspi_state($ctx) -} - -# Shuts down a security context in orderly fashion -# Caller should start sspi_step -proc twapi::sspi_shutdown_context {ctx} { - variable _sspi_state - - _sspi_context_handle $ctx; # Verify handle - dict with _sspi_state($ctx) { - switch -nocase -- [lindex [QueryContextAttributes $Handle 10] 4] { - schannel - - "Microsoft Unified Security Protocol Provider" {} - default { return } - } - - # Signal to security provider we want to shutdown - Twapi_ApplyControlToken_SCHANNEL_SHUTDOWN $Handle - - if {$Ctxtype eq "client"} { - set rawctx [InitializeSecurityContext \ - $Credentials \ - $Handle \ - $Target \ - $Inattr \ - 0 \ - $Datarep \ - [list ] \ - 0] - } else { - set rawctx [AcceptSecurityContext \ - $Credentials \ - $Handle \ - [list ] \ - $Inattr \ - $Datarep] - } - lassign $rawctx State Handle out Outattr Expiration extra - if {$State in {ok expired}} { - return [list done [_gather_secbuf_data $out]] - } else { - return [list continue [_gather_secbuf_data $out]] - } - } -} - -# Take the next step in an SSPI negotiation -# Returns -# {done data extradata} -# {continue data} -# {expired data} -proc twapi::sspi_step {ctx {received ""}} { - variable _sspi_state - variable _client_security_context_syms - - _sspi_validate_handle $ctx - - dict with _sspi_state($ctx) { - # Note the dictionary content variables are - # State, Handle, Output, Outattr, Expiration, - # Ctxtype, Inattr, Target, Datarep, Credentials - - # Append new input to existing input - append Input $received - switch -exact -- $State { - ok { - set data [_gather_secbuf_data $Output] - set Output {} - - # $Input at this point contains left over input that is - # actually application data (streaming case). - # Application should pass this to decrypt commands - return [list done $data $Input[set Input ""]] - } - continue { - # Continue with the negotiation - if {[string length $Input] != 0} { - # Pass in received data to SSPI. - # Most providers take only the first buffer - # but SChannel/UNISP need the second. Since - # others don't seem to mind the second buffer - # we always always include it - # 2 -> SECBUFFER_TOKEN, 0 -> SECBUFFER_EMPTY - set inbuflist [list [list 2 $Input] [list 0]] - if {$Ctxtype eq "client"} { - set rawctx [InitializeSecurityContext \ - $Credentials \ - $Handle \ - $Target \ - $Inattr \ - 0 \ - $Datarep \ - $inbuflist \ - 0] - } else { - set rawctx [AcceptSecurityContext \ - $Credentials \ - $Handle \ - $inbuflist \ - $Inattr \ - $Datarep] - } - lassign $rawctx State Handle out Outattr Expiration extra - lappend Output {*}$out - # When the error is incomplete_credentials, we will retry - # with the SEC_I_INCOMPLETE_CREDENTIALS flag set. For - # this the Input should remain the same. Otherwise set it - # to whatever remains to be processed in the buffer. - if {$State ne "incomplete_credentials"} { - set Input $extra - } - # Will recurse at proc end - } else { - # There was no received data. Return any data - # to be sent to remote end - set data [_gather_secbuf_data $Output] - set Output {} - return [list continue $data ""] - } - } - incomplete_message { - # Caller has to get more data from remote end - set State continue - return [list continue "" ""] - } - expired { - # Remote end closed in middle of negotiation - return [list disconnected "" ""] - } - incomplete_credentials { - # In this state, the remote has asked for an client certificate. - # In this case, we ask Schannel to limit itself to whatever - # the user supplied and retry. Servers that ask for a cert - # but do not mandate it will then proceed. However, we only - # do this if we have not already tried this route. If we have, - # then generate an error. The real solution would be to attempt - # to look up new credentials by retrieving a certificate - # from the certificate store (possibly by asking the user) but - # this is not implemented. - # TBD - get client cert from user. See - # https://github.com/david-maw/StreamSSL and - # https://www.codeproject.com/Articles/1094525/Configuring-SSL-and-Client-Certificate-Validation - if {$Inattr & $_client_security_context_syms(usesuppliedcreds)} { - # Already tried with this. Give up. - set ermsg "Handling of incomplete credentials not implemented. If using TLS, specify the -credentials option to tls_socket to provide credentials." - error $ermsg "" [list TWAPI SSPI UNSUPPORTED $ermsg] - } - set Inattr [expr {$Inattr | $_client_security_context_syms(usesuppliedcreds)}] - set State continue - # Fall to bottom to recurse one more time - } - complete - - complete_and_continue { - # Should not actually occur as sspi.c no longer returns - # these codes - error "State $State handling not implemented." - } - } - } - - # Recurse to return next state. - # This has to be OUTSIDE the [dict with] above else it will not - # see the updated values - return [sspi_step $ctx] -} - -# Return a server context -proc twapi::sspi_server_context {cred clientdata args} { - _init_security_context_syms - variable _server_security_context_syms - - parseargs args { - {datarep.arg network {native network}} - confidentiality.bool - connection.bool - delegate.bool - extendederror.bool - identify.bool - integrity.bool - mutualauth.bool - replaydetect.bool - sequencedetect.bool - stream.bool - } -maxleftover 0 -nulldefault -setvars - - set context_flags 0 - foreach {opt flag} [array get _server_security_context_syms] { - if {[set $opt]} { - set context_flags [expr {$context_flags | $flag}] - } - } - - set drep [kl_get {native 0x10 network 0} $datarep] - return [_construct_sspi_security_context \ - sspiserver#[TwapiId] \ - [AcceptSecurityContext \ - $cred \ - "" \ - [list [list 2 $clientdata]] \ - $context_flags \ - $drep] \ - server \ - $context_flags \ - "" \ - $cred \ - $drep \ - ] -} - - -# Get the security context flags after completion of request -proc ::twapi::sspi_context_features {ctx} { - variable _sspi_state - - set ctxh [_sspi_context_handle $ctx] - - _init_security_context_syms - - # We could directly look in the context itself but intead we make - # an explicit call, just in case they change after initial setup - set flags [QueryContextAttributes $ctxh 14] - - # Mapping of symbols depends on whether it is a client or server - # context - if {[dict get $_sspi_state($ctx) Ctxtype] eq "client"} { - upvar 0 [namespace current]::_client_security_context_syms syms - } else { - upvar 0 [namespace current]::_server_security_context_syms syms - } - - set result [list -raw $flags] - foreach {sym flag} [array get syms] { - lappend result -$sym [expr {($flag & $flags) != 0}] - } - - return $result -} - -# Get the user name for a security context -proc twapi::sspi_context_username {ctx} { - return [QueryContextAttributes [_sspi_context_handle $ctx] 1] -} - -# Get the field size information for a security context -# TBD - update for SSL -proc twapi::sspi_context_sizes {ctx} { - set sizes [QueryContextAttributes [_sspi_context_handle $ctx] 0] - return [twine {-maxtoken -maxsig -blocksize -trailersize} $sizes] -} - -proc twapi::sspi_remote_cert {ctx} { - return [QueryContextAttributes [_sspi_context_handle $ctx] 0x53] -} - -proc twapi::sspi_local_cert {ctx} { - return [QueryContextAttributes [_sspi_context_handle $ctx] 0x54] -} - -proc twapi::sspi_issuers_accepted_by_peer {ctx} { - return [QueryContextAttributes [_sspi_context_handle $ctx] 0x59] -} - -# Returns a signature -proc twapi::sspi_sign {ctx data args} { - parseargs args { - {seqnum.int 0} - {qop.int 0} - } -maxleftover 0 -setvars - - return [MakeSignature \ - [_sspi_context_handle $ctx] \ - $qop \ - $data \ - $seqnum] -} - -# Verify signature -proc twapi::sspi_verify_signature {ctx sig data args} { - parseargs args { - {seqnum.int 0} - } -maxleftover 0 -setvars - - # Buffer type 2 - Token, 1- Data - return [VerifySignature \ - [_sspi_context_handle $ctx] \ - [list [list 2 $sig] [list 1 $data]] \ - $seqnum] -} - -# Encrypts a data as per a context -# Returns {securitytrailer encrypteddata padding} -proc twapi::sspi_encrypt {ctx data args} { - parseargs args { - {seqnum.int 0} - {qop.int 0} - } -maxleftover 0 -setvars - - return [EncryptMessage \ - [_sspi_context_handle $ctx] \ - $qop \ - $data \ - $seqnum] -} - -proc twapi::sspi_encrypt_stream {ctx data args} { - variable _sspi_state - - set h [_sspi_context_handle $ctx] - - # TBD - docment options - parseargs args { - {qop.int 0} - } -maxleftover 0 -setvars - - set enc "" - while {[string length $data]} { - lassign [EncryptStream $h $qop $data] fragment data - lappend enc $fragment - } - - return [join $enc ""] -} - -# chan must be in binary mode -proc twapi::sspi_encrypt_and_write {ctx data chan args} { - variable _sspi_state - - set h [_sspi_context_handle $ctx] - - parseargs args { - {qop.int 0} - {flush.bool 1} - } -maxleftover 0 -setvars - - while {[string length $data]} { - lassign [EncryptStream $h $qop $data] fragment data - puts -nonewline $chan $fragment - } - - if {$flush} { - chan flush $chan - } -} - - -# Decrypts a message -# TBD - why does this not return a status like sspi_decrypt_stream ? -proc twapi::sspi_decrypt {ctx sig data padding args} { - variable _sspi_state - _sspi_validate_handle $ctx - - parseargs args { - {seqnum.int 0} - } -maxleftover 0 -setvars - - # Buffer type 2 - Token, 1- Data, 9 - padding - set decrypted [DecryptMessage \ - [dict get $_sspi_state($ctx) Handle] \ - [list [list 2 $sig] [list 1 $data] [list 9 $padding]] \ - $seqnum] - set plaintext {} - # Pick out only the data buffers, ignoring pad buffers and signature - # Optimize copies by keeping as a list so in the common case of a - # single buffer can return it as is. Multiple buffers are expensive - # because Tcl will shimmer each byte array into a list and then - # incur additional copies during joining - foreach buf $decrypted { - # SECBUFFER_DATA -> 1 - if {[lindex $buf 0] == 1} { - lappend plaintext [lindex $buf 1] - } - } - - if {[llength $plaintext] < 2} { - return [lindex $plaintext 0] - } else { - return [join $plaintext ""] - } -} - -# Decrypts a stream -proc twapi::sspi_decrypt_stream {ctx data} { - variable _sspi_state - set hctx [_sspi_context_handle $ctx] - - # SSL decryption is done in max size chunks. - # We will loop collecting as much data as possible. Collecting - # as a list and joining at end minimizes internal byte copies - set plaintext {} - lassign [DecryptStream $hctx [dict get $_sspi_state($ctx) Input] $data] status decrypted extra - lappend plaintext $decrypted - - # TBD - handle renegotiate status - while {$status eq "ok" && [string length $extra]} { - # See if additional data and loop again - lassign [DecryptStream $hctx $extra] status decrypted extra - lappend plaintext $decrypted - } - - dict set _sspi_state($ctx) Input $extra - if {$status eq "incomplete_message"} { - set status ok - } - return [list $status [join $plaintext ""]] -} - - -################################################################ -# Utility procs - - -# Construct a high level SSPI security context structure -# rawctx is context as returned from C level code -proc twapi::_construct_sspi_security_context {id rawctx ctxtype inattr target credentials datarep} { - variable _sspi_state - - set _sspi_state($id) [dict merge [dict create Ctxtype $ctxtype \ - Inattr $inattr \ - Target $target \ - Datarep $datarep \ - Credentials $credentials] \ - [twine \ - {State Handle Output Outattr Expiration Input} \ - $rawctx]] - - return $id -} - -proc twapi::_sspi_validate_handle {ctx} { - variable _sspi_state - - if {![info exists _sspi_state($ctx)]} { - badargs! "Invalid SSPI security context handle $ctx" 3 - } -} - -proc twapi::_sspi_context_handle {ctx} { - variable _sspi_state - - if {![info exists _sspi_state($ctx)]} { - badargs! "Invalid SSPI security context handle $ctx" 3 - } - - return [dict get $_sspi_state($ctx) Handle] -} - -proc twapi::_gather_secbuf_data {bufs} { - if {[llength $bufs] == 1} { - return [lindex [lindex $bufs 0] 1] - } else { - set data {} - foreach buf $bufs { - # First element is buffer type, which we do not care - # Second element is actual data - lappend data [lindex $buf 1] - } - return [join $data {}] - } -} - -if {0} { - TBD - delete - set cred [sspi_acquire_credentials -package ssl -role client] - set client [sspi_client_context $cred -stream 1 -manualvalidation 1] - set out [sspi_step $client] - set so [socket 192.168.1.127 443] - fconfigure $so -blocking 0 -buffering none -translation binary - puts -nonewline $so [lindex $out 1] - - set data [read $so] - set out [sspi_step $client $data] - puts -nonewline $so [lindex $out 1] - - set data [read $so] - set out [sspi_step $client $data] - - set out [sspi_encrypt_stream $client "GET / HTTP/1.0\r\n\r\n"] - puts -nonewline $so $out - set data [read $so] - set d [sspi_decrypt_stream $client $data] - sspi_shutdown_context $client - close $so ; sspi_free_credentials $cred ; sspi_free_context $client - sspi_context_free $client - sspi_shutdown_context $client - - # INTERNAL client-server - proc 'sslsetup {} { - uplevel #0 { - twapi - source ../tests/testutil.tcl - set ca [make_test_certs] - set cacert [cert_store_find_certificate $ca subject_substring twapitestca] - set scert [cert_store_find_certificate $ca subject_substring twapitestserver] - set scred [sspi_acquire_credentials -package ssl -role server -credentials [sspi_schannel_credentials -certificates [list $scert]]] - set ccert [cert_store_find_certificate $ca subject_substring twapitestclient] - set ccred [sspi_acquire_credentials -package ssl -role client -credentials [sspi_schannel_credentials]] - set cctx [sspi_client_context $ccred -stream 1 -manualvalidation 1] - set cstep [sspi_step $cctx] - - set sctx [sspi_server_context $scred [lindex $cstep 1] -stream 1] - set sstep [sspi_step $sctx] - set cstep [sspi_step $cctx [lindex $sstep 1]] - set sstep [sspi_step $sctx [lindex $cstep 1]] - set cstep [sspi_step $cctx [lindex $sstep 1]] - } - } - set out [sspi_encrypt_stream $cctx "This is a test"] - - sspi_decrypt_stream $sctx $out - sspi_decrypt_stream $sctx "" - set out [sspi_encrypt_stream $sctx "This is a testx"] - sspi_decrypt_stream $cctx $out - - proc 'ccred {} { - set store [cert_system_store_open twapitest user] - set ccert [cert_store_find_certificate $store subject_substring twapitestclient] - set ccred [sspi_acquire_credentials -package ssl -role client -credentials [sspi_schannel_credentials -certificates [list $ccert]]] - cert_store_release $store - cert_release $ccert - return $ccred - } - -} +# +# Copyright (c) 2007-2013, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + + + # Holds SSPI security contexts indexed by a handle + # Each element is a dict with the following keys: + # State - state of the security context - see sspi_step + # Handle - the Win32 SecHandle for the context + # Input - Pending input from remote end to be passed in to + # SSPI provider (only valid for streams) + # Output - list of SecBuffers that contain data to be sent + # to remote end during a SSPI negotiation + # Inattr - requested context attributes + # Outattr - context attributes returned from service provider + # (currently not used) + # Expiration - time when context will expire + # Ctxtype - client, server + # Target - + # Datarep - data representation format + # Credentials - handle for credentials to pass to sspi provider + variable _sspi_state + array set _sspi_state {} + + proc* _init_security_context_syms {} { + variable _server_security_context_syms + variable _client_security_context_syms + variable _secpkg_capability_syms + + + # Symbols used for mapping server security context flags + array set _server_security_context_syms { + confidentiality 0x10 + connection 0x800 + delegate 0x1 + extendederror 0x8000 + identify 0x80000 + integrity 0x20000 + mutualauth 0x2 + replaydetect 0x4 + sequencedetect 0x8 + stream 0x10000 + } + + # Symbols used for mapping client security context flags + array set _client_security_context_syms { + confidentiality 0x10 + connection 0x800 + delegate 0x1 + extendederror 0x4000 + identify 0x20000 + integrity 0x10000 + manualvalidation 0x80000 + mutualauth 0x2 + replaydetect 0x4 + sequencedetect 0x8 + stream 0x8000 + usesessionkey 0x20 + usesuppliedcreds 0x80 + } + + # Symbols used for mapping security package capabilities + array set _secpkg_capability_syms { + integrity 0x00000001 + privacy 0x00000002 + tokenonly 0x00000004 + datagram 0x00000008 + connection 0x00000010 + multirequired 0x00000020 + clientonly 0x00000040 + extendederror 0x00000080 + impersonation 0x00000100 + acceptwin32name 0x00000200 + stream 0x00000400 + negotiable 0x00000800 + gsscompatible 0x00001000 + logon 0x00002000 + asciibuffers 0x00004000 + fragment 0x00008000 + mutualauth 0x00010000 + delegation 0x00020000 + readonlywithchecksum 0x00040000 + restrictedtokens 0x00080000 + negoextender 0x00100000 + negotiable2 0x00200000 + appcontainerpassthrough 0x00400000 + appcontainerchecks 0x00800000 + } + } {} +} + +# Return list of security packages +proc twapi::sspi_enumerate_packages {args} { + set pkgs [EnumerateSecurityPackages] + if {[llength $args] == 0} { + set names [list ] + foreach pkg $pkgs { + lappend names [kl_get $pkg Name] + } + return $names + } + + # TBD - why is this hyphenated ? + array set opts [parseargs args { + all capabilities version rpcid maxtokensize name comment + } -maxleftover 0 -hyphenated] + + _init_security_context_syms + variable _secpkg_capability_syms + set retdata {} + foreach pkg $pkgs { + set rec {} + if {$opts(-all) || $opts(-capabilities)} { + lappend rec -capabilities [_make_symbolic_bitmask [kl_get $pkg fCapabilities] _secpkg_capability_syms] + } + foreach {opt field} {-version wVersion -rpcid wRPCID -maxtokensize cbMaxToken -name Name -comment Comment} { + if {$opts(-all) || $opts($opt)} { + lappend rec $opt [kl_get $pkg $field] + } + } + dict set recdata [kl_get $pkg Name] $rec + } + return $recdata +} + +proc twapi::sspi_schannel_credentials args { + # TBD - do all these options work ? Check before documenting + # since they seem to be duplicated in InitializeSecurityContext + parseargs args { + certificates.arg + {rootstore.arg NULL} + sessionlifespan.int + usedefaultclientcert.bool + {disablereconnects.bool 0 0x80} + {revocationcheck.arg none {full endonly excluderoot none}} + {ignoreerrorrevocationoffline.bool 0 0x1000} + {ignoreerrornorevocationcheck.bool 0 0x800} + {validateservercert.bool 1} + cipherstrength.arg + protocols.arg + } -setvars -nulldefault -maxleftover 0 + + set flags [expr {$disablereconnects | $ignoreerrornorevocationcheck | $ignoreerrorrevocationoffline}] + incr flags [dict get { + none 0 full 0x200 excluderoot 0x400 endonly 0x100 + } $revocationcheck] + + if {$validateservercert} { + incr flags 0x20; # SCH_CRED_AUTO_CRED_VALIDATION + } else { + incr flags 0x8; # SCH_CRED_MANUAL_CRED_VALIDATION + } + if {$usedefaultclientcert} { + incr flags 0x40; # SCH_CRED_USE_DEFAULT_CREDS + } else { + incr flags 0x10; # SCH_CRED_NO_DEFAULT_CREDS + } + + set protbits 0 + foreach prot $protocols { + set protbits [expr { + $protbits | [dict! { + ssl2 0xc ssl3 0x30 tls1 0xc0 tls1.1 0x300 tls1.2 0xc00 + } $prot] + }] + } + + switch [llength $cipherstrength] { + 0 { set minbits 0 ; set maxbits 0 } + 1 { set minbits [lindex $cipherstrength 0] ; set maxbits $minbits } + 2 { + set minbits [lindex $cipherstrength 0] + set maxbits [lindex $cipherstrength 1] + } + default { + error "Invalid value '$cipherstrength' for option -cipherstrength" + } + } + + # 4 -> SCHANNEL_CRED_VERSION + return [list 4 $certificates $rootstore {} {} $protbits $minbits $maxbits $sessionlifespan $flags 0] +} + +proc twapi::sspi_winnt_identity_credentials {user domain password} { + return [list $user $domain $password] +} + +proc twapi::sspi_acquire_credentials {args} { + parseargs args { + {credentials.arg {}} + principal.arg + {package.arg NTLM} + {role.arg both {client server inbound outbound both}} + getexpiration + } -maxleftover 0 -setvars -nulldefault + + set creds [AcquireCredentialsHandle $principal \ + [dict* { + unisp {Microsoft Unified Security Protocol Provider} + ssl {Microsoft Unified Security Protocol Provider} + tls {Microsoft Unified Security Protocol Provider} + } $package] \ + [kl_get {inbound 1 server 1 outbound 2 client 2 both 3} $role] \ + "" $credentials] + + if {$getexpiration} { + return [kl_create2 {-handle -expiration} $creds] + } else { + return [lindex $creds 0] + } +} + +# Frees credentials +proc twapi::sspi_free_credentials {cred} { + FreeCredentialsHandle $cred +} + +# Return a client context +proc twapi::sspi_client_context {cred args} { + _init_security_context_syms + variable _client_security_context_syms + + parseargs args { + target.arg + {datarep.arg network {native network}} + confidentiality.bool + connection.bool + delegate.bool + extendederror.bool + identify.bool + integrity.bool + manualvalidation.bool + mutualauth.bool + replaydetect.bool + sequencedetect.bool + stream.bool + usesessionkey.bool + usesuppliedcreds.bool + } -maxleftover 0 -nulldefault -setvars + + set context_flags 0 + foreach {opt flag} [array get _client_security_context_syms] { + if {[set $opt]} { + set context_flags [expr {$context_flags | $flag}] + } + } + + set drep [kl_get {native 0x10 network 0} $datarep] + return [_construct_sspi_security_context \ + sspiclient#[TwapiId] \ + [InitializeSecurityContext \ + $cred \ + "" \ + $target \ + $context_flags \ + 0 \ + $drep \ + [list ] \ + 0] \ + client \ + $context_flags \ + $target \ + $cred \ + $drep \ + ] +} + +# Delete a security context +proc twapi::sspi_delete_context {ctx} { + variable _sspi_state + set h [_sspi_context_handle $ctx] + if {[llength $h]} { + DeleteSecurityContext $h + } + unset _sspi_state($ctx) +} + +# Shuts down a security context in orderly fashion +# Caller should start sspi_step +proc twapi::sspi_shutdown_context {ctx} { + variable _sspi_state + + _sspi_context_handle $ctx; # Verify handle + dict with _sspi_state($ctx) { + switch -nocase -- [lindex [QueryContextAttributes $Handle 10] 4] { + schannel - + "Microsoft Unified Security Protocol Provider" {} + default { return } + } + + # Signal to security provider we want to shutdown + Twapi_ApplyControlToken_SCHANNEL_SHUTDOWN $Handle + + if {$Ctxtype eq "client"} { + set rawctx [InitializeSecurityContext \ + $Credentials \ + $Handle \ + $Target \ + $Inattr \ + 0 \ + $Datarep \ + [list ] \ + 0] + } else { + set rawctx [AcceptSecurityContext \ + $Credentials \ + $Handle \ + [list ] \ + $Inattr \ + $Datarep] + } + lassign $rawctx State Handle out Outattr Expiration extra + if {$State in {ok expired}} { + return [list done [_gather_secbuf_data $out]] + } else { + return [list continue [_gather_secbuf_data $out]] + } + } +} + +# Take the next step in an SSPI negotiation +# Returns +# {done data extradata} +# {continue data} +# {expired data} +proc twapi::sspi_step {ctx {received ""}} { + variable _sspi_state + variable _client_security_context_syms + + _sspi_validate_handle $ctx + + dict with _sspi_state($ctx) { + # Note the dictionary content variables are + # State, Handle, Output, Outattr, Expiration, + # Ctxtype, Inattr, Target, Datarep, Credentials + + # Append new input to existing input + append Input $received + switch -exact -- $State { + ok { + set data [_gather_secbuf_data $Output] + set Output {} + + # $Input at this point contains left over input that is + # actually application data (streaming case). + # Application should pass this to decrypt commands + return [list done $data $Input[set Input ""]] + } + continue { + # Continue with the negotiation + if {[string length $Input] != 0} { + # Pass in received data to SSPI. + # Most providers take only the first buffer + # but SChannel/UNISP need the second. Since + # others don't seem to mind the second buffer + # we always always include it + # 2 -> SECBUFFER_TOKEN, 0 -> SECBUFFER_EMPTY + set inbuflist [list [list 2 $Input] [list 0]] + if {$Ctxtype eq "client"} { + set rawctx [InitializeSecurityContext \ + $Credentials \ + $Handle \ + $Target \ + $Inattr \ + 0 \ + $Datarep \ + $inbuflist \ + 0] + } else { + set rawctx [AcceptSecurityContext \ + $Credentials \ + $Handle \ + $inbuflist \ + $Inattr \ + $Datarep] + } + lassign $rawctx State Handle out Outattr Expiration extra + lappend Output {*}$out + # When the error is incomplete_credentials, we will retry + # with the SEC_I_INCOMPLETE_CREDENTIALS flag set. For + # this the Input should remain the same. Otherwise set it + # to whatever remains to be processed in the buffer. + if {$State ne "incomplete_credentials"} { + set Input $extra + } + # Will recurse at proc end + } else { + # There was no received data. Return any data + # to be sent to remote end + set data [_gather_secbuf_data $Output] + set Output {} + return [list continue $data ""] + } + } + incomplete_message { + # Caller has to get more data from remote end + set State continue + return [list continue "" ""] + } + expired { + # Remote end closed in middle of negotiation + return [list disconnected "" ""] + } + incomplete_credentials { + # In this state, the remote has asked for an client certificate. + # In this case, we ask Schannel to limit itself to whatever + # the user supplied and retry. Servers that ask for a cert + # but do not mandate it will then proceed. However, we only + # do this if we have not already tried this route. If we have, + # then generate an error. The real solution would be to attempt + # to look up new credentials by retrieving a certificate + # from the certificate store (possibly by asking the user) but + # this is not implemented. + # TBD - get client cert from user. See + # https://github.com/david-maw/StreamSSL and + # https://www.codeproject.com/Articles/1094525/Configuring-SSL-and-Client-Certificate-Validation + if {$Inattr & $_client_security_context_syms(usesuppliedcreds)} { + # Already tried with this. Give up. + set ermsg "Handling of incomplete credentials not implemented. If using TLS, specify the -credentials option to tls_socket to provide credentials." + error $ermsg "" [list TWAPI SSPI UNSUPPORTED $ermsg] + } + set Inattr [expr {$Inattr | $_client_security_context_syms(usesuppliedcreds)}] + set State continue + # Fall to bottom to recurse one more time + } + complete - + complete_and_continue { + # Should not actually occur as sspi.c no longer returns + # these codes + error "State $State handling not implemented." + } + } + } + + # Recurse to return next state. + # This has to be OUTSIDE the [dict with] above else it will not + # see the updated values + return [sspi_step $ctx] +} + +# Return a server context +proc twapi::sspi_server_context {cred clientdata args} { + _init_security_context_syms + variable _server_security_context_syms + + parseargs args { + {datarep.arg network {native network}} + confidentiality.bool + connection.bool + delegate.bool + extendederror.bool + identify.bool + integrity.bool + mutualauth.bool + replaydetect.bool + sequencedetect.bool + stream.bool + } -maxleftover 0 -nulldefault -setvars + + set context_flags 0 + foreach {opt flag} [array get _server_security_context_syms] { + if {[set $opt]} { + set context_flags [expr {$context_flags | $flag}] + } + } + + set drep [kl_get {native 0x10 network 0} $datarep] + return [_construct_sspi_security_context \ + sspiserver#[TwapiId] \ + [AcceptSecurityContext \ + $cred \ + "" \ + [list [list 2 $clientdata]] \ + $context_flags \ + $drep] \ + server \ + $context_flags \ + "" \ + $cred \ + $drep \ + ] +} + + +# Get the security context flags after completion of request +proc ::twapi::sspi_context_features {ctx} { + variable _sspi_state + + set ctxh [_sspi_context_handle $ctx] + + _init_security_context_syms + + # We could directly look in the context itself but intead we make + # an explicit call, just in case they change after initial setup + set flags [QueryContextAttributes $ctxh 14] + + # Mapping of symbols depends on whether it is a client or server + # context + if {[dict get $_sspi_state($ctx) Ctxtype] eq "client"} { + upvar 0 [namespace current]::_client_security_context_syms syms + } else { + upvar 0 [namespace current]::_server_security_context_syms syms + } + + set result [list -raw $flags] + foreach {sym flag} [array get syms] { + lappend result -$sym [expr {($flag & $flags) != 0}] + } + + return $result +} + +# Get the user name for a security context +proc twapi::sspi_context_username {ctx} { + return [QueryContextAttributes [_sspi_context_handle $ctx] 1] +} + +# Get the field size information for a security context +# TBD - update for SSL +proc twapi::sspi_context_sizes {ctx} { + set sizes [QueryContextAttributes [_sspi_context_handle $ctx] 0] + return [twine {-maxtoken -maxsig -blocksize -trailersize} $sizes] +} + +proc twapi::sspi_remote_cert {ctx} { + return [QueryContextAttributes [_sspi_context_handle $ctx] 0x53] +} + +proc twapi::sspi_local_cert {ctx} { + return [QueryContextAttributes [_sspi_context_handle $ctx] 0x54] +} + +proc twapi::sspi_issuers_accepted_by_peer {ctx} { + return [QueryContextAttributes [_sspi_context_handle $ctx] 0x59] +} + +# Returns a signature +proc twapi::sspi_sign {ctx data args} { + parseargs args { + {seqnum.int 0} + {qop.int 0} + } -maxleftover 0 -setvars + + return [MakeSignature \ + [_sspi_context_handle $ctx] \ + $qop \ + $data \ + $seqnum] +} + +# Verify signature +proc twapi::sspi_verify_signature {ctx sig data args} { + parseargs args { + {seqnum.int 0} + } -maxleftover 0 -setvars + + # Buffer type 2 - Token, 1- Data + return [VerifySignature \ + [_sspi_context_handle $ctx] \ + [list [list 2 $sig] [list 1 $data]] \ + $seqnum] +} + +# Encrypts a data as per a context +# Returns {securitytrailer encrypteddata padding} +proc twapi::sspi_encrypt {ctx data args} { + parseargs args { + {seqnum.int 0} + {qop.int 0} + } -maxleftover 0 -setvars + + return [EncryptMessage \ + [_sspi_context_handle $ctx] \ + $qop \ + $data \ + $seqnum] +} + +proc twapi::sspi_encrypt_stream {ctx data args} { + variable _sspi_state + + set h [_sspi_context_handle $ctx] + + # TBD - docment options + parseargs args { + {qop.int 0} + } -maxleftover 0 -setvars + + set enc "" + while {[string length $data]} { + lassign [EncryptStream $h $qop $data] fragment data + lappend enc $fragment + } + + return [join $enc ""] +} + +# chan must be in binary mode +proc twapi::sspi_encrypt_and_write {ctx data chan args} { + variable _sspi_state + + set h [_sspi_context_handle $ctx] + + parseargs args { + {qop.int 0} + {flush.bool 1} + } -maxleftover 0 -setvars + + while {[string length $data]} { + lassign [EncryptStream $h $qop $data] fragment data + puts -nonewline $chan $fragment + } + + if {$flush} { + chan flush $chan + } +} + + +# Decrypts a message +# TBD - why does this not return a status like sspi_decrypt_stream ? +proc twapi::sspi_decrypt {ctx sig data padding args} { + variable _sspi_state + _sspi_validate_handle $ctx + + parseargs args { + {seqnum.int 0} + } -maxleftover 0 -setvars + + # Buffer type 2 - Token, 1- Data, 9 - padding + set decrypted [DecryptMessage \ + [dict get $_sspi_state($ctx) Handle] \ + [list [list 2 $sig] [list 1 $data] [list 9 $padding]] \ + $seqnum] + set plaintext {} + # Pick out only the data buffers, ignoring pad buffers and signature + # Optimize copies by keeping as a list so in the common case of a + # single buffer can return it as is. Multiple buffers are expensive + # because Tcl will shimmer each byte array into a list and then + # incur additional copies during joining + foreach buf $decrypted { + # SECBUFFER_DATA -> 1 + if {[lindex $buf 0] == 1} { + lappend plaintext [lindex $buf 1] + } + } + + if {[llength $plaintext] < 2} { + return [lindex $plaintext 0] + } else { + return [join $plaintext ""] + } +} + +# Decrypts a stream +proc twapi::sspi_decrypt_stream {ctx data} { + variable _sspi_state + set hctx [_sspi_context_handle $ctx] + + # SSL decryption is done in max size chunks. + # We will loop collecting as much data as possible. Collecting + # as a list and joining at end minimizes internal byte copies + set plaintext {} + lassign [DecryptStream $hctx [dict get $_sspi_state($ctx) Input] $data] status decrypted extra + lappend plaintext $decrypted + + # TBD - handle renegotiate status + while {$status eq "ok" && [string length $extra]} { + # See if additional data and loop again + lassign [DecryptStream $hctx $extra] status decrypted extra + lappend plaintext $decrypted + } + + dict set _sspi_state($ctx) Input $extra + if {$status eq "incomplete_message"} { + set status ok + } + return [list $status [join $plaintext ""]] +} + + +################################################################ +# Utility procs + + +# Construct a high level SSPI security context structure +# rawctx is context as returned from C level code +proc twapi::_construct_sspi_security_context {id rawctx ctxtype inattr target credentials datarep} { + variable _sspi_state + + set _sspi_state($id) [dict merge [dict create Ctxtype $ctxtype \ + Inattr $inattr \ + Target $target \ + Datarep $datarep \ + Credentials $credentials] \ + [twine \ + {State Handle Output Outattr Expiration Input} \ + $rawctx]] + + return $id +} + +proc twapi::_sspi_validate_handle {ctx} { + variable _sspi_state + + if {![info exists _sspi_state($ctx)]} { + badargs! "Invalid SSPI security context handle $ctx" 3 + } +} + +proc twapi::_sspi_context_handle {ctx} { + variable _sspi_state + + if {![info exists _sspi_state($ctx)]} { + badargs! "Invalid SSPI security context handle $ctx" 3 + } + + return [dict get $_sspi_state($ctx) Handle] +} + +proc twapi::_gather_secbuf_data {bufs} { + if {[llength $bufs] == 1} { + return [lindex [lindex $bufs 0] 1] + } else { + set data {} + foreach buf $bufs { + # First element is buffer type, which we do not care + # Second element is actual data + lappend data [lindex $buf 1] + } + return [join $data {}] + } +} + +if {0} { + TBD - delete + set cred [sspi_acquire_credentials -package ssl -role client] + set client [sspi_client_context $cred -stream 1 -manualvalidation 1] + set out [sspi_step $client] + set so [socket 192.168.1.127 443] + fconfigure $so -blocking 0 -buffering none -translation binary + puts -nonewline $so [lindex $out 1] + + set data [read $so] + set out [sspi_step $client $data] + puts -nonewline $so [lindex $out 1] + + set data [read $so] + set out [sspi_step $client $data] + + set out [sspi_encrypt_stream $client "GET / HTTP/1.0\r\n\r\n"] + puts -nonewline $so $out + set data [read $so] + set d [sspi_decrypt_stream $client $data] + sspi_shutdown_context $client + close $so ; sspi_free_credentials $cred ; sspi_free_context $client + sspi_context_free $client + sspi_shutdown_context $client + + # INTERNAL client-server + proc 'sslsetup {} { + uplevel #0 { + twapi + source ../tests/testutil.tcl + set ca [make_test_certs] + set cacert [cert_store_find_certificate $ca subject_substring twapitestca] + set scert [cert_store_find_certificate $ca subject_substring twapitestserver] + set scred [sspi_acquire_credentials -package ssl -role server -credentials [sspi_schannel_credentials -certificates [list $scert]]] + set ccert [cert_store_find_certificate $ca subject_substring twapitestclient] + set ccred [sspi_acquire_credentials -package ssl -role client -credentials [sspi_schannel_credentials]] + set cctx [sspi_client_context $ccred -stream 1 -manualvalidation 1] + set cstep [sspi_step $cctx] + + set sctx [sspi_server_context $scred [lindex $cstep 1] -stream 1] + set sstep [sspi_step $sctx] + set cstep [sspi_step $cctx [lindex $sstep 1]] + set sstep [sspi_step $sctx [lindex $cstep 1]] + set cstep [sspi_step $cctx [lindex $sstep 1]] + } + } + set out [sspi_encrypt_stream $cctx "This is a test"] + + sspi_decrypt_stream $sctx $out + sspi_decrypt_stream $sctx "" + set out [sspi_encrypt_stream $sctx "This is a testx"] + sspi_decrypt_stream $cctx $out + + proc 'ccred {} { + set store [cert_system_store_open twapitest user] + set ccert [cert_store_find_certificate $store subject_substring twapitestclient] + set ccred [sspi_acquire_credentials -package ssl -role client -credentials [sspi_schannel_credentials -certificates [list $ccert]]] + cert_store_release $store + cert_release $ccert + return $ccred + } + +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/storage.tcl b/src/vendorlib_tcl8/twapi-5.0b1/storage.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/storage.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/storage.tcl index 72bd2d73..ef95e2cb 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/storage.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/storage.tcl @@ -1,616 +1,616 @@ -# -# Copyright (c) 2003, 2008 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# TBD - convert file spec to drive root path - -# Get info associated with a drive -proc twapi::get_volume_info {drive args} { - - set drive [_drive_rootpath $drive] - - array set opts [parseargs args { - all size freespace used useravail type serialnum label maxcomponentlen fstype attr device extents - } -maxleftover 0] - - if {$opts(all)} { - # -all option does not cover -type, -extents and -device - foreach opt { - all size freespace used useravail serialnum label maxcomponentlen fstype attr - } { - set opts($opt) 1 - } - } - - set result [list ] - if {$opts(size) || $opts(freespace) || $opts(used) || $opts(useravail)} { - lassign [GetDiskFreeSpaceEx $drive] useravail size freespace - foreach opt {size freespace useravail} { - if {$opts($opt)} { - lappend result -$opt [set $opt] - } - } - if {$opts(used)} { - lappend result -used [expr {$size - $freespace}] - } - } - - if {$opts(type)} { - set drive_type [get_drive_type $drive] - lappend result -type $drive_type - } - if {$opts(device)} { - if {[_is_unc $drive]} { - # UNC paths cannot be used with QueryDosDevice - lappend result -device "" - } else { - lappend result -device [QueryDosDevice [string range $drive 0 1]] - } - } - - if {$opts(extents)} { - set extents {} - if {! [_is_unc $drive]} { - trap { - set device_handle [create_file "\\\\.\\[string range $drive 0 1]" -createdisposition open_existing] - set bin [device_ioctl $device_handle 0x560000 -outputcount 32] - if {[binary scan $bin i nextents] != 1} { - error "Truncated information returned from ioctl 0x560000" - } - set off 8 - for {set i 0} {$i < $nextents} {incr i} { - if {[binary scan $bin "@$off i x4 w w" extent(-disknumber) extent(-startingoffset) extent(-extentlength)] != 3} { - error "Truncated information returned from ioctl 0x560000" - } - lappend extents [array get extent] - incr off 24; # Size of one extent element - } - } onerror {} { - # Do nothing, device does not support extents or access denied - # Empty list is returned - } finally { - if {[info exists device_handle]} { - CloseHandle $device_handle - } - } - } - - lappend result -extents $extents - } - - if {$opts(serialnum) || $opts(label) || $opts(maxcomponentlen) - || $opts(fstype) || $opts(attr)} { - foreach {label serialnum maxcomponentlen attr fstype} \ - [GetVolumeInformation $drive] { break } - foreach opt {label maxcomponentlen fstype} { - if {$opts($opt)} { - lappend result -$opt [set $opt] - } - } - if {$opts(serialnum)} { - set low [expr {$serialnum & 0x0000ffff}] - set high [expr {($serialnum >> 16) & 0x0000ffff}] - lappend result -serialnum [format "%.4X-%.4X" $high $low] - } - if {$opts(attr)} { - set attrs [list ] - foreach {sym val} { - case_preserved_names 2 - unicode_on_disk 4 - persistent_acls 8 - file_compression 16 - volume_quotas 32 - supports_sparse_files 64 - supports_reparse_points 128 - supports_remote_storage 256 - volume_is_compressed 0x8000 - supports_object_ids 0x10000 - supports_encryption 0x20000 - named_streams 0x40000 - read_only_volume 0x80000 - sequential_write_once 0x00100000 - supports_transactions 0x00200000 - supports_hard_links 0x00400000 - supports_extended_attributes 0x00800000 - supports_open_by_file_id 0x01000000 - supports_usn_journal 0x02000000 - } { - if {$attr & $val} { - lappend attrs $sym - } - } - lappend result -attr $attrs - } - } - - return $result -} -interp alias {} twapi::get_drive_info {} twapi::get_volume_info - - -# Check if disk has at least n bytes available for the user (NOT total free) -proc twapi::user_drive_space_available {drv space} { - return [expr {$space <= [lindex [get_drive_info $drv -useravail] 1]}] -} - -# Get the drive type -proc twapi::get_drive_type {drive} { - # set type [GetDriveType "[string trimright $drive :/\\]:\\"] - set type [GetDriveType [_drive_rootpath $drive]] - switch -exact -- $type { - 0 { return unknown} - 1 { return invalid} - 2 { return removable} - 3 { return fixed} - 4 { return remote} - 5 { return cdrom} - 6 { return ramdisk} - } -} - -# Get list of drives -proc twapi::find_logical_drives {args} { - array set opts [parseargs args {type.arg}] - - set drives [list ] - foreach drive [_drivemask_to_drivelist [GetLogicalDrives]] { - if {(![info exists opts(type)]) || - [lsearch -exact $opts(type) [get_drive_type $drive]] >= 0} { - lappend drives $drive - } - } - return $drives -} - -twapi::proc* twapi::drive_ready {drive} { - uplevel #0 package require twapi_device -} { - set drive [string trimright $drive "/\\"] - if {[string length $drive] != 2 || [string index $drive 1] ne ":"} { - error "Invalid drive specification" - } - set drive "\\\\.\\$drive" - - # Do our best to avoid the Windows "Drive not ready" dialog - # 1 -> SEM_FAILCRITICALERRORS - if {[min_os_version 6]} { - set old_mode [SetErrorMode 1] - } - trap { - - # We will first try using IOCTL_STORAGE_CHECK_VERIFY2 as that is - # much faster and only needs FILE_READ_ATTRIBUTES access. - set error [catch { - set h [create_file $drive -access file_read_attributes \ - -createdisposition open_existing -share {read write}] - device_ioctl $h 0x2d0800; # IOCTL_STORAGE_CHECK_VERIFY2 - }] - if {[info exists h]} { - close_handle $h - } - if {! $error} { - return 1; # Device is ready - } - - # On error, try the older slower method. Note we now need - # GENERIC_READ access. (NOTE: FILE_READ_DATA will not work with some - # volume types) - unset -nocomplain h - set error [catch { - set h [create_file $drive -access generic_read \ - -createdisposition open_existing -share {read write}] - device_ioctl $h 0x2d4800; # IOCTL_STORAGE_CHECK_VERIFY - }] - if {[info exists h]} { - close_handle $h - } - if {! $error} { - return 1; # Device is ready - } - - # Remote shares sometimes return access denied with the above - # even when actually available. Try with good old file exists - # on root directory - return [file exists "[string range $drive end-1 end]\\"] - } finally { - if {[min_os_version 6]} { - SetErrorMode $old_mode - } - } -} - - -# Set the drive label -proc twapi::set_drive_label {drive label} { - SetVolumeLabel [_drive_rootpath $drive] $label -} - -# Maps a drive letter to the given path -proc twapi::map_drive_local {drive path args} { - array set opts [parseargs args {raw}] - - set drive [string range [_drive_rootpath $drive] 0 1] - DefineDosDevice $opts(raw) $drive [file nativename $path] -} - - -# Unmaps a drive letter -proc twapi::unmap_drive_local {drive args} { - array set opts [parseargs args { - path.arg - raw - } -nulldefault] - - set drive [string range [_drive_rootpath $drive] 0 1] - - set flags $opts(raw) - setbits flags 0x2; # DDD_REMOVE_DEFINITION - if {$opts(path) ne ""} { - setbits flags 0x4; # DDD_EXACT_MATCH_ON_REMOVE - } - DefineDosDevice $flags $drive [file nativename $opts(path)] -} - - -# Callback from C code -proc twapi::_filesystem_monitor_handler {id changes} { - variable _filesystem_monitor_scripts - if {[info exists _filesystem_monitor_scripts($id)]} { - return [uplevel #0 [linsert $_filesystem_monitor_scripts($id) end $id $changes]] - } else { - # Callback queued after close. Ignore - } -} - -# Monitor file changes -proc twapi::begin_filesystem_monitor {path script args} { - variable _filesystem_monitor_scripts - - array set opts [parseargs args { - {subtree.bool 0} - {filename.bool 0 0x1} - {dirname.bool 0 0x2} - {attr.bool 0 0x4} - {size.bool 0 0x8} - {write.bool 0 0x10} - {access.bool 0 0x20} - {create.bool 0 0x40} - {secd.bool 0 0x100} - {pattern.arg ""} - {patterns.arg ""} - } -maxleftover 0] - - if {[string length $opts(pattern)] && - [llength $opts(patterns)]} { - error "Options -pattern and -patterns are mutually exclusive. Note option -pattern is deprecated." - } - - if {[string length $opts(pattern)]} { - # Old style single pattern. Convert to new -patterns - set opts(patterns) [list "+$opts(pattern)"] - } - - # Change to use \ style path separator as that is what the file monitoring functions return - if {[llength $opts(patterns)]} { - foreach pat $opts(patterns) { - # Note / is replaced by \\ within the pattern - # since \ needs to be escaped with another \ within - # string match patterns - lappend pats [string map [list / \\\\] $pat] - } - set opts(patterns) $pats - } - - set flags [expr { $opts(filename) | $opts(dirname) | $opts(attr) | - $opts(size) | $opts(write) | $opts(access) | - $opts(create) | $opts(secd)}] - - if {! $flags} { - # If no options specified, default to all - set flags 0x17f - } - - set id [Twapi_RegisterDirectoryMonitor $path $opts(subtree) $flags $opts(patterns)] - set _filesystem_monitor_scripts($id) $script - return $id -} - -# Stop monitoring of files -proc twapi::cancel_filesystem_monitor {id} { - variable _filesystem_monitor_scripts - if {[info exists _filesystem_monitor_scripts($id)]} { - Twapi_UnregisterDirectoryMonitor $id - unset _filesystem_monitor_scripts($id) - } -} - - -# Get list of volumes -proc twapi::find_volumes {} { - set vols [list ] - set found 1 - # Assumes there has to be at least one volume - lassign [FindFirstVolume] handle vol - while {$found} { - lappend vols $vol - lassign [FindNextVolume $handle] found vol - } - FindVolumeClose $handle - return $vols -} - -# Get list of volume mount points -proc twapi::find_volume_mount_points {vol} { - set mntpts [list ] - set found 1 - trap { - lassign [FindFirstVolumeMountPoint $vol] handle mntpt - } onerror {TWAPI_WIN32 18} { - # ERROR_NO_MORE_FILES - # No volume mount points - return [list ] - } onerror {TWAPI_WIN32 3} { - # Volume does not support them - return [list ] - } - - # At least one volume found - while {$found} { - lappend mntpts $mntpt - lassign [FindNextVolumeMountPoint $handle] found mntpt - } - FindVolumeMountPointClose $handle - return $mntpts -} - -# Set volume mount point -proc twapi::mount_volume {volpt volname} { - # Note we don't use _drive_rootpath for trimming since may not be root path - SetVolumeMountPoint "[string trimright $volpt /\\]\\" "[string trimright $volname /\\]\\" -} - -# Delete volume mount point -proc twapi::unmount_volume {volpt} { - # Note we don't use _drive_rootpath for trimming since may not be root path - DeleteVolumeMountPoint "[string trimright $volpt /\\]\\" -} - -# Get the volume mounted at a volume mount point -proc twapi::get_mounted_volume_name {volpt} { - # Note we don't use _drive_rootpath for trimming since may not be root path - return [GetVolumeNameForVolumeMountPoint "[string trimright $volpt /\\]\\"] -} - -# Get the mount point corresponding to a given path -proc twapi::get_volume_mount_point_for_path {path} { - return [GetVolumePathName [file nativename $path]] -} - - -# Return the times associated with a file -proc twapi::get_file_times {fd args} { - array set opts [parseargs args { - all - mtime - ctime - atime - } -maxleftover 0] - - # Figure out if fd is a file path, Tcl channel or a handle - set close_handle false - if {[file exists $fd]} { - # It's a file name - # 0x02000000 -> FILE_FLAG_BACKUP_SEMANTICS, always required in case - # opening a directory (even if SeBackupPrivilege is not held - set h [create_file $fd -createdisposition open_existing -flags 0x02000000] - set close_handle true - } elseif {[catch {fconfigure $fd}]} { - # Not a Tcl channel, See if handle - if {[pointer? $fd]} { - set h $fd - } else { - error "$fd is not an existing file, handle or Tcl channel." - } - } else { - # Tcl channel - set h [get_tcl_channel_handle $fd read] - } - - set result [list ] - - foreach opt {ctime atime mtime} time [GetFileTime $h] { - if {$opts(all) || $opts($opt)} { - lappend result -$opt $time - } - } - - if {$close_handle} { - CloseHandle $h - } - - return $result -} - - -# Set the times associated with a file -proc twapi::set_file_times {fd args} { - - array set opts [parseargs args { - mtime.arg - ctime.arg - atime.arg - preserveatime - } -maxleftover 0 -nulldefault] - - if {$opts(atime) ne "" && $opts(preserveatime)} { - win32_error 87 "Cannot specify -atime and -preserveatime at the same time." - } - if {$opts(preserveatime)} { - set opts(atime) -1; # Meaning preserve access to original - } - - # Figure out if fd is a file path, Tcl channel or a handle - set close_handle false - if {[file exists $fd]} { - if {$opts(preserveatime)} { - win32_error 87 "Cannot specify -preserveatime unless file is specified as a Tcl channel or a Win32 handle." - } - - # It's a file name - # 0x02000000 -> FILE_FLAG_BACKUP_SEMANTICS, always required in case - # opening a directory (even if SeBackupPrivilege is not held - set h [create_file $fd -access {generic_write} -createdisposition open_existing -flags 0x02000000] - set close_handle true - } elseif {[catch {fconfigure $fd}]} { - # Not a Tcl channel, assume a handle - set h $fd - } else { - # Tcl channel - set h [get_tcl_channel_handle $fd read] - } - - SetFileTime $h $opts(ctime) $opts(atime) $opts(mtime) - - if {$close_handle} { - CloseHandle $h - } - - return -} - -# Convert a device based path to a normalized Win32 path with drive letters -proc twapi::normalize_device_rooted_path {path args} { - # TBD - keep a cache ? - # For example, we need to map \Device\HarddiskVolume1 to C: - # Can only do that by enumerating logical drives - set npath [file nativename $path] - if {![string match -nocase {\\Device\\*} $npath]} { - error "$path is not a valid device based path." - } - array set device_map {} - foreach drive [find_logical_drives] { - set device_path [lindex [lindex [get_volume_info $drive -device] 1] 0] - if {$device_path ne ""} { - set len [string length $device_path] - if {[string equal -nocase -length $len $path $device_path]} { - # Prefix matches, must be terminated by end or path separator - set ch [string index $npath $len] - if {$ch eq "" || $ch eq "\\"} { - set path ${drive}[string range $npath $len end] - if {[llength $args]} { - upvar [lindex $args 0] retvar - set retvar $path - return 1 - } else { - return $path - } - } - } - } - } - - if {[llength $args]} { - return 0 - } else { - error "Could not map device based path '$path'" - } -} - -proc twapi::flush_channel {chan} { - flush $chan - FlushFileBuffers [get_tcl_channel_handle $chan write] -} - -proc twapi::find_file_open {path args} { - variable _find_tokens - variable _find_counter - parseargs args { - {detail.arg basic {basic full}} - } -setvars -maxleftover 0 - - set detail_level [expr {$detail eq "basic" ? 1 : 0}] - if {[min_os_version 6 1]} { - set flags 2; # FIND_FIRST_EX_LARGE_FETCH - Win 7 - } else { - set flags 0 - } - # 0 -> search op. Could be specified as 1 to limit search to - # directories but that is only advisory and does not seem to work - # in many cases. So don't bother making it an option. - lassign [FindFirstFileEx $path $detail_level 0 "" $flags] handle entry - set token ff#[incr _find_counter] - set _find_tokens($token) [list Handle $handle Entry $entry] - return $token -} - -proc twapi::find_file_close {token} { - variable _find_tokens - if {[info exists _find_tokens($token)]} { - FindClose [dict get $_find_tokens($token) Handle] - unset _find_tokens($token) - } - return -} - -proc twapi::decode_file_attributes {attrs} { - return [_make_symbolic_bitmask $attrs { - archive 0x20 - compressed 0x800 - device 0x40 - directory 0x10 - encrypted 0x4000 - hidden 0x2 - integrity_stream 0x8000 - normal 0x80 - not_content_indexed 0x2000 - no_scrub_data 0x20000 - offline 0x1000 - readonly 0x1 - recall_on_data_access 0x400000 - recall_on_open 0x40000 - reparse_point 0x400 - sparse_file 0x200 - system 0x4 - temporary 0x100 - virtual 0x10000 - }] -} - -proc twapi::find_file_next {token varname} { - variable _find_tokens - if {![info exists _find_tokens($token)]} { - return false - } - if {[dict exists $_find_tokens($token) Entry]} { - set entry [dict get $_find_tokens($token) Entry] - dict unset _find_tokens($token) Entry - } else { - set entry [FindNextFile [dict get $_find_tokens($token) Handle]] - } - if {[llength $entry]} { - upvar 1 $varname result - set result [twine {attrs ctime atime mtime size reserve0 reserve1 name altname} $entry] - return true - } else { - return false - } -} - -# Utility functions - -proc twapi::_drive_rootpath {drive} { - if {[_is_unc $drive]} { - # UNC - return "[string trimright $drive ]\\" - } else { - return "[string trimright $drive :/\\]:\\" - } -} - -proc twapi::_is_unc {path} { - return [expr {[string match {\\\\*} $path] || [string match //* $path]}] -} - - +# +# Copyright (c) 2003, 2008 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# TBD - convert file spec to drive root path + +# Get info associated with a drive +proc twapi::get_volume_info {drive args} { + + set drive [_drive_rootpath $drive] + + array set opts [parseargs args { + all size freespace used useravail type serialnum label maxcomponentlen fstype attr device extents + } -maxleftover 0] + + if {$opts(all)} { + # -all option does not cover -type, -extents and -device + foreach opt { + all size freespace used useravail serialnum label maxcomponentlen fstype attr + } { + set opts($opt) 1 + } + } + + set result [list ] + if {$opts(size) || $opts(freespace) || $opts(used) || $opts(useravail)} { + lassign [GetDiskFreeSpaceEx $drive] useravail size freespace + foreach opt {size freespace useravail} { + if {$opts($opt)} { + lappend result -$opt [set $opt] + } + } + if {$opts(used)} { + lappend result -used [expr {$size - $freespace}] + } + } + + if {$opts(type)} { + set drive_type [get_drive_type $drive] + lappend result -type $drive_type + } + if {$opts(device)} { + if {[_is_unc $drive]} { + # UNC paths cannot be used with QueryDosDevice + lappend result -device "" + } else { + lappend result -device [QueryDosDevice [string range $drive 0 1]] + } + } + + if {$opts(extents)} { + set extents {} + if {! [_is_unc $drive]} { + trap { + set device_handle [create_file "\\\\.\\[string range $drive 0 1]" -createdisposition open_existing] + set bin [device_ioctl $device_handle 0x560000 -outputcount 32] + if {[binary scan $bin i nextents] != 1} { + error "Truncated information returned from ioctl 0x560000" + } + set off 8 + for {set i 0} {$i < $nextents} {incr i} { + if {[binary scan $bin "@$off i x4 w w" extent(-disknumber) extent(-startingoffset) extent(-extentlength)] != 3} { + error "Truncated information returned from ioctl 0x560000" + } + lappend extents [array get extent] + incr off 24; # Size of one extent element + } + } onerror {} { + # Do nothing, device does not support extents or access denied + # Empty list is returned + } finally { + if {[info exists device_handle]} { + CloseHandle $device_handle + } + } + } + + lappend result -extents $extents + } + + if {$opts(serialnum) || $opts(label) || $opts(maxcomponentlen) + || $opts(fstype) || $opts(attr)} { + foreach {label serialnum maxcomponentlen attr fstype} \ + [GetVolumeInformation $drive] { break } + foreach opt {label maxcomponentlen fstype} { + if {$opts($opt)} { + lappend result -$opt [set $opt] + } + } + if {$opts(serialnum)} { + set low [expr {$serialnum & 0x0000ffff}] + set high [expr {($serialnum >> 16) & 0x0000ffff}] + lappend result -serialnum [format "%.4X-%.4X" $high $low] + } + if {$opts(attr)} { + set attrs [list ] + foreach {sym val} { + case_preserved_names 2 + unicode_on_disk 4 + persistent_acls 8 + file_compression 16 + volume_quotas 32 + supports_sparse_files 64 + supports_reparse_points 128 + supports_remote_storage 256 + volume_is_compressed 0x8000 + supports_object_ids 0x10000 + supports_encryption 0x20000 + named_streams 0x40000 + read_only_volume 0x80000 + sequential_write_once 0x00100000 + supports_transactions 0x00200000 + supports_hard_links 0x00400000 + supports_extended_attributes 0x00800000 + supports_open_by_file_id 0x01000000 + supports_usn_journal 0x02000000 + } { + if {$attr & $val} { + lappend attrs $sym + } + } + lappend result -attr $attrs + } + } + + return $result +} +interp alias {} twapi::get_drive_info {} twapi::get_volume_info + + +# Check if disk has at least n bytes available for the user (NOT total free) +proc twapi::user_drive_space_available {drv space} { + return [expr {$space <= [lindex [get_drive_info $drv -useravail] 1]}] +} + +# Get the drive type +proc twapi::get_drive_type {drive} { + # set type [GetDriveType "[string trimright $drive :/\\]:\\"] + set type [GetDriveType [_drive_rootpath $drive]] + switch -exact -- $type { + 0 { return unknown} + 1 { return invalid} + 2 { return removable} + 3 { return fixed} + 4 { return remote} + 5 { return cdrom} + 6 { return ramdisk} + } +} + +# Get list of drives +proc twapi::find_logical_drives {args} { + array set opts [parseargs args {type.arg}] + + set drives [list ] + foreach drive [_drivemask_to_drivelist [GetLogicalDrives]] { + if {(![info exists opts(type)]) || + [lsearch -exact $opts(type) [get_drive_type $drive]] >= 0} { + lappend drives $drive + } + } + return $drives +} + +twapi::proc* twapi::drive_ready {drive} { + uplevel #0 package require twapi_device +} { + set drive [string trimright $drive "/\\"] + if {[string length $drive] != 2 || [string index $drive 1] ne ":"} { + error "Invalid drive specification" + } + set drive "\\\\.\\$drive" + + # Do our best to avoid the Windows "Drive not ready" dialog + # 1 -> SEM_FAILCRITICALERRORS + if {[min_os_version 6]} { + set old_mode [SetErrorMode 1] + } + trap { + + # We will first try using IOCTL_STORAGE_CHECK_VERIFY2 as that is + # much faster and only needs FILE_READ_ATTRIBUTES access. + set error [catch { + set h [create_file $drive -access file_read_attributes \ + -createdisposition open_existing -share {read write}] + device_ioctl $h 0x2d0800; # IOCTL_STORAGE_CHECK_VERIFY2 + }] + if {[info exists h]} { + close_handle $h + } + if {! $error} { + return 1; # Device is ready + } + + # On error, try the older slower method. Note we now need + # GENERIC_READ access. (NOTE: FILE_READ_DATA will not work with some + # volume types) + unset -nocomplain h + set error [catch { + set h [create_file $drive -access generic_read \ + -createdisposition open_existing -share {read write}] + device_ioctl $h 0x2d4800; # IOCTL_STORAGE_CHECK_VERIFY + }] + if {[info exists h]} { + close_handle $h + } + if {! $error} { + return 1; # Device is ready + } + + # Remote shares sometimes return access denied with the above + # even when actually available. Try with good old file exists + # on root directory + return [file exists "[string range $drive end-1 end]\\"] + } finally { + if {[min_os_version 6]} { + SetErrorMode $old_mode + } + } +} + + +# Set the drive label +proc twapi::set_drive_label {drive label} { + SetVolumeLabel [_drive_rootpath $drive] $label +} + +# Maps a drive letter to the given path +proc twapi::map_drive_local {drive path args} { + array set opts [parseargs args {raw}] + + set drive [string range [_drive_rootpath $drive] 0 1] + DefineDosDevice $opts(raw) $drive [file nativename $path] +} + + +# Unmaps a drive letter +proc twapi::unmap_drive_local {drive args} { + array set opts [parseargs args { + path.arg + raw + } -nulldefault] + + set drive [string range [_drive_rootpath $drive] 0 1] + + set flags $opts(raw) + setbits flags 0x2; # DDD_REMOVE_DEFINITION + if {$opts(path) ne ""} { + setbits flags 0x4; # DDD_EXACT_MATCH_ON_REMOVE + } + DefineDosDevice $flags $drive [file nativename $opts(path)] +} + + +# Callback from C code +proc twapi::_filesystem_monitor_handler {id changes} { + variable _filesystem_monitor_scripts + if {[info exists _filesystem_monitor_scripts($id)]} { + return [uplevel #0 [linsert $_filesystem_monitor_scripts($id) end $id $changes]] + } else { + # Callback queued after close. Ignore + } +} + +# Monitor file changes +proc twapi::begin_filesystem_monitor {path script args} { + variable _filesystem_monitor_scripts + + array set opts [parseargs args { + {subtree.bool 0} + {filename.bool 0 0x1} + {dirname.bool 0 0x2} + {attr.bool 0 0x4} + {size.bool 0 0x8} + {write.bool 0 0x10} + {access.bool 0 0x20} + {create.bool 0 0x40} + {secd.bool 0 0x100} + {pattern.arg ""} + {patterns.arg ""} + } -maxleftover 0] + + if {[string length $opts(pattern)] && + [llength $opts(patterns)]} { + error "Options -pattern and -patterns are mutually exclusive. Note option -pattern is deprecated." + } + + if {[string length $opts(pattern)]} { + # Old style single pattern. Convert to new -patterns + set opts(patterns) [list "+$opts(pattern)"] + } + + # Change to use \ style path separator as that is what the file monitoring functions return + if {[llength $opts(patterns)]} { + foreach pat $opts(patterns) { + # Note / is replaced by \\ within the pattern + # since \ needs to be escaped with another \ within + # string match patterns + lappend pats [string map [list / \\\\] $pat] + } + set opts(patterns) $pats + } + + set flags [expr { $opts(filename) | $opts(dirname) | $opts(attr) | + $opts(size) | $opts(write) | $opts(access) | + $opts(create) | $opts(secd)}] + + if {! $flags} { + # If no options specified, default to all + set flags 0x17f + } + + set id [Twapi_RegisterDirectoryMonitor $path $opts(subtree) $flags $opts(patterns)] + set _filesystem_monitor_scripts($id) $script + return $id +} + +# Stop monitoring of files +proc twapi::cancel_filesystem_monitor {id} { + variable _filesystem_monitor_scripts + if {[info exists _filesystem_monitor_scripts($id)]} { + Twapi_UnregisterDirectoryMonitor $id + unset _filesystem_monitor_scripts($id) + } +} + + +# Get list of volumes +proc twapi::find_volumes {} { + set vols [list ] + set found 1 + # Assumes there has to be at least one volume + lassign [FindFirstVolume] handle vol + while {$found} { + lappend vols $vol + lassign [FindNextVolume $handle] found vol + } + FindVolumeClose $handle + return $vols +} + +# Get list of volume mount points +proc twapi::find_volume_mount_points {vol} { + set mntpts [list ] + set found 1 + trap { + lassign [FindFirstVolumeMountPoint $vol] handle mntpt + } onerror {TWAPI_WIN32 18} { + # ERROR_NO_MORE_FILES + # No volume mount points + return [list ] + } onerror {TWAPI_WIN32 3} { + # Volume does not support them + return [list ] + } + + # At least one volume found + while {$found} { + lappend mntpts $mntpt + lassign [FindNextVolumeMountPoint $handle] found mntpt + } + FindVolumeMountPointClose $handle + return $mntpts +} + +# Set volume mount point +proc twapi::mount_volume {volpt volname} { + # Note we don't use _drive_rootpath for trimming since may not be root path + SetVolumeMountPoint "[string trimright $volpt /\\]\\" "[string trimright $volname /\\]\\" +} + +# Delete volume mount point +proc twapi::unmount_volume {volpt} { + # Note we don't use _drive_rootpath for trimming since may not be root path + DeleteVolumeMountPoint "[string trimright $volpt /\\]\\" +} + +# Get the volume mounted at a volume mount point +proc twapi::get_mounted_volume_name {volpt} { + # Note we don't use _drive_rootpath for trimming since may not be root path + return [GetVolumeNameForVolumeMountPoint "[string trimright $volpt /\\]\\"] +} + +# Get the mount point corresponding to a given path +proc twapi::get_volume_mount_point_for_path {path} { + return [GetVolumePathName [file nativename $path]] +} + + +# Return the times associated with a file +proc twapi::get_file_times {fd args} { + array set opts [parseargs args { + all + mtime + ctime + atime + } -maxleftover 0] + + # Figure out if fd is a file path, Tcl channel or a handle + set close_handle false + if {[file exists $fd]} { + # It's a file name + # 0x02000000 -> FILE_FLAG_BACKUP_SEMANTICS, always required in case + # opening a directory (even if SeBackupPrivilege is not held + set h [create_file $fd -createdisposition open_existing -flags 0x02000000] + set close_handle true + } elseif {[catch {fconfigure $fd}]} { + # Not a Tcl channel, See if handle + if {[pointer? $fd]} { + set h $fd + } else { + error "$fd is not an existing file, handle or Tcl channel." + } + } else { + # Tcl channel + set h [get_tcl_channel_handle $fd read] + } + + set result [list ] + + foreach opt {ctime atime mtime} time [GetFileTime $h] { + if {$opts(all) || $opts($opt)} { + lappend result -$opt $time + } + } + + if {$close_handle} { + CloseHandle $h + } + + return $result +} + + +# Set the times associated with a file +proc twapi::set_file_times {fd args} { + + array set opts [parseargs args { + mtime.arg + ctime.arg + atime.arg + preserveatime + } -maxleftover 0 -nulldefault] + + if {$opts(atime) ne "" && $opts(preserveatime)} { + win32_error 87 "Cannot specify -atime and -preserveatime at the same time." + } + if {$opts(preserveatime)} { + set opts(atime) -1; # Meaning preserve access to original + } + + # Figure out if fd is a file path, Tcl channel or a handle + set close_handle false + if {[file exists $fd]} { + if {$opts(preserveatime)} { + win32_error 87 "Cannot specify -preserveatime unless file is specified as a Tcl channel or a Win32 handle." + } + + # It's a file name + # 0x02000000 -> FILE_FLAG_BACKUP_SEMANTICS, always required in case + # opening a directory (even if SeBackupPrivilege is not held + set h [create_file $fd -access {generic_write} -createdisposition open_existing -flags 0x02000000] + set close_handle true + } elseif {[catch {fconfigure $fd}]} { + # Not a Tcl channel, assume a handle + set h $fd + } else { + # Tcl channel + set h [get_tcl_channel_handle $fd read] + } + + SetFileTime $h $opts(ctime) $opts(atime) $opts(mtime) + + if {$close_handle} { + CloseHandle $h + } + + return +} + +# Convert a device based path to a normalized Win32 path with drive letters +proc twapi::normalize_device_rooted_path {path args} { + # TBD - keep a cache ? + # For example, we need to map \Device\HarddiskVolume1 to C: + # Can only do that by enumerating logical drives + set npath [file nativename $path] + if {![string match -nocase {\\Device\\*} $npath]} { + error "$path is not a valid device based path." + } + array set device_map {} + foreach drive [find_logical_drives] { + set device_path [lindex [lindex [get_volume_info $drive -device] 1] 0] + if {$device_path ne ""} { + set len [string length $device_path] + if {[string equal -nocase -length $len $path $device_path]} { + # Prefix matches, must be terminated by end or path separator + set ch [string index $npath $len] + if {$ch eq "" || $ch eq "\\"} { + set path ${drive}[string range $npath $len end] + if {[llength $args]} { + upvar [lindex $args 0] retvar + set retvar $path + return 1 + } else { + return $path + } + } + } + } + } + + if {[llength $args]} { + return 0 + } else { + error "Could not map device based path '$path'" + } +} + +proc twapi::flush_channel {chan} { + flush $chan + FlushFileBuffers [get_tcl_channel_handle $chan write] +} + +proc twapi::find_file_open {path args} { + variable _find_tokens + variable _find_counter + parseargs args { + {detail.arg basic {basic full}} + } -setvars -maxleftover 0 + + set detail_level [expr {$detail eq "basic" ? 1 : 0}] + if {[min_os_version 6 1]} { + set flags 2; # FIND_FIRST_EX_LARGE_FETCH - Win 7 + } else { + set flags 0 + } + # 0 -> search op. Could be specified as 1 to limit search to + # directories but that is only advisory and does not seem to work + # in many cases. So don't bother making it an option. + lassign [FindFirstFileEx $path $detail_level 0 "" $flags] handle entry + set token ff#[incr _find_counter] + set _find_tokens($token) [list Handle $handle Entry $entry] + return $token +} + +proc twapi::find_file_close {token} { + variable _find_tokens + if {[info exists _find_tokens($token)]} { + FindClose [dict get $_find_tokens($token) Handle] + unset _find_tokens($token) + } + return +} + +proc twapi::decode_file_attributes {attrs} { + return [_make_symbolic_bitmask $attrs { + archive 0x20 + compressed 0x800 + device 0x40 + directory 0x10 + encrypted 0x4000 + hidden 0x2 + integrity_stream 0x8000 + normal 0x80 + not_content_indexed 0x2000 + no_scrub_data 0x20000 + offline 0x1000 + readonly 0x1 + recall_on_data_access 0x400000 + recall_on_open 0x40000 + reparse_point 0x400 + sparse_file 0x200 + system 0x4 + temporary 0x100 + virtual 0x10000 + }] +} + +proc twapi::find_file_next {token varname} { + variable _find_tokens + if {![info exists _find_tokens($token)]} { + return false + } + if {[dict exists $_find_tokens($token) Entry]} { + set entry [dict get $_find_tokens($token) Entry] + dict unset _find_tokens($token) Entry + } else { + set entry [FindNextFile [dict get $_find_tokens($token) Handle]] + } + if {[llength $entry]} { + upvar 1 $varname result + set result [twine {attrs ctime atime mtime size reserve0 reserve1 name altname} $entry] + return true + } else { + return false + } +} + +# Utility functions + +proc twapi::_drive_rootpath {drive} { + if {[_is_unc $drive]} { + # UNC + return "[string trimright $drive ]\\" + } else { + return "[string trimright $drive :/\\]:\\" + } +} + +proc twapi::_is_unc {path} { + return [expr {[string match {\\\\*} $path] || [string match //* $path]}] +} + + diff --git a/src/vendorlib_tcl8/twapi4.7.2/synch.tcl b/src/vendorlib_tcl8/twapi-5.0b1/synch.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/synch.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/synch.tcl index eabf5a71..d535282a 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/synch.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/synch.tcl @@ -1,94 +1,94 @@ -# -# Copyright (c) 2004, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license -# -# TBD - tcl wrappers for semaphores - -namespace eval twapi { -} - -# -# Create and return a handle to a mutex -proc twapi::create_mutex {args} { - array set opts [parseargs args { - name.arg - secd.arg - inherit.bool - lock.bool - } -nulldefault -maxleftover 0] - - if {$opts(name) ne "" && $opts(lock)} { - # TBD - remove this mutex limitation - # This is not a Win32 limitation but ours. Would need to change the C - # implementation and our return format - error "Option -lock must not be specified as true if mutex is named" - } - - return [CreateMutex [_make_secattr $opts(secd) $opts(inherit)] $opts(lock) $opts(name)] -} - -# Get handle to an existing mutex -proc twapi::open_mutex {name args} { - array set opts [parseargs args { - {inherit.bool 0} - {access.arg {mutex_all_access}} - } -maxleftover 0] - - return [OpenMutex [_access_rights_to_mask $opts(access)] $opts(inherit) $name] -} - -# Lock the mutex -proc twapi::lock_mutex {h args} { - array set opts [parseargs args { - {wait.int -1} - }] - - return [wait_on_handle $h -wait $opts(wait)] -} - - -# Unlock the mutex -proc twapi::unlock_mutex {h} { - ReleaseMutex $h -} - -# -# Create and return a handle to a event -proc twapi::create_event {args} { - array set opts [parseargs args { - name.arg - secd.arg - inherit.bool - signalled.bool - manualreset.bool - existvar.arg - } -nulldefault -maxleftover 0] - - if {$opts(name) ne "" && $opts(signalled)} { - # Not clear whether event will be signalled state if it already - # existed but was not signalled - error "Option -signalled must not be specified as true if event is named." - } - - lassign [CreateEvent [_make_secattr $opts(secd) $opts(inherit)] $opts(manualreset) $opts(signalled) $opts(name)] h preexisted - if {$opts(manualreset)} { - # We want to catch attempts to wait on manual reset handles - set h [cast_handle $h HANDLE_MANUALRESETEVENT] - } - if {$opts(existvar) ne ""} { - upvar 1 $opts(existvar) existvar - set existvar $preexisted - } - - return $h -} - -interp alias {} twapi::set_event {} twapi::SetEvent -interp alias {} twapi::reset_event {} twapi::ResetEvent - -# Hack to work with the various build configuration. -if {[info commands ::twapi::get_version] ne ""} { - package provide twapi_synch [::twapi::get_version -patchlevel] -} +# +# Copyright (c) 2004, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license +# +# TBD - tcl wrappers for semaphores + +namespace eval twapi { +} + +# +# Create and return a handle to a mutex +proc twapi::create_mutex {args} { + array set opts [parseargs args { + name.arg + secd.arg + inherit.bool + lock.bool + } -nulldefault -maxleftover 0] + + if {$opts(name) ne "" && $opts(lock)} { + # TBD - remove this mutex limitation + # This is not a Win32 limitation but ours. Would need to change the C + # implementation and our return format + error "Option -lock must not be specified as true if mutex is named" + } + + return [CreateMutex [_make_secattr $opts(secd) $opts(inherit)] $opts(lock) $opts(name)] +} + +# Get handle to an existing mutex +proc twapi::open_mutex {name args} { + array set opts [parseargs args { + {inherit.bool 0} + {access.arg {mutex_all_access}} + } -maxleftover 0] + + return [OpenMutex [_access_rights_to_mask $opts(access)] $opts(inherit) $name] +} + +# Lock the mutex +proc twapi::lock_mutex {h args} { + array set opts [parseargs args { + {wait.int -1} + }] + + return [wait_on_handle $h -wait $opts(wait)] +} + + +# Unlock the mutex +proc twapi::unlock_mutex {h} { + ReleaseMutex $h +} + +# +# Create and return a handle to a event +proc twapi::create_event {args} { + array set opts [parseargs args { + name.arg + secd.arg + inherit.bool + signalled.bool + manualreset.bool + existvar.arg + } -nulldefault -maxleftover 0] + + if {$opts(name) ne "" && $opts(signalled)} { + # Not clear whether event will be signalled state if it already + # existed but was not signalled + error "Option -signalled must not be specified as true if event is named." + } + + lassign [CreateEvent [_make_secattr $opts(secd) $opts(inherit)] $opts(manualreset) $opts(signalled) $opts(name)] h preexisted + if {$opts(manualreset)} { + # We want to catch attempts to wait on manual reset handles + set h [cast_handle $h HANDLE_MANUALRESETEVENT] + } + if {$opts(existvar) ne ""} { + upvar 1 $opts(existvar) existvar + set existvar $preexisted + } + + return $h +} + +interp alias {} twapi::set_event {} twapi::SetEvent +interp alias {} twapi::reset_event {} twapi::ResetEvent + +# Hack to work with the various build configuration. +if {[info commands ::twapi::get_version] ne ""} { + package provide twapi_synch [::twapi::get_version -patchlevel] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/tls.tcl b/src/vendorlib_tcl8/twapi-5.0b1/tls.tcl similarity index 95% rename from src/vendorlib_tcl8/twapi4.7.2/tls.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/tls.tcl index 977ac751..b43bb868 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/tls.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/tls.tcl @@ -1,1296 +1,1313 @@ -# -# Copyright (c) 2012-2020, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license -namespace eval twapi::tls { - # Each element of _channels is dictionary with the following keys - # Socket - the underlying socket. This key will not exist if - # socket has been closed. - # State - SERVERINIT, CLIENTINIT, LISTENERINIT, OPEN, NEGOTIATING, CLOSED - # Type - SERVER, CLIENT, LISTENER - # Blocking - 0/1 indicating whether blocking or non-blocking channel - # WatchMask - list of {read write} indicating what events to post - # Target - Name for server cert - # Credentials - credentials handle to use for local end of connection - # FreeCredentials - if credentials should be freed on connection cleanup - # AcceptCallback - application callback on a listener and server socket. - # On listener, it is the accept command prefix. On a server - # (accepted socket) it is the prefix plus arguments passed to - # accept callback. On client and on servers sockets initialized - # with starttls, this key must NOT be present - # SspiContext - SSPI context for the connection - # Input - plaintext data to pass to app - # Output - plaintext data to encrypt and output - # ReadEventPosted - if this key exists, a chan postevent for read - # is already in progress and a second one should not be posted - # WriteEventPosted - if this key exists, a chan postevent for write - # is already in progress and a second one should not be posted - # WriteDisabled - 0 normally. Set to 1 on a half-close - - variable _channels - array set _channels {} - - # Socket command - Tcl socket by default. - variable _socket_cmd ::socket - - namespace path [linsert [namespace path] 0 [namespace parent]] - -} - -proc twapi::tls_socket_command {args} { - set orig_command $tls::_socket_cmd - if {[llength $args] == 1} { - set tls::_socket_cmd [lindex $args 0] - } elseif {[llength $args] != 0} { - error "wrong # args: should be \"tls_socket_command ?cmd?\"" - } - return $orig_command -} - -interp alias {} twapi::tls_socket {} twapi::tls::_socket -proc twapi::tls::_socket {args} { - variable _channels - variable _socket_cmd - - debuglog [info level 0] - - parseargs args { - myaddr.arg - myport.int - async - socketcmd.arg - server.arg - peersubject.arg - requestclientcert - {credentials.arg {}} - {verifier.arg {}} - } -setvars - - set chan [chan create {read write} [list [namespace current]]] - # NOTE: We were originally using badargs! instead of error to raise - # exceptions. However that lands up bypassing the trap because of - # the way badargs! is implemented. So stick to error. - trap { - set socket_args {} - foreach opt {myaddr myport} { - if {[info exists $opt]} { - lappend socket_args -$opt [set $opt] - } - } - if {$async} { - lappend socket_args -async - } - - if {[info exists server]} { - if {$server eq ""} { - error "Cannot specify an empty value for -server." - } - - if {[info exists peersubject]} { - error "Option -peersubject cannot be specified for with -server" - } - set peersubject "" - set type LISTENER - lappend socket_args -server [list [namespace current]::_accept $chan] - if {[llength $credentials] == 0} { - error "Option -credentials must be specified for server sockets" - } - } else { - if {![info exists peersubject]} { - set peersubject [lindex $args 0] - } - set requestclientcert 0; # Not valid for client side - set server "" - set type CLIENT - } - - if {[info exists socketcmd]} { - if {$socketcmd eq ""} { - set socketcmd ::socket - } - } else { - set socketcmd $_socket_cmd - } - } onerror {} { - catch {chan close $chan} - rethrow - } - trap { - set so [$socketcmd {*}$socket_args {*}$args] - _init $chan $type $so $credentials $peersubject $requestclientcert [lrange $verifier 0 end] $server - - if {$type eq "CLIENT"} { - if {$async} { - chan event $so writable [list [namespace current]::_so_write_handler $chan] - } else { - _client_blocking_negotiate $chan - if {(![info exists _channels($chan)]) || - [dict get $_channels($chan) State] ne "OPEN"} { - if {[info exists _channels($chan)] && - [dict exists $_channels($chan) ErrorResult]} { - error [dict get $_channels($chan) ErrorResult] - } else { - error "TLS negotiation aborted" - } - } - } - } - } onerror {} { - # If _init did not even go as far initializing _channels($chan), - # close socket ourselves. If it was initialized, the socket - # would have been closed even on error - if {![info exists _channels($chan)]} { - catch {chan close $so} - } - catch {chan close $chan} - # DON'T ACCESS _channels HERE ON - if {[string match "wrong # args*" [trapresult]]} { - badargs! "wrong # args: should be \"tls_socket ?-credentials creds? ?-verifier command? ?-peersubject peer? ?-myaddr addr? ?-myport myport? ?-async? host port\" or \"tls_socket ?-credentials creds? ?-verifier command? -server command ?-myaddr addr? port\"" - } else { - rethrow - } - } - - return $chan -} - -interp alias {} twapi::starttls {} twapi::tls::_starttls -proc twapi::tls::_starttls {so args} { - variable _channels - - debuglog [info level 0] - - trap { - parseargs args { - server - requestclientcert - peersubject.arg - {credentials.arg {}} - {verifier.arg {}} - } -setvars -maxleftover 0 - - if {$server} { - if {[info exists peersubject]} { - badargs! "Option -peersubject cannot be specified with -server." - } - if {[llength $credentials] == 0} { - error "Option -credentials must be specified for server sockets." - } - set peersubject "" - set type SERVER - } else { - set requestclientcert 0; # Ignored for client side - if {![info exists peersubject]} { - # TBD - even if verifier is specified ? - badargs! "Option -peersubject must be specified for client connections." - } - set type CLIENT - } - set chan [chan create {read write} [list [namespace current]]] - } onerror {} { - chan close $so - rethrow - } - trap { - # Get config from the wrapped socket and reset its handlers - # Do not get all options because that results in reverse name - # lookups for -peername and -sockname causing a stall. - foreach opt { - -blocking -buffering -buffersize -encoding -eofchar -translation - } { - lappend so_opts $opt [chan configure $so $opt] - } - - # NOTE: we do NOT save read and write handlers and attach - # them to the new channel because the channel name is different. - # Thus in most cases the callbacks, which often are passed the - # channel name as an arg, would not be valid. It is up - # to the caller to reestablish handlers - # TBD - maybe keep handlers but replace $so with $chan in them ? - chan event $so readable {} - chan event $so writable {} - _init $chan $type $so $credentials $peersubject $requestclientcert [lrange $verifier 0 end] "" - # Copy saved config to wrapper channel - chan configure $chan {*}$so_opts - if {$type eq "CLIENT"} { - if {[dict get $_channels($chan) Blocking]} { - _client_blocking_negotiate $chan - if {(![info exists _channels($chan)]) || - [dict get $_channels($chan) State] ne "OPEN"} { - if {[info exists _channels($chan)] && - [dict exists $_channels($chan) ErrorResult]} { - error [dict get $_channels($chan) ErrorResult] - } else { - error "TLS negotiation aborted" - } - } - } else { - _negotiate $chan - } - } else { - # Note: unlike the tls_socket server case, here we - # do not need to switch a blocking socket to non-blocking - # and then switch back, primarily because the socket - # is already open and there is no need for a callback - # when connection opens. - if {! [dict get $_channels($chan) Blocking]} { - chan configure $so -blocking 0 - chan event $so readable [list [namespace current]::_so_read_handler $chan] - } - _negotiate $chan - } - } onerror {} { - # If _init did not even go as far initializing _channels($chan), - # close socket ourselves. If it was initialized, the socket - # would have been closed even on error - if {![info exists _channels($chan)]} { - catch {chan close $so} - } - catch {chan close $chan} - # DON'T ACCESS _channels HERE ON - if {[string match "wrong # args*" [trapresult]]} { - badargs! "wrong # args: should be \"tls_socket ?-credentials creds? ?-verifier command? ?-peersubject peer? ?-myaddr addr? ?-myport myport? ?-async? host port\" or \"tls_socket ?-credentials creds? ?-verifier command? -server command ?-myaddr addr? port\"" - } else { - rethrow - } - } - - return $chan -} - -interp alias {} twapi::tls_state {} twapi::tls::_state -proc twapi::tls::_state {chan} { - variable _channels - if {![info exists _channels($chan)]} { - twapi::badargs! "Not a valid TLS channel." - } - return [dict get $_channels($chan) State] -} - -interp alias {} twapi::tls_handshake {} twapi::tls::_handshake -proc twapi::tls::_handshake {chan} { - variable _channels - if {![info exists _channels($chan)]} { - twapi::badargs "Not a valid TLS channel." - } - - dict with _channels($chan) {} - - # For a blocking channel, complete the handshake before returning - if {$Blocking} { - switch -exact $State { - NEGOTIATING - CLIENTINIT - SERVERINIT { - _negotiate2 $chan - } - OPEN {} - LISTERNERINIT { - error "Cannot do a TLS handshake on a listening socket." - } - CLOSED - - default { - error "Channel has been closed or in error state." - } - } - } else { - # For non-blocking channels, simply return the state - switch -exact -- $State { - OPEN {} - CLIENTINIT - SERVERINIT - LISTENERINIT - NEGOTIATING { - return 0 - } - CLOSED - default { - error "Channel has been closed or in error state." - } - } - } - return 1 -} - -proc twapi::tls::_accept {listener so raddr raport} { - variable _channels - - debuglog [info level 0] - - trap { - set chan [chan create {read write} [list [namespace current]]] - _init $chan SERVER $so [dict get $_channels($listener) Credentials] "" [dict get $_channels($listener) RequestClientCert] [dict get $_channels($listener) Verifier] [linsert [dict get $_channels($listener) AcceptCallback] end $chan $raddr $raport] - # If we negotiate the connection, the socket is blocking so - # will hang the whole operation. Instead we mark it non-blocking - # and the switch back to blocking when the connection gets opened. - # For accepts to work, the event loop has to be running anyways. - chan configure $so -blocking 0 - chan event $so readable [list [namespace current]::_so_read_handler $chan] - _negotiate $chan - } onerror {} { - catch {_cleanup $chan} - rethrow - } - return -} - -proc twapi::tls::initialize {chan mode} { - debuglog [info level 0] - - # All init is done in chan creation routine after base socket is created - return {initialize finalize watch blocking read write configure cget cgetall} -} - -proc twapi::tls::finalize {chan} { - debuglog [info level 0] - _cleanup $chan - return -} - -proc twapi::tls::blocking {chan mode} { - debuglog [info level 0] - - variable _channels - - dict set _channels($chan) Blocking $mode - - if {![dict exists $_channels($chan) Socket]} { - # We do not currently generate an error because the Tcl socket - # command does not either on a fconfigure when remote has - # closed connection - return - } - set so [dict get $_channels($chan) Socket] - fconfigure $so -blocking $mode - - # There is an issue with Tcl sockets created with -async switching - # from blocking->non-blocking->blocking and writing to the socket - # before connection is fully open. The internal buffers containing - # data that was written before the connection was open do not get - # flushed even if there was an explicit flush call by the application. - # Doing a flush after changing blocking mode seems to fix this - # problem. TBD - check if still the case - flush $so - - # TBD - Should we change handlers BEFORE flushing? - - # The flush may recursively call event handler (possibly) which - # may change state so have to retrieve values from _channels again. - if {![dict exists $_channels($chan) Socket]} { - return - } - set so [dict get $_channels($chan) Socket] - - if {[dict get $_channels($chan) Blocking] == 0} { - # Non-blocking - # Since we need to negotiate TLS we always have socket event - # handlers irrespective of the state of the watch mask - chan event $so readable [list [namespace current]::_so_read_handler $chan] - chan event $so writable [list [namespace current]::_so_write_handler $chan] - } else { - # TBD - is this right? Application may have file event handlers even - # on blocking sockets - chan event $so readable {} - chan event $so writable {} - } - return -} - -proc twapi::tls::watch {chan watchmask} { - debuglog [info level 0] - variable _channels - - dict set _channels($chan) WatchMask $watchmask - - if {"read" in $watchmask} { - # Post a read even if we already have input or if the - # underlying socket has gone away. - # TBD - do we have a mechanism for continuously posting - # events when socket has gone away ? Do we even post once - # when socket is closed (on error for example) - if {[string length [dict get $_channels($chan) Input]] || ![dict exists $_channels($chan) Socket]} { - _post_read_event $chan - } - # Turn read handler back on in case it had been turned off. - chan event [dict get $_channels($chan) Socket] readable [list [namespace current]::_so_read_handler $chan] - } - - if {"write" in [dict get $_channels($chan) WatchMask]} { - if {[dict get $_channels($chan) State] in {OPEN NEGOTIATING CLOSED} } { - _post_write_event $chan - } - # TBD - do we need to turn write handler back on? - } - - return -} - -proc twapi::tls::read {chan nbytes} { - variable _channels - - debuglog [info level 0] - - if {$nbytes == 0} { - return {} - } - - # This is not inside the dict with because _negotiate will update the dict - if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { - _negotiate $chan - if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { - # If a blocking channel, should have come back with negotiation - # complete. If non-blocking, return EAGAIN to indicate no - # data yet - if {[dict get $_channels($chan) Blocking]} { - error "TLS negotiation failed on blocking channel" - } else { - return -code error EAGAIN - } - } - } - - dict with _channels($chan) { - # Either in OPEN or CLOSED state. For the latter, if an error is - # present, immediately raise it else go on to return any pending data. - if {$State eq "CLOSED" && [info exists ErrorResult]} { - error $ErrorResult - } - # Try to read more bytes if don't have enough AND conn is open - set status ok - if {[string length $Input] < $nbytes && $State eq "OPEN"} { - if {$Blocking} { - # For blocking channels, we do not want to block if some - # bytes are already available. The refchan will call us - # with number of bytes corresponding to its buffer size, - # not what app's read call has asked. It expects us - # to return whatever we have (but at least one byte) - # and block only if nothing is available - while {[string length $Input] == 0 && $status eq "ok"} { - # The channel does not compress so we need to read in - # at least $needed bytes. Because of TLS overhead, we may - # actually need even more - set status ok - set data [_blocking_read $Socket] - if {[string length $data]} { - lassign [sspi_decrypt_stream $SspiContext $data] status plaintext - # Note plaintext might be "" if complete cipher block - # was not received - append Input $plaintext - } else { - set status eof - } - } - } else { - # Non-blocking - read all that we can - set status ok - set data [chan read $Socket] - if {[string length $data]} { - lassign [sspi_decrypt_stream $SspiContext $data] status plaintext - append Input $plaintext - } else { - if {[chan eof $Socket]} { - set status eof - } - } - if {[string length $Input] == 0} { - # Do not have enough data. See if connection closed - # TBD - also handle status == renegotiate - if {$status eq "ok"} { - # Not closed, just waiting for data - return -code error EAGAIN - } - } - } - } - - # TBD - use inline K operator to make this faster? Probably no use - # since Input is also referred to from _channels($chan) - set ret [string range $Input 0 $nbytes-1] - set Input [string range $Input $nbytes end] - if {"read" in [dict get $_channels($chan) WatchMask] && [string length $Input]} { - _post_read_event $chan - } - if {$status ne "ok"} { - # TBD - handle renegotiate - debuglog "read: setting State CLOSED" - - # Need a EOF event even if read event posted. See Bug #203 - _post_eof_event $chan - set State CLOSED - lassign [sspi_shutdown_context $SspiContext] _ outdata - if {[info exists Socket]} { - if {[string length $outdata] && $status ne "eof"} { - puts -nonewline $Socket $outdata - } - catch {close $Socket} - unset Socket - } - } - return $ret; # Note ret may be "" - } -} - -proc twapi::tls::write {chan data} { - variable _channels - - set datalen [string length $data] - debuglog "twapi::tls::write: $chan, $datalen bytes" - - # This is not inside the dict with below because _negotiate will update the dict - if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { - _negotiate $chan - if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { - if {[dict get $_channels($chan) Blocking]} { - # If a blocking channel, negotiation should have completed - error "TLS negotiation failed on blocking channel" - } else { - # TBD - which of the following alternatives to use? - if {1} { - # Store for later output once connection is open - debuglog "twapi::tls::write conn not open, appending $datalen bytes to pending output" - dict append _channels($chan) Output $data - return $datalen - } else { - # If non-blocking, return EAGAIN to indicate channel - # not open yet. - debuglog "twapi::tls::write returning EAGAIN" - return -code error EAGAIN - } - } - } - } - - dict with _channels($chan) { - debuglog "twapi::tls::write state $State" - switch $State { - CLOSED { - # Just like a Tcl socket, we do not raise an error on a - # write to a closed socket. Simply throw away the data/ - # However, if an error already exists (negotiation fail) - # raise it. - if {[info exists ErrorResult]} { - error $ErrorResult - } - } - OPEN { - if {$WriteDisabled} { - error "Channel closed for output." - } - # There might be pending output if channel has just - # transitioned to OPEN state - _flush_pending_output $chan - # TBD - use sspi_encrypt_and_write instead - chan puts -nonewline $Socket [sspi_encrypt_stream $SspiContext $data] - flush $Socket - } - default { - append Output $data - } - } - } - debuglog "twapi::tls::write returning $datalen" - return $datalen -} - -proc twapi::tls::configure {chan opt val} { - debuglog [info level 0] - # Does not make sense to change creds and verifier after creation - switch $opt { - -context - - -verifier - - -credentials { - error "$opt is a read-only option." - } - default { - chan configure [_chansocket $chan] $opt $val - } - } - - return -} - -proc twapi::tls::cget {chan opt} { - debuglog [info level 0] - variable _channels - - switch $opt { - -credentials { - return [dict get $_channels($chan) Credentials] - } - -verifier { - return [dict get $_channels($chan) Verifier] - } - -context { - return [dict get $_channels($chan) SspiContext] - } - -error { - if {[dict exists $_channels($chan) ErrorResult]} { - set result "[dict get $_channels($chan) ErrorResult]" - if {$result ne ""} { - return $result - } - } - # Get -error from underlying socket - # -error should not raise an error but return the error as result - catch {chan configure [_chansocket $chan] -error} result - return $result - } - default { - return [chan configure [_chansocket $chan] $opt] - } - } -} - -proc twapi::tls::cgetall {chan} { - debuglog [info level 0] - variable _channels - dict with _channels($chan) { - if {[info exists Socket]} { - # First get all options underlying socket supports. Note this may - # or may not a Tcl native socket. - array set so_config [chan configure $Socket] - # Only return options that are not owned by the core channel code - # and apply to the $chan itself. - foreach {opt val} [chan configure $Socket] { - if {$opt ni {-blocking -buffering -buffersize -encoding -eofchar -translation}} { - lappend config $opt $val - } - } - } - lappend config -credentials $Credentials \ - -verifier $Verifier \ - -context $SspiContext - } - return $config -} - -# Implement a half-close command since Tcl does not support it for -# reflected channels. -interp alias {} twapi::tls_close {} twapi::tls::_close -proc twapi::tls::_close {chan {direction ""}} { - - if {$direction in {read r re rea}} { - error "Half close of input side not currently supported for TLS sockets." - } - - # We handle write-side half-closes. Let Tcl close handle everything else. - if {$direction ni {write w wr wri writ}} { - return [close $chan] - } - - # Closing the write side of the channel - - variable _channels - - dict with _channels($chan) {} - if {$State eq "CLOSED"} return - if {$State ne "OPEN"} { - error "Connection not in OPEN state." - } - flush $chan - # Note state may have changed - if {[dict get $_channels($chan) State] ne "OPEN"} { - return - } - # Flush internally buffered, if any. Can happen if we buffered - # data before TLS negotiation was complete. - _flush_pending_output $chan - close $Socket write - dict set _channels($chan) WriteDisabled 1 - return -} - -proc twapi::tls::_chansocket {chan} { - debuglog [info level 0] - variable _channels - if {![info exists _channels($chan)]} { - error "Channel $chan not found." - } - if {![dict exists $_channels($chan) Socket]} { - set error "Socket not connected." - if {[dict exists $_channels($chan) ErrorResult]} { - append error " [dict get $_channels($chan) ErrorResult]" - } - error $error - } - return [dict get $_channels($chan) Socket] -} - -proc twapi::tls::_init {chan type so creds peersubject requestclientcert verifier {accept_callback {}}} { - debuglog [info level 0] - variable _channels - - # TBD - verify that -buffering none is the right thing to do - # as the scripted channel interface takes care of this itself - chan configure $so -translation binary -buffering none - set _channels($chan) [list Socket $so \ - State ${type}INIT \ - Type $type \ - Blocking [chan configure $so -blocking] \ - WatchMask {} \ - WriteDisabled 0 \ - RequestClientCert $requestclientcert \ - Verifier $verifier \ - SspiContext {} \ - PeerSubject $peersubject \ - Input {} Output {}] - - if {[llength $creds]} { - set free_creds 0 - } else { - set creds [sspi_acquire_credentials -package tls -role client -credentials [sspi_schannel_credentials]] - set free_creds 1 - } - dict set _channels($chan) Credentials $creds - dict set _channels($chan) FreeCredentials $free_creds - - # See SF issue #178. Need to supply -usesuppliedcreds to sspi_client_context - # else servers that request (even optionally) client certs might fail since - # we do not currently implement incomplete credentials handling. This - # option will prevent schannel from trying to automatically look up client - # certificates. - dict set _channels($chan) UseSuppliedCreds 0; # TBD - make this use settable option - - if {[string length $accept_callback] && - ($type eq "LISTENER" || $type eq "SERVER")} { - dict set _channels($chan) AcceptCallback $accept_callback - } -} - -proc twapi::tls::_cleanup {chan} { - debuglog [info level 0] - variable _channels - if {[info exists _channels($chan)]} { - # Note _cleanup can be called in inconsistent state so not all - # keys may be set up - dict with _channels($chan) { - if {[info exists SspiContext]} { - if {$State eq "OPEN"} { - lassign [sspi_shutdown_context $SspiContext] _ outdata - if {[string length $outdata] && [info exists Socket]} { - if {[catch {puts -nonewline $Socket $outdata} msg]} { - # TBD - debug log - } - } - } - if {[catch {sspi_delete_context $SspiContext} msg]} { - # TBD - debug log - } - } - if {[info exists Socket]} { - if {[catch {chan close $Socket} msg]} { - # TBD - debug log socket close error - } - } - if {[info exists Credentials] && $FreeCredentials} { - if {[catch {sspi_free_credentials $Credentials} msg]} { - # TBD - debug log - } - } - } - unset _channels($chan) - } -} - -proc twapi::tls::_cleanup_failed_accept {chan} { - debuglog [info level 0] - variable _channels - # This proc is called from the event loop when negotiation fails - # on a server TLS channel that is not yet open (and hence not - # known to the application). For some protection against - # channel name re-use (which does not happen as of 8.6) - # check the state before cleaning up. - if {[info exists _channels($chan)] && - [dict get $_channels($chan) Type] eq "SERVER" && - [dict get $_channels($chan) State] eq "CLOSED"} { - close $chan; # Really close - } -} - -if {[llength [info commands ::twapi::tls_background_error]] == 0} { - proc twapi::tls_background_error {result ropts} { - return -options $ropts $result - } -} - -proc twapi::tls::_negotiate_from_handler {chan} { - # Called from socket read / write handlers if - # negotiation is still in progress. - # Returns the error code from next step of - # negotiation. - # 1 -> ok, - # 0 -> some error occured, most likely negotiation failure - variable _channels - if {[catch {_negotiate $chan} result ropts]} { - if {![dict exists $_channels($chan) ErrorResult]} { - dict set _channels($chan) ErrorResult $result - } - if {"read" in [dict get $_channels($chan) WatchMask]} { - _post_read_event $chan - } - if {"write" in [dict get $_channels($chan) WatchMask]} { - _post_write_event $chan - } - # For SERVER sockets, force error because no other way - # to record some error happened. - if {[dict get $_channels($chan) Type] eq "SERVER"} { - ::twapi::tls_background_error $result $ropts - # Above should raise an error, else do it ourselves - # since stack needs to be rewound - return -options $ropts $result - } - return 0 - } - return 1 -} - -proc twapi::tls::_so_read_handler {chan} { - debuglog [info level 0] - variable _channels - - if {[info exists _channels($chan)]} { - if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { - if {![_negotiate_from_handler $chan]} { - return - } - } - - if {"read" in [dict get $_channels($chan) WatchMask]} { - _post_read_event $chan - } else { - # We are not asked to generate read events, turn off the read - # event handler unless we are negotiating - if {[dict get $_channels($chan) State] ni {SERVERINIT CLIENTINIT NEGOTIATING}} { - if {[dict exists $_channels($chan) Socket]} { - chan event [dict get $_channels($chan) Socket] readable {} - } - } - } - } - return -} - -proc twapi::tls::_so_write_handler {chan} { - debuglog [info level 0] - variable _channels - - if {[info exists _channels($chan)]} { - dict with _channels($chan) {} - - # If we are not actually asked to generate write events, - # the only time we want a write handler is on a client -async - # Once it runs, we never want it again else it will keep triggering - # as sockets are always writable - if {"write" ni $WatchMask} { - if {[info exists Socket]} { - chan event $Socket writable {} - } - } - - if {$State in {SERVERINIT CLIENTINIT NEGOTIATING}} { - if {![_negotiate_from_handler $chan]} { - # TBD - should we throw so bgerror gets run? - return - } - } - - # Do not use local var $State because _negotiate might have updated it - if {"write" in $WatchMask && [dict get $_channels($chan) State] eq "OPEN"} { - _post_write_event $chan - } - } - return -} - -proc twapi::tls::_negotiate chan { - debuglog [info level 0] - trap { - _negotiate2 $chan - } onerror {} { - variable _channels - if {[info exists _channels($chan)]} { - if {[dict get $_channels($chan) Type] eq "SERVER" && - [dict get $_channels($chan) State] in {SERVERINIT NEGOTIATING}} { - # There is no one to clean up accepted sockets (server) that - # fail verification (or error out) since application does - # not know about them. So queue some garbage - # cleaning. - after 0 [namespace current]::_cleanup_failed_accept $chan - } - dict set _channels($chan) State CLOSED - dict set _channels($chan) ErrorOptions [trapoptions] - dict set _channels($chan) ErrorResult [trapresult] - if {[dict exists $_channels($chan) Socket]} { - catch {close [dict get $_channels($chan) Socket]} - dict unset _channels($chan) Socket - } - } - rethrow - } -} - -proc twapi::tls::_negotiate2 {chan} { - variable _channels - - dict with _channels($chan) {}; # dict -> local vars - - debuglog [info level 0] - switch $State { - NEGOTIATING { - if {$Blocking && ![info exists AcceptCallback]} { - return [_blocking_negotiate_loop $chan] - } - - set data [chan read $Socket] - if {[string length $data] == 0} { - if {[chan eof $Socket]} { - throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation (NEGOTIATING)" - } else { - # No data yet, just keep waiting - debuglog "Waiting (chan $chan) for more data on Socket $Socket" - return - } - } else { - lassign [sspi_step $SspiContext $data] status outdata leftover - debuglog "sspi_step returned status $status with [string length $outdata] bytes" - if {[string length $outdata]} { - chan puts -nonewline $Socket $outdata - chan flush $Socket - } - switch $status { - done { - if {[string length $leftover]} { - lassign [sspi_decrypt_stream $SspiContext $leftover] status plaintext - dict append _channels($chan) Input $plaintext - if {$status ne "ok"} { - # TBD - shutdown channel or let _cleanup do it? - } - } - _open $chan - } - continue { - # Keep waiting for next input - } - default { - debuglog "sspi_step returned $status" - error "Unexpected status $status from sspi_step" - } - } - } - } - - CLIENTINIT { - if {$Blocking} { - _client_blocking_negotiate $chan - } else { - dict set _channels($chan) State NEGOTIATING - set SspiContext [sspi_client_context $Credentials -usesuppliedcreds $UseSuppliedCreds -stream 1 -target $PeerSubject -manualvalidation [expr {[llength $Verifier] > 0}]] - dict set _channels($chan) SspiContext $SspiContext - lassign [sspi_step $SspiContext] status outdata - if {[string length $outdata]} { - chan puts -nonewline $Socket $outdata - chan flush $Socket - } - if {$status ne "continue"} { - error "Unexpected status $status from sspi_step" - } - } - } - - SERVERINIT { - # For server sockets created from tls_socket, we - # always take the non-blocking path as we set the socket - # to be non-blocking so as to not hold up the whole app - # For server sockets created with starttls - # (AcceptCallback will not exist), we can do a blocking - # negotiate. - if {$Blocking && ![info exists AcceptCallback]} { - _server_blocking_negotiate $chan - } else { - set data [chan read $Socket] - if {[string length $data] == 0} { - if {[chan eof $Socket]} { - throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation (SERVERINIT)" - } else { - # No data yet, just keep waiting - debuglog "$chan: no data from socket $Socket. Waiting..." - return - } - } else { - debuglog "Setting $chan State=NEGOTIATING" - - dict set _channels($chan) State NEGOTIATING - set SspiContext [sspi_server_context $Credentials $data -stream 1 -mutualauth $RequestClientCert] - dict set _channels($chan) SspiContext $SspiContext - lassign [sspi_step $SspiContext] status outdata leftover - debuglog "sspi_step returned status $status with [string length $outdata] bytes" - if {[string length $outdata]} { - debuglog "Writing [string length $outdata] bytes to socket $Socket" - chan puts -nonewline $Socket $outdata - chan flush $Socket - } - switch $status { - done { - if {[string length $leftover]} { - lassign [sspi_decrypt_stream $SspiContext $leftover] status plaintext - dict append _channels($chan) Input $plaintext - if {$status ne "ok"} { - # TBD - shut down channel - } - } - debuglog "Marking channel $chan open" - _open $chan - } - continue { - # Keep waiting for next input - } - default { - error "Unexpected status $status from sspi_step" - } - } - } - } - } - - default { - error "Internal error: _negotiate called in state [dict get $_channels($chan) State]" - } - } - - return -} - -proc twapi::tls::_client_blocking_negotiate {chan} { - debuglog [info level 0] - variable _channels - dict with _channels($chan) { - set State NEGOTIATING - set SspiContext [sspi_client_context $Credentials -usesuppliedcreds $UseSuppliedCreds -stream 1 -target $PeerSubject -manualvalidation [expr {[llength $Verifier] > 0}]] - } - return [_blocking_negotiate_loop $chan] -} - -proc twapi::tls::_server_blocking_negotiate {chan} { - debuglog [info level 0] - variable _channels - dict set _channels($chan) State NEGOTIATING - set so [dict get $_channels($chan) Socket] - set indata [_blocking_read $so] - if {[chan eof $so]} { - throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation (server)." - } - dict set _channels($chan) SspiContext [sspi_server_context [dict get $_channels($chan) Credentials] $indata -stream 1 -mutualauth [dict get $_channels($chan) RequestClientCert]] - return [_blocking_negotiate_loop $chan] -} - -proc twapi::tls::_blocking_negotiate_loop {chan} { - debuglog [info level 0] - variable _channels - - dict with _channels($chan) {}; # dict -> local vars - - lassign [sspi_step $SspiContext] status outdata - debuglog "sspi_step status $status" - # Keep looping as long as the SSPI state machine tells us to - while {$status eq "continue"} { - # If the previous step had any output, send it out - if {[string length $outdata]} { - debuglog "Writing [string length $outdata] to socket $Socket" - chan puts -nonewline $Socket $outdata - chan flush $Socket - } - - set indata [_blocking_read $Socket] - debuglog "Read [string length $indata] from socket $Socket" - if {[chan eof $Socket]} { - throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation." - } - trap { - lassign [sspi_step $SspiContext $indata] status outdata leftover - } onerror {} { - debuglog "sspi_step returned error: [trapresult]" - close $Socket - unset Socket - rethrow - } - debuglog "sspi_step status $status" - } - - # Send output irrespective of status - if {[string length $outdata]} { - chan puts -nonewline $Socket $outdata - chan flush $Socket - } - - if {$status eq "done"} { - if {[string length $leftover]} { - lassign [sspi_decrypt_stream $SspiContext $leftover] status plaintext - dict append _channels($chan) Input $plaintext - if {$status ne "ok"} { - error "Error status $status decrypting data" - } - } - _open $chan - } else { - # Should not happen. Negotiation failures will raise an error, - # not return a value - error "TLS negotiation failed: status $status." - } - - return -} - -proc twapi::tls::_blocking_read {so} { - debuglog [info level 0] - # Read from a blocking socket. We do not know how much data is needed - # so read a single byte and then read any pending - set input [chan read $so 1] - if {[string length $input]} { - set more [chan pending input $so] - if {$more > 0} { - append input [chan read $so $more] - } - } - return $input -} - -proc twapi::tls::_flush_pending_output {chan} { - variable _channels - - dict with _channels($chan) { - if {[string length $Output]} { - debuglog "_flush_pending_output: flushing output" - puts -nonewline $Socket [sspi_encrypt_stream $SspiContext $Output] - set Output "" - } - } - return -} - -# Transitions connection to OPEN or throws error if verifier returns false -# or fails -proc twapi::tls::_open {chan} { - debuglog [info level 0] - variable _channels - - dict with _channels($chan) {}; # dict -> local vars - - if {[llength $Verifier] == 0} { - # No verifier specified. In this case, we would not have specified - # -manualvalidation in creating the context and the system would - # have done the verification already for client. For servers, - # there is no verification of clients to be done by default - - # For compatibility with TLS we call accept callbacks AFTER verification - dict set _channels($chan) State OPEN - if {[info exists AcceptCallback]} { - # Server sockets are set up to be non-blocking during negotiation - # Change them back to original state before notifying app - chan configure $Socket -blocking [dict get $_channels($chan) Blocking] - chan event $Socket readable {} - after 0 $AcceptCallback - } - # If there is any pending output waiting for the connection to - # open, write it out - _flush_pending_output $chan - - return - } - - # TBD - what if verifier closes the channel - if {[{*}$Verifier $chan $SspiContext]} { - dict set _channels($chan) State OPEN - # For compatibility with TLS we call accept callbacks AFTER verification - if {[info exists AcceptCallback]} { - # Server sockets are set up to be non-blocking during - # negotiation. Change them back to original state - # before notifying app - chan configure $Socket -blocking [dict get $_channels($chan) Blocking] - chan event $Socket readable {} - after 0 $AcceptCallback - } - _flush_pending_output $chan - return - } else { - error "SSL/TLS negotiation failed. Verifier callback returned false." "" [list TWAPI TLS VERIFYFAIL] - } -} - -# Calling [chan postevent] results in filevent handlers being called right -# away which can recursively call back into channel code making things -# more than a bit messy. So we always schedule them through the event loop -proc twapi::tls::_post_read_event_callback {chan} { - debuglog [info level 0] - variable _channels - if {[info exists _channels($chan)]} { - dict unset _channels($chan) ReadEventPosted - if {"read" in [dict get $_channels($chan) WatchMask]} { - chan postevent $chan read - } - } -} -proc twapi::tls::_post_read_event {chan} { - debuglog [info level 0] - variable _channels - if {![dict exists $_channels($chan) ReadEventPosted]} { - # Note after 0 after idle does not work - (never get called) - # not sure why so just do after 0 - dict set _channels($chan) ReadEventPosted \ - [after 0 [namespace current]::_post_read_event_callback $chan] - } -} -proc twapi::tls::_post_eof_event_callback {chan} { - debuglog [info level 0] - variable _channels - if {[info exists _channels($chan)]} { - if {"read" in [dict get $_channels($chan) WatchMask]} { - chan postevent $chan read - } - } -} -proc twapi::tls::_post_eof_event {chan} { - # EOF events are always generated event if a read event is already posted. - # See Bug #203 - debuglog [info level 0] - after 0 [namespace current]::_post_eof_event_callback $chan -} - - -proc twapi::tls::_post_write_event_callback {chan} { - debuglog [info level 0] - variable _channels - if {[info exists _channels($chan)]} { - dict unset _channels($chan) WriteEventPosted - if {"write" in [dict get $_channels($chan) WatchMask]} { - # NOTE: we do not check state here as we should generate an event - # even in the CLOSED state - see Bug #206 - chan postevent $chan write - } - } -} -proc twapi::tls::_post_write_event {chan} { - debuglog [info level 0] - variable _channels - if {![dict exists $_channels($chan) WriteEventPosted]} { - # Note after 0 after idle does not work - (never get called) - # not sure why so just do after 0 - dict set _channels($chan) WriteEventPosted \ - [after 0 [namespace current]::_post_write_event_callback $chan] - } -} - -namespace eval twapi::tls { - namespace ensemble create -subcommands { - initialize finalize blocking watch read write configure cget cgetall - } -} - -proc twapi::tls::sample_server_creds pfxFile { - set fd [open $pfxFile rb] - set pfx [read $fd] - close $fd - # Set up the store containing the certificates - set certStore [twapi::cert_temporary_store -pfx $pfx] - # Set up the client and server credentials - set serverCert [twapi::cert_store_find_certificate $certStore subject_substring twapitestserver] - # TBD - check if certs can be released as soon as we obtain credentials - set creds [twapi::sspi_acquire_credentials -credentials [twapi::sspi_schannel_credentials -certificates [list $serverCert]] -package unisp -role server] - twapi::cert_release $serverCert - twapi::cert_store_release $certStore - return $creds -} +# +# Copyright (c) 2012-2020, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license +namespace eval twapi::tls { + # Each element of _channels is dictionary with the following keys + # Socket - the underlying socket. This key will not exist if + # socket has been closed. + # State - SERVERINIT, CLIENTINIT, LISTENERINIT, OPEN, NEGOTIATING, CLOSED + # Type - SERVER, CLIENT, LISTENER + # Blocking - 0/1 indicating whether blocking or non-blocking channel + # WatchMask - list of {read write} indicating what events to post + # Target - Name for server cert + # Credentials - credentials handle to use for local end of connection + # FreeCredentials - if credentials should be freed on connection cleanup + # AcceptCallback - application callback on a listener and server socket. + # On listener, it is the accept command prefix. On a server + # (accepted socket) it is the prefix plus arguments passed to + # accept callback. On client and on servers sockets initialized + # with starttls, this key must NOT be present + # SspiContext - SSPI context for the connection + # Input - plaintext data to pass to app + # Output - plaintext data to encrypt and output + # ReadEventPosted - if this key exists, a chan postevent for read + # is already in progress and a second one should not be posted + # WriteEventPosted - if this key exists, a chan postevent for write + # is already in progress and a second one should not be posted + # WriteDisabled - 0 normally. Set to 1 on a half-close + + variable _channels + array set _channels {} + + # Socket command - Tcl socket by default. + variable _socket_cmd ::socket + + namespace path [linsert [namespace path] 0 [namespace parent]] + +} + +proc twapi::tls_socket_command {args} { + set orig_command $tls::_socket_cmd + if {[llength $args] == 1} { + set tls::_socket_cmd [lindex $args 0] + } elseif {[llength $args] != 0} { + error "wrong # args: should be \"tls_socket_command ?cmd?\"" + } + return $orig_command +} + +interp alias {} twapi::tls_socket {} twapi::tls::_socket +proc twapi::tls::_socket {args} { + variable _channels + variable _socket_cmd + + debuglog [info level 0] + + parseargs args { + myaddr.arg + myport.int + async + socketcmd.arg + server.arg + peersubject.arg + requestclientcert + {credentials.arg {}} + {verifier.arg {}} + } -setvars + + set chan [chan create {read write} [list [namespace current]]] + # NOTE: We were originally using badargs! instead of error to raise + # exceptions. However that lands up bypassing the trap because of + # the way badargs! is implemented. So stick to error. + trap { + set socket_args {} + foreach opt {myaddr myport} { + if {[info exists $opt]} { + lappend socket_args -$opt [set $opt] + } + } + if {$async} { + lappend socket_args -async + } + + if {[info exists server]} { + if {$server eq ""} { + error "Cannot specify an empty value for -server." + } + + if {[info exists peersubject]} { + error "Option -peersubject cannot be specified for with -server" + } + set peersubject "" + set type LISTENER + lappend socket_args -server [list [namespace current]::_accept $chan] + if {[llength $credentials] == 0} { + error "Option -credentials must be specified for server sockets" + } + } else { + if {![info exists peersubject]} { + set peersubject [lindex $args 0] + } + set requestclientcert 0; # Not valid for client side + set server "" + set type CLIENT + } + + if {[info exists socketcmd]} { + if {$socketcmd eq ""} { + set socketcmd ::socket + } + } else { + set socketcmd $_socket_cmd + } + } onerror {} { + catch {chan close $chan} + rethrow + } + trap { + set so [$socketcmd {*}$socket_args {*}$args] + _init $chan $type $so $credentials $peersubject $requestclientcert [lrange $verifier 0 end] $server + + if {$type eq "CLIENT"} { + if {$async} { + chan event $so writable [list [namespace current]::_so_write_handler $chan] + } else { + _client_blocking_negotiate $chan + if {(![info exists _channels($chan)]) || + [dict get $_channels($chan) State] ne "OPEN"} { + if {[info exists _channels($chan)] && + [dict exists $_channels($chan) ErrorResult]} { + error [dict get $_channels($chan) ErrorResult] + } else { + error "TLS negotiation aborted" + } + } + } + } + } onerror {} { + # If _init did not even go as far initializing _channels($chan), + # close socket ourselves. If it was initialized, the socket + # would have been closed even on error + if {![info exists _channels($chan)]} { + catch {chan close $so} + } + catch {chan close $chan} + # DON'T ACCESS _channels HERE ON + if {[string match "wrong # args*" [trapresult]]} { + badargs! "wrong # args: should be \"tls_socket ?-credentials creds? ?-verifier command? ?-peersubject peer? ?-myaddr addr? ?-myport myport? ?-async? host port\" or \"tls_socket ?-credentials creds? ?-verifier command? -server command ?-myaddr addr? port\"" + } else { + rethrow + } + } + + return $chan +} + +interp alias {} twapi::starttls {} twapi::tls::_starttls +proc twapi::tls::_starttls {so args} { + variable _channels + + debuglog [info level 0] + + trap { + parseargs args { + server + requestclientcert + peersubject.arg + {credentials.arg {}} + {verifier.arg {}} + } -setvars -maxleftover 0 + + if {$server} { + if {[info exists peersubject]} { + badargs! "Option -peersubject cannot be specified with -server." + } + if {[llength $credentials] == 0} { + error "Option -credentials must be specified for server sockets." + } + set peersubject "" + set type SERVER + } else { + set requestclientcert 0; # Ignored for client side + if {![info exists peersubject]} { + # TBD - even if verifier is specified ? + badargs! "Option -peersubject must be specified for client connections." + } + set type CLIENT + } + set chan [chan create {read write} [list [namespace current]]] + } onerror {} { + chan close $so + rethrow + } + trap { + # Get config from the wrapped socket and reset its handlers + # Do not get all options because that results in reverse name + # lookups for -peername and -sockname causing a stall. + foreach opt { + -blocking -buffering -buffersize -encoding -eofchar -translation + } { + lappend so_opts $opt [chan configure $so $opt] + } + + # NOTE: we do NOT save read and write handlers and attach + # them to the new channel because the channel name is different. + # Thus in most cases the callbacks, which often are passed the + # channel name as an arg, would not be valid. It is up + # to the caller to reestablish handlers + # TBD - maybe keep handlers but replace $so with $chan in them ? + chan event $so readable {} + chan event $so writable {} + _init $chan $type $so $credentials $peersubject $requestclientcert [lrange $verifier 0 end] "" + # Copy saved config to wrapper channel + chan configure $chan {*}$so_opts + if {$type eq "CLIENT"} { + if {[dict get $_channels($chan) Blocking]} { + _client_blocking_negotiate $chan + if {(![info exists _channels($chan)]) || + [dict get $_channels($chan) State] ne "OPEN"} { + if {[info exists _channels($chan)] && + [dict exists $_channels($chan) ErrorResult]} { + error [dict get $_channels($chan) ErrorResult] + } else { + error "TLS negotiation aborted" + } + } + } else { + _negotiate $chan + } + } else { + # Note: unlike the tls_socket server case, here we + # do not need to switch a blocking socket to non-blocking + # and then switch back, primarily because the socket + # is already open and there is no need for a callback + # when connection opens. + if {! [dict get $_channels($chan) Blocking]} { + chan configure $so -blocking 0 + chan event $so readable [list [namespace current]::_so_read_handler $chan] + } + _negotiate $chan + } + } onerror {} { + # If _init did not even go as far initializing _channels($chan), + # close socket ourselves. If it was initialized, the socket + # would have been closed even on error + if {![info exists _channels($chan)]} { + catch {chan close $so} + } + catch {chan close $chan} + # DON'T ACCESS _channels HERE ON + if {[string match "wrong # args*" [trapresult]]} { + badargs! "wrong # args: should be \"tls_socket ?-credentials creds? ?-verifier command? ?-peersubject peer? ?-myaddr addr? ?-myport myport? ?-async? host port\" or \"tls_socket ?-credentials creds? ?-verifier command? -server command ?-myaddr addr? port\"" + } else { + rethrow + } + } + + return $chan +} + +interp alias {} twapi::tls_state {} twapi::tls::_state +proc twapi::tls::_state {chan} { + variable _channels + if {![info exists _channels($chan)]} { + twapi::badargs! "Not a valid TLS channel." + } + return [dict get $_channels($chan) State] +} + +interp alias {} twapi::tls_handshake {} twapi::tls::_handshake +proc twapi::tls::_handshake {chan} { + variable _channels + if {![info exists _channels($chan)]} { + twapi::badargs "Not a valid TLS channel." + } + + dict with _channels($chan) {} + + # For a blocking channel, complete the handshake before returning + if {$Blocking} { + switch -exact $State { + NEGOTIATING - CLIENTINIT - SERVERINIT { + _negotiate2 $chan + } + OPEN {} + LISTERNERINIT { + error "Cannot do a TLS handshake on a listening socket." + } + CLOSED - + default { + error "Channel has been closed or in error state." + } + } + } else { + # For non-blocking channels, simply return the state + switch -exact -- $State { + OPEN {} + CLIENTINIT - SERVERINIT - LISTENERINIT - NEGOTIATING { + return 0 + } + CLOSED - default { + error "Channel has been closed or in error state." + } + } + } + return 1 +} + +proc twapi::tls::_accept {listener so raddr raport} { + variable _channels + + debuglog [info level 0] + + trap { + set chan [chan create {read write} [list [namespace current]]] + _init $chan SERVER $so [dict get $_channels($listener) Credentials] "" [dict get $_channels($listener) RequestClientCert] [dict get $_channels($listener) Verifier] [linsert [dict get $_channels($listener) AcceptCallback] end $chan $raddr $raport] + # If we negotiate the connection, the socket is blocking so + # will hang the whole operation. Instead we mark it non-blocking + # and the switch back to blocking when the connection gets opened. + # For accepts to work, the event loop has to be running anyways. + chan configure $so -blocking 0 + chan event $so readable [list [namespace current]::_so_read_handler $chan] + _negotiate $chan + } onerror {} { + catch {_cleanup $chan} + rethrow + } + return +} + +proc twapi::tls::initialize {chan mode} { + debuglog [info level 0] + + # All init is done in chan creation routine after base socket is created + return {initialize finalize watch blocking read write configure cget cgetall} +} + +proc twapi::tls::finalize {chan} { + debuglog [info level 0] + _cleanup $chan + return +} + +proc twapi::tls::blocking {chan mode} { + debuglog [info level 0] + + variable _channels + + dict set _channels($chan) Blocking $mode + + if {![dict exists $_channels($chan) Socket]} { + # We do not currently generate an error because the Tcl socket + # command does not either on a fconfigure when remote has + # closed connection + return + } + set so [dict get $_channels($chan) Socket] + fconfigure $so -blocking $mode + + # There is an issue with Tcl sockets created with -async switching + # from blocking->non-blocking->blocking and writing to the socket + # before connection is fully open. The internal buffers containing + # data that was written before the connection was open do not get + # flushed even if there was an explicit flush call by the application. + # Doing a flush after changing blocking mode seems to fix this + # problem. TBD - check if still the case + flush $so + + # TBD - Should we change handlers BEFORE flushing? + + # The flush may recursively call event handler (possibly) which + # may change state so have to retrieve values from _channels again. + if {![dict exists $_channels($chan) Socket]} { + return + } + set so [dict get $_channels($chan) Socket] + + if {[dict get $_channels($chan) Blocking] == 0} { + # Non-blocking + # Since we need to negotiate TLS we always have socket event + # handlers irrespective of the state of the watch mask + chan event $so readable [list [namespace current]::_so_read_handler $chan] + chan event $so writable [list [namespace current]::_so_write_handler $chan] + } else { + # TBD - is this right? Application may have file event handlers even + # on blocking sockets + chan event $so readable {} + chan event $so writable {} + } + return +} + +proc twapi::tls::watch {chan watchmask} { + debuglog [info level 0] + variable _channels + + dict set _channels($chan) WatchMask $watchmask + + if {"read" in $watchmask} { + debuglog "[info level 0]: read" + # Post a read even if we already have input or if the + # underlying socket has gone away. + # TBD - do we have a mechanism for continuously posting + # events when socket has gone away ? Do we even post once + # when socket is closed (on error for example) + if {[string length [dict get $_channels($chan) Input]] || ![dict exists $_channels($chan) Socket]} { + _post_read_event $chan + } + # Turn read handler back on in case it had been turned off. + chan event [dict get $_channels($chan) Socket] readable [list [namespace current]::_so_read_handler $chan] + } + + if {"write" in [dict get $_channels($chan) WatchMask]} { + debuglog "[info level 0]: write" + if {[dict get $_channels($chan) State] in {OPEN NEGOTIATING CLOSED} } { + _post_write_event $chan + } + # TBD - do we need to turn write handler back on? + } + + return +} + +proc twapi::tls::read {chan nbytes} { + variable _channels + + debuglog [info level 0] + + if {$nbytes == 0} { + return {} + } + + # This is not inside the dict with because _negotiate will update the dict + if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { + _negotiate $chan + if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { + # If a blocking channel, should have come back with negotiation + # complete. If non-blocking, return EAGAIN to indicate no + # data yet + if {[dict get $_channels($chan) Blocking]} { + error "TLS negotiation failed on blocking channel" + } else { + return -code error EAGAIN + } + } + } + + dict with _channels($chan) { + # Either in OPEN or CLOSED state. For the latter, if an error is + # present, immediately raise it else go on to return any pending data. + if {$State eq "CLOSED" && [info exists ErrorResult]} { + error $ErrorResult + } + # Try to read more bytes if don't have enough AND conn is open + set status ok + if {[string length $Input] < $nbytes && $State eq "OPEN"} { + if {$Blocking} { + # For blocking channels, we do not want to block if some + # bytes are already available. The refchan will call us + # with number of bytes corresponding to its buffer size, + # not what app's read call has asked. It expects us + # to return whatever we have (but at least one byte) + # and block only if nothing is available + while {[string length $Input] == 0 && $status eq "ok"} { + # The channel does not compress so we need to read in + # at least $needed bytes. Because of TLS overhead, we may + # actually need even more + set status ok + set data [_blocking_read $Socket] + if {[string length $data]} { + lassign [sspi_decrypt_stream $SspiContext $data] status plaintext + # Note plaintext might be "" if complete cipher block + # was not received + append Input $plaintext + } else { + set status eof + } + } + } else { + # Non-blocking - read all that we can + set status ok + set data [chan read $Socket] + if {[string length $data]} { + lassign [sspi_decrypt_stream $SspiContext $data] status plaintext + append Input $plaintext + } else { + if {[chan eof $Socket]} { + set status eof + } + } + if {[string length $Input] == 0} { + # Do not have enough data. See if connection closed + # TBD - also handle status == renegotiate + if {$status eq "ok"} { + # Not closed, just waiting for data + return -code error EAGAIN + } + } + } + } + + # TBD - use inline K operator to make this faster? Probably no use + # since Input is also referred to from _channels($chan) + set ret [string range $Input 0 $nbytes-1] + set Input [string range $Input $nbytes end] + if {"read" in [dict get $_channels($chan) WatchMask] && [string length $Input]} { + _post_read_event $chan + } + if {$status ne "ok"} { + # TBD - handle renegotiate + debuglog "read: setting State CLOSED" + + # Need a EOF event even if read event posted. See Bug #203 + _post_eof_event $chan + set State CLOSED + lassign [sspi_shutdown_context $SspiContext] _ outdata + if {[info exists Socket]} { + if {[string length $outdata] && $status ne "eof"} { + puts -nonewline $Socket $outdata + } + catch {close $Socket} + unset Socket + } + } + return $ret; # Note ret may be "" + } +} + +proc twapi::tls::write {chan data} { + variable _channels + + set datalen [string length $data] + debuglog "twapi::tls::write: $chan, $datalen bytes" + + # This is not inside the dict with below because _negotiate will update the dict + if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { + _negotiate $chan + if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { + if {[dict get $_channels($chan) Blocking]} { + # If a blocking channel, negotiation should have completed + error "TLS negotiation failed on blocking channel" + } else { + # TBD - which of the following alternatives to use? + if {1} { + # Store for later output once connection is open + debuglog "twapi::tls::write conn not open, appending $datalen bytes to pending output" + dict append _channels($chan) Output $data + return $datalen + } else { + # If non-blocking, return EAGAIN to indicate channel + # not open yet. + debuglog "twapi::tls::write returning EAGAIN" + return -code error EAGAIN + } + } + } + } + + dict with _channels($chan) { + debuglog "twapi::tls::write state $State" + switch $State { + CLOSED { + # Just like a Tcl socket, we do not raise an error on a + # write to a closed socket. Simply throw away the data/ + # However, if an error already exists (negotiation fail) + # raise it. + if {[info exists ErrorResult]} { + error $ErrorResult + } + } + OPEN { + if {$WriteDisabled} { + error "Channel closed for output." + } + # There might be pending output if channel has just + # transitioned to OPEN state + _flush_pending_output $chan + # TBD - use sspi_encrypt_and_write instead + chan puts -nonewline $Socket [sspi_encrypt_stream $SspiContext $data] + flush $Socket + } + default { + append Output $data + } + } + } + debuglog "twapi::tls::write returning $datalen" + return $datalen +} + +proc twapi::tls::configure {chan opt val} { + debuglog [info level 0] + # Does not make sense to change creds and verifier after creation + switch $opt { + -context - + -verifier - + -credentials { + error "$opt is a read-only option." + } + default { + chan configure [_chansocket $chan] $opt $val + } + } + + return +} + +proc twapi::tls::cget {chan opt} { + debuglog [info level 0] + variable _channels + + switch $opt { + -credentials { + return [dict get $_channels($chan) Credentials] + } + -verifier { + return [dict get $_channels($chan) Verifier] + } + -context { + return [dict get $_channels($chan) SspiContext] + } + -error { + if {[dict exists $_channels($chan) ErrorResult]} { + set result "[dict get $_channels($chan) ErrorResult]" + if {$result ne ""} { + return $result + } + } + # Get -error from underlying socket + # -error should not raise an error but return the error as result + catch {chan configure [_chansocket $chan] -error} result + return $result + } + default { + return [chan configure [_chansocket $chan] $opt] + } + } +} + +proc twapi::tls::cgetall {chan} { + debuglog [info level 0] + variable _channels + dict with _channels($chan) { + if {[info exists Socket]} { + # First get all options underlying socket supports. Note this may + # or may not a Tcl native socket. + array set so_config [chan configure $Socket] + # Only return options that are not owned by the core channel code + # and apply to the $chan itself. + foreach {opt val} [chan configure $Socket] { + if {$opt ni {-blocking -buffering -buffersize -encoding -eofchar -translation}} { + lappend config $opt $val + } + } + } + lappend config -credentials $Credentials \ + -verifier $Verifier \ + -context $SspiContext + } + return $config +} + +# Implement a half-close command since Tcl does not support it for +# reflected channels. +interp alias {} twapi::tls_close {} twapi::tls::_close +proc twapi::tls::_close {chan {direction ""}} { + + if {$direction in {read r re rea}} { + error "Half close of input side not currently supported for TLS sockets." + } + + # We handle write-side half-closes. Let Tcl close handle everything else. + if {$direction ni {write w wr wri writ}} { + return [close $chan] + } + + # Closing the write side of the channel + + variable _channels + + dict with _channels($chan) {} + if {$State eq "CLOSED"} return + if {$State ne "OPEN"} { + error "Connection not in OPEN state." + } + flush $chan + # Note state may have changed + if {[dict get $_channels($chan) State] ne "OPEN"} { + return + } + # Flush internally buffered, if any. Can happen if we buffered + # data before TLS negotiation was complete. + _flush_pending_output $chan + close $Socket write + dict set _channels($chan) WriteDisabled 1 + return +} + +proc twapi::tls::_chansocket {chan} { + debuglog [info level 0] + variable _channels + if {![info exists _channels($chan)]} { + error "Channel $chan not found." + } + if {![dict exists $_channels($chan) Socket]} { + set error "Socket not connected." + if {[dict exists $_channels($chan) ErrorResult]} { + append error " [dict get $_channels($chan) ErrorResult]" + } + error $error + } + return [dict get $_channels($chan) Socket] +} + +proc twapi::tls::_init {chan type so creds peersubject requestclientcert verifier {accept_callback {}}} { + debuglog [info level 0] + variable _channels + + # TBD - verify that -buffering none is the right thing to do + # as the scripted channel interface takes care of this itself + chan configure $so -translation binary -buffering none + set _channels($chan) [list Socket $so \ + State ${type}INIT \ + Type $type \ + Blocking [chan configure $so -blocking] \ + WatchMask {} \ + WriteDisabled 0 \ + RequestClientCert $requestclientcert \ + Verifier $verifier \ + SspiContext {} \ + PeerSubject $peersubject \ + Input {} Output {}] + + if {[llength $creds]} { + set free_creds 0 + } else { + set creds [sspi_acquire_credentials -package tls -role client -credentials [sspi_schannel_credentials]] + set free_creds 1 + } + dict set _channels($chan) Credentials $creds + dict set _channels($chan) FreeCredentials $free_creds + + # See SF issue #178. Need to supply -usesuppliedcreds to sspi_client_context + # else servers that request (even optionally) client certs might fail since + # we do not currently implement incomplete credentials handling. This + # option will prevent schannel from trying to automatically look up client + # certificates. + dict set _channels($chan) UseSuppliedCreds 0; # TBD - make this use settable option + + if {[string length $accept_callback] && + ($type eq "LISTENER" || $type eq "SERVER")} { + dict set _channels($chan) AcceptCallback $accept_callback + } +} + +proc twapi::tls::_cleanup {chan} { + debuglog [info level 0] + variable _channels + if {[info exists _channels($chan)]} { + # Note _cleanup can be called in inconsistent state so not all + # keys may be set up + dict with _channels($chan) { + if {[info exists SspiContext]} { + if {$State eq "OPEN"} { + lassign [sspi_shutdown_context $SspiContext] _ outdata + if {[string length $outdata] && [info exists Socket]} { + if {[catch {puts -nonewline $Socket $outdata} msg]} { + # TBD - debug log + } + } + } + if {[catch {sspi_delete_context $SspiContext} msg]} { + # TBD - debug log + } + } + if {[info exists Socket]} { + if {[catch {chan close $Socket} msg]} { + # TBD - debug log socket close error + } + } + if {[info exists Credentials] && $FreeCredentials} { + if {[catch {sspi_free_credentials $Credentials} msg]} { + # TBD - debug log + } + } + } + unset _channels($chan) + } +} + +proc twapi::tls::_cleanup_failed_accept {chan} { + debuglog [info level 0] + variable _channels + # This proc is called from the event loop when negotiation fails + # on a server TLS channel that is not yet open (and hence not + # known to the application). For some protection against + # channel name re-use (which does not happen as of 8.6) + # check the state before cleaning up. + if {[info exists _channels($chan)] && + [dict get $_channels($chan) Type] eq "SERVER" && + [dict get $_channels($chan) State] eq "CLOSED"} { + close $chan; # Really close + } +} + +if {[llength [info commands ::twapi::tls_background_error]] == 0} { + proc twapi::tls_background_error {result ropts} { + return -options $ropts $result + } +} + +proc twapi::tls::_negotiate_from_handler {chan} { + # Called from socket read / write handlers if + # negotiation is still in progress. + # Returns the error code from next step of + # negotiation. + # 1 -> ok, + # 0 -> some error occured, most likely negotiation failure + variable _channels + if {[catch {_negotiate $chan} result ropts]} { + if {![dict exists $_channels($chan) ErrorResult]} { + dict set _channels($chan) ErrorResult $result + } + if {"read" in [dict get $_channels($chan) WatchMask]} { + _post_read_event $chan + } + if {"write" in [dict get $_channels($chan) WatchMask]} { + _post_write_event $chan + } + # For SERVER sockets, force error because no other way + # to record some error happened. + if {[dict get $_channels($chan) Type] eq "SERVER"} { + ::twapi::tls_background_error $result $ropts + # Above should raise an error, else do it ourselves + # since stack needs to be rewound + return -options $ropts $result + } + return 0 + } + return 1 +} + +proc twapi::tls::_so_read_handler {chan} { + debuglog [info level 0] + variable _channels + + if {[info exists _channels($chan)]} { + if {[dict get $_channels($chan) State] in {SERVERINIT CLIENTINIT NEGOTIATING}} { + if {![_negotiate_from_handler $chan]} { + return + } + } + + if {"read" in [dict get $_channels($chan) WatchMask]} { + _post_read_event $chan + } else { + # We are not asked to generate read events, turn off the read + # event handler unless we are negotiating + if {[dict get $_channels($chan) State] ni {SERVERINIT CLIENTINIT NEGOTIATING}} { + if {[dict exists $_channels($chan) Socket]} { + chan event [dict get $_channels($chan) Socket] readable {} + } + } + } + } + return +} + +proc twapi::tls::_so_write_handler {chan} { + debuglog [info level 0] + variable _channels + + if {[info exists _channels($chan)]} { + debuglog "[info level 0]: channel exists" + dict with _channels($chan) {} + + # If we are not actually asked to generate write events, + # the only time we want a write handler is on a client -async + # Once it runs, we never want it again else it will keep triggering + # as sockets are always writable + if {"write" ni $WatchMask} { + debuglog "[info level 0]: write not in writemask" + if {[info exists Socket]} { + chan event $Socket writable {} + } + } + + if {$State in {SERVERINIT CLIENTINIT NEGOTIATING}} { + debuglog "[info level 0]: Calling _negotiate_from_handler, State=$State" + if {![_negotiate_from_handler $chan]} { + # TBD - should we throw so bgerror gets run? + debuglog "[info level 0]: _negotiate_from_handler returned non-zero." + return + } + } + debuglog "[info level 0]: State = $State, newstate=[dict get $_channels($chan) State]" + # Do not use local var $State because _negotiate might have updated it + if {"write" in $WatchMask && [dict get $_channels($chan) State] eq "OPEN"} { + debuglog "[info level 0]: posting write event" + _post_write_event $chan + } else { + debuglog "[info level 0]: NOT posting write event" + } + } + debuglog "[info level 0]: returning" + return +} + +proc twapi::tls::_negotiate chan { + debuglog [info level 0] + trap { + _negotiate2 $chan + } onerror {} { + variable _channels + if {[info exists _channels($chan)]} { + if {[dict get $_channels($chan) Type] eq "SERVER" && + [dict get $_channels($chan) State] in {SERVERINIT NEGOTIATING}} { + # There is no one to clean up accepted sockets (server) that + # fail verification (or error out) since application does + # not know about them. So queue some garbage + # cleaning. + after 0 [namespace current]::_cleanup_failed_accept $chan + } + dict set _channels($chan) State CLOSED + dict set _channels($chan) ErrorOptions [trapoptions] + dict set _channels($chan) ErrorResult [trapresult] + if {[dict exists $_channels($chan) Socket]} { + catch {close [dict get $_channels($chan) Socket]} + dict unset _channels($chan) Socket + } + } + rethrow + } +} + +proc twapi::tls::_negotiate2 {chan} { + variable _channels + + dict with _channels($chan) {}; # dict -> local vars + + debuglog "[info level 0]: State=$State" + switch $State { + NEGOTIATING { + if {$Blocking && ![info exists AcceptCallback]} { + debuglog "[info level 0]: Blocking" + return [_blocking_negotiate_loop $chan] + } + + set data [chan read $Socket] + if {[string length $data] == 0} { + debuglog "[info level 0]: No data from socket" + if {[chan eof $Socket]} { + debuglog "[info level 0]: EOF on socket" + throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation (NEGOTIATING)" + } else { + # No data yet, just keep waiting + debuglog "Waiting (chan $chan) for more data on Socket $Socket" + return + } + } else { + debuglog "[info level 0]: Read data from socket" + lassign [sspi_step $SspiContext $data] status outdata leftover + debuglog "[info level 0]: sspi_step returned $status" + debuglog "sspi_step returned status $status with [string length $outdata] bytes" + if {[string length $outdata]} { + chan puts -nonewline $Socket $outdata + chan flush $Socket + } + switch $status { + done { + if {[string length $leftover]} { + lassign [sspi_decrypt_stream $SspiContext $leftover] status plaintext + dict append _channels($chan) Input $plaintext + if {$status ne "ok"} { + # TBD - shutdown channel or let _cleanup do it? + } + } + _open $chan + } + continue { + # Keep waiting for next input + } + default { + debuglog "sspi_step returned $status" + error "Unexpected status $status from sspi_step" + } + } + } + } + + CLIENTINIT { + if {$Blocking} { + debuglog "[info level 0]: CLIENTINIT - blocking negotiate" + _client_blocking_negotiate $chan + } else { + dict set _channels($chan) State NEGOTIATING + set SspiContext [sspi_client_context $Credentials -usesuppliedcreds $UseSuppliedCreds -stream 1 -target $PeerSubject -manualvalidation [expr {[llength $Verifier] > 0}]] + dict set _channels($chan) SspiContext $SspiContext + lassign [sspi_step $SspiContext] status outdata + debuglog "[info level 0]: sspi_step returned $status" + if {[string length $outdata]} { + chan puts -nonewline $Socket $outdata + chan flush $Socket + } + if {$status ne "continue"} { + error "Unexpected status $status from sspi_step" + } + } + } + + SERVERINIT { + # For server sockets created from tls_socket, we + # always take the non-blocking path as we set the socket + # to be non-blocking so as to not hold up the whole app + # For server sockets created with starttls + # (AcceptCallback will not exist), we can do a blocking + # negotiate. + if {$Blocking && ![info exists AcceptCallback]} { + _server_blocking_negotiate $chan + } else { + set data [chan read $Socket] + if {[string length $data] == 0} { + if {[chan eof $Socket]} { + throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation (SERVERINIT)" + } else { + # No data yet, just keep waiting + debuglog "$chan: no data from socket $Socket. Waiting..." + return + } + } else { + debuglog "Setting $chan State=NEGOTIATING" + + dict set _channels($chan) State NEGOTIATING + set SspiContext [sspi_server_context $Credentials $data -stream 1 -mutualauth $RequestClientCert] + dict set _channels($chan) SspiContext $SspiContext + lassign [sspi_step $SspiContext] status outdata leftover + debuglog "sspi_step returned status $status with [string length $outdata] bytes" + if {[string length $outdata]} { + debuglog "Writing [string length $outdata] bytes to socket $Socket" + chan puts -nonewline $Socket $outdata + chan flush $Socket + } + switch $status { + done { + if {[string length $leftover]} { + lassign [sspi_decrypt_stream $SspiContext $leftover] status plaintext + dict append _channels($chan) Input $plaintext + if {$status ne "ok"} { + # TBD - shut down channel + } + } + debuglog "Marking channel $chan open" + _open $chan + } + continue { + # Keep waiting for next input + } + default { + error "Unexpected status $status from sspi_step" + } + } + } + } + } + + default { + error "Internal error: _negotiate called in state [dict get $_channels($chan) State]" + } + } + debuglog "[info level 0]: returning with state [dict get $_channels($chan) State]" + return +} + +proc twapi::tls::_client_blocking_negotiate {chan} { + debuglog [info level 0] + variable _channels + dict with _channels($chan) { + set State NEGOTIATING + set SspiContext [sspi_client_context $Credentials -usesuppliedcreds $UseSuppliedCreds -stream 1 -target $PeerSubject -manualvalidation [expr {[llength $Verifier] > 0}]] + } + return [_blocking_negotiate_loop $chan] +} + +proc twapi::tls::_server_blocking_negotiate {chan} { + debuglog [info level 0] + variable _channels + dict set _channels($chan) State NEGOTIATING + set so [dict get $_channels($chan) Socket] + set indata [_blocking_read $so] + if {[chan eof $so]} { + throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation (server)." + } + dict set _channels($chan) SspiContext [sspi_server_context [dict get $_channels($chan) Credentials] $indata -stream 1 -mutualauth [dict get $_channels($chan) RequestClientCert]] + return [_blocking_negotiate_loop $chan] +} + +proc twapi::tls::_blocking_negotiate_loop {chan} { + debuglog [info level 0] + variable _channels + + dict with _channels($chan) {}; # dict -> local vars + + lassign [sspi_step $SspiContext] status outdata + debuglog "sspi_step status $status" + # Keep looping as long as the SSPI state machine tells us to + while {$status eq "continue"} { + # If the previous step had any output, send it out + if {[string length $outdata]} { + debuglog "Writing [string length $outdata] to socket $Socket" + chan puts -nonewline $Socket $outdata + chan flush $Socket + } + + set indata [_blocking_read $Socket] + debuglog "Read [string length $indata] from socket $Socket" + if {[chan eof $Socket]} { + throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation." + } + trap { + lassign [sspi_step $SspiContext $indata] status outdata leftover + } onerror {} { + debuglog "sspi_step returned error: [trapresult]" + close $Socket + unset Socket + rethrow + } + debuglog "sspi_step status $status" + } + + # Send output irrespective of status + if {[string length $outdata]} { + chan puts -nonewline $Socket $outdata + chan flush $Socket + } + + if {$status eq "done"} { + if {[string length $leftover]} { + lassign [sspi_decrypt_stream $SspiContext $leftover] status plaintext + dict append _channels($chan) Input $plaintext + if {$status ne "ok"} { + error "Error status $status decrypting data" + } + } + _open $chan + } else { + # Should not happen. Negotiation failures will raise an error, + # not return a value + error "TLS negotiation failed: status $status." + } + + return +} + +proc twapi::tls::_blocking_read {so} { + debuglog [info level 0] + # Read from a blocking socket. We do not know how much data is needed + # so read a single byte and then read any pending + set input [chan read $so 1] + if {[string length $input]} { + set more [chan pending input $so] + if {$more > 0} { + append input [chan read $so $more] + } + } + return $input +} + +proc twapi::tls::_flush_pending_output {chan} { + variable _channels + + dict with _channels($chan) { + if {[string length $Output]} { + debuglog "_flush_pending_output: flushing output" + puts -nonewline $Socket [sspi_encrypt_stream $SspiContext $Output] + set Output "" + } + } + return +} + +# Transitions connection to OPEN or throws error if verifier returns false +# or fails +proc twapi::tls::_open {chan} { + debuglog [info level 0] + variable _channels + + dict with _channels($chan) {}; # dict -> local vars + + if {[llength $Verifier] == 0} { + # No verifier specified. In this case, we would not have specified + # -manualvalidation in creating the context and the system would + # have done the verification already for client. For servers, + # there is no verification of clients to be done by default + + # For compatibility with TLS we call accept callbacks AFTER verification + dict set _channels($chan) State OPEN + if {[info exists AcceptCallback]} { + # Server sockets are set up to be non-blocking during negotiation + # Change them back to original state before notifying app + chan configure $Socket -blocking [dict get $_channels($chan) Blocking] + chan event $Socket readable {} + after 0 $AcceptCallback + } + # If there is any pending output waiting for the connection to + # open, write it out + _flush_pending_output $chan + + return + } + + # TBD - what if verifier closes the channel + if {[{*}$Verifier $chan $SspiContext]} { + dict set _channels($chan) State OPEN + # For compatibility with TLS we call accept callbacks AFTER verification + if {[info exists AcceptCallback]} { + # Server sockets are set up to be non-blocking during + # negotiation. Change them back to original state + # before notifying app + chan configure $Socket -blocking [dict get $_channels($chan) Blocking] + chan event $Socket readable {} + after 0 $AcceptCallback + } + _flush_pending_output $chan + return + } else { + error "SSL/TLS negotiation failed. Verifier callback returned false." "" [list TWAPI TLS VERIFYFAIL] + } +} + +# Calling [chan postevent] results in filevent handlers being called right +# away which can recursively call back into channel code making things +# more than a bit messy. So we always schedule them through the event loop +proc twapi::tls::_post_read_event_callback {chan} { + debuglog [info level 0] + variable _channels + if {[info exists _channels($chan)]} { + dict unset _channels($chan) ReadEventPosted + if {"read" in [dict get $_channels($chan) WatchMask]} { + chan postevent $chan read + } + } +} +proc twapi::tls::_post_read_event {chan} { + debuglog [info level 0] + variable _channels + if {![dict exists $_channels($chan) ReadEventPosted]} { + # Note after 0 after idle does not work - (never get called) + # not sure why so just do after 0 + dict set _channels($chan) ReadEventPosted \ + [after 0 [namespace current]::_post_read_event_callback $chan] + } +} +proc twapi::tls::_post_eof_event_callback {chan} { + debuglog [info level 0] + variable _channels + if {[info exists _channels($chan)]} { + if {"read" in [dict get $_channels($chan) WatchMask]} { + chan postevent $chan read + } + } +} +proc twapi::tls::_post_eof_event {chan} { + # EOF events are always generated event if a read event is already posted. + # See Bug #203 + debuglog [info level 0] + after 0 [namespace current]::_post_eof_event_callback $chan +} + + +proc twapi::tls::_post_write_event_callback {chan} { + debuglog [info level 0] + variable _channels + if {[info exists _channels($chan)]} { + dict unset _channels($chan) WriteEventPosted + if {"write" in [dict get $_channels($chan) WatchMask]} { + # NOTE: we do not check state here as we should generate an event + # even in the CLOSED state - see Bug #206 + chan postevent $chan write + } + } +} +proc twapi::tls::_post_write_event {chan} { + debuglog [info level 0] + variable _channels + if {![dict exists $_channels($chan) WriteEventPosted]} { + # Note after 0 after idle does not work - (never get called) + # not sure why so just do after 0 + dict set _channels($chan) WriteEventPosted \ + [after 0 [namespace current]::_post_write_event_callback $chan] + } +} + +namespace eval twapi::tls { + namespace ensemble create -subcommands { + initialize finalize blocking watch read write configure cget cgetall + } +} + +proc twapi::tls::sample_server_creds pfxFile { + set fd [open $pfxFile rb] + set pfx [read $fd] + close $fd + # Set up the store containing the certificates + set certStore [twapi::cert_temporary_store -pfx $pfx] + # Set up the client and server credentials + set serverCert [twapi::cert_store_find_certificate $certStore subject_substring twapitestserver] + # TBD - check if certs can be released as soon as we obtain credentials + set creds [twapi::sspi_acquire_credentials -credentials [twapi::sspi_schannel_credentials -certificates [list $serverCert]] -package unisp -role server] + twapi::cert_release $serverCert + twapi::cert_store_release $certStore + return $creds +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/twapi.tcl b/src/vendorlib_tcl8/twapi-5.0b1/twapi.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/twapi.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/twapi.tcl index 20a5f179..5ef86188 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/twapi.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/twapi.tcl @@ -1,858 +1,855 @@ -# -# Copyright (c) 2003-2018, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# General definitions and procs used by all TWAPI modules - -package require Tcl 8.5 -package require registry - -namespace eval twapi { - # Get rid of this ugliness - TBD - # Note this is different from NULL or {0 VOID} etc. It is more like - # a null token passed to functions that expect ptr to strings and - # allow the ptr to be NULL. - variable nullptr "__null__" - - variable scriptdir [file dirname [info script]] - - # Name of the var holding log messages in reflected in the C - # code, don't change it! - variable log_messages {} - - ################################################################ - # Following procs are used early in init process so defined here - - # Throws a bad argument error that appears to come from caller's invocation - # (if default level is 2) - proc badargs! {msg {level 2}} { - return -level $level -code error -errorcode [list TWAPI BADARGS $msg] $msg - } - - proc lambda {arglist body {ns {}}} { - return [list ::apply [list $arglist $body $ns]] - } - - # Similar to lambda but takes additional parameters to be passed - # to the anonymous functin - proc lambda* {arglist body {ns {}} args} { - return [list ::apply [list $arglist $body $ns] {*}$args] - } - - # Rethrow original exception from inside a trap - proc rethrow {} { - return -code error -level 0 -options [twapi::trapoptions] [twapi::trapresult] - } - - # Dict lookup, returns default (from args) if not in dict and - # key itself if no defaults specified - proc dict* {d key args} { - if {[dict exists $d $key]} { - return [dict get $d $key] - } elseif {[llength $args]} { - return [lindex $args 0] - } else { - return $key - } - } - - proc dict! {d key {frame 0}} { - if {[dict exists $d $key]} { - return [dict get $d $key] - } else { - # frame is how must above the caller errorInfo must appear - return [badargs! "Bad value \"$key\". Must be one of [join [dict keys $d] {, }]" [incr frame 2]] - } - } - - - # Defines a proc with some initialization code - proc proc* {procname arglist initcode body} { - if {![string match ::* $procname]} { - set ns [uplevel 1 {namespace current}] - set procname ${ns}::$procname - } - set proc_def [format {proc %s {%s} {%s ; proc %s {%s} {%s} ; uplevel 1 [list %s] [lrange [info level 0] 1 end]}} $procname $arglist $initcode $procname $arglist $body $procname] - uplevel 1 $proc_def - } - - # Swap keys and values - proc swapl {l} { - set swapped {} - foreach {a b} $l { - lappend swapped $b $a - } - return $swapped - } - - # TBD - see if C would make faster - # Returns a list consisting of n'th index within each sublist element - # Should we allow n to be a nested index ? C impl may be harder - proc lpick {l {n 0}} { - set result {} - foreach e $l { - lappend result [lindex $e $n] - } - return $result - } - - # Simple helper to treat lists as a stack - proc lpop {vl} { - upvar 1 $vl l - set top [lindex $l end] - # K combinator trick to reset l to allow lreplace to work in place - set l [lreplace $l [set l end] end] - return $top - } - - # twine list of n items - proc ntwine {fields l} { - set ntwine {} - foreach e $l { - lappend ntwine [twine $fields $e] - } - return $ntwine - } - - # Qualifies a name in context of caller's caller - proc callerns {name} { - if {[string match "::*" $name]} { - return $name - } - if {[info level] > 2} { - return [uplevel 2 namespace current]::$name - } else { - return ::$name - } - } -} - -# Make twapi versions the same as the base module versions -set twapi::version(twapi) $::twapi::version(twapi_base) - -# -# log for tracing / debug messages. -proc twapi::debuglog_clear {} { - variable log_messages - set log_messages {} -} - -proc twapi::debuglog_enable {} { - catch {rename [namespace current]::debuglog {}} - interp alias {} [namespace current]::debuglog {} [namespace current]::Twapi_AppendLog -} - -proc twapi::debuglog_disable {} { - proc [namespace current]::debuglog {args} {} -} - -proc twapi::debuglog_get {} { - variable log_messages - return $log_messages -} - -# Logging disabled by default -twapi::debuglog_disable - -proc twapi::get_build_config {{key ""}} { - variable build_ids - array set config [GetTwapiBuildInfo] - - # This is actually a runtime config and might not have been initialized - if {[info exists ::twapi::use_tcloo_for_com]} { - if {$::twapi::use_tcloo_for_com} { - set config(comobj_ootype) tcloo - } else { - set config(comobj_ootype) metoo - } - } else { - set config(comobj_ootype) uninitialized - } - - if {$key eq ""} { - return [array get config] - } else { - if {![info exists config($key)]} { - error "key not known"; # Matches tcl::pkgconfig error message - } - return $config($key) - } -} - -# This matches the pkgconfig command as defined by Tcl_RegisterConfig -# TBD - Doc and test -proc twapi::pkgconfig {subcommand {arg {}}} { - if {$subcommand eq "list"} { - if {$arg ne ""} { - error {wrong # args: should be "twapi::pkgconfig list"} - } - return [dict keys [get_build_config]] - } elseif {$subcommand eq "get"} { - if {$arg eq ""} { - error {wrong # args: should be "twapi::pkgconfig get key"} - } - return [get_build_config $arg] - } else { - error {wrong # args: should be "tcl::pkgconfig subcommand ?arg?"} - } -} - -# TBD - document -proc twapi::support_report {} { - set report "Operating system: [get_os_description]\n" - append report "Processors: [get_processor_count]\n" - append report "WOW64: [wow64_process]\n" - append report "Virtualized: [virtualized_process]\n" - append report "System locale: [get_system_default_lcid], [get_system_default_langid]\n" - append report "User locale: [get_user_default_lcid], [get_user_default_langid]\n" - append report "Tcl version: [info patchlevel]\n" - append report "tcl_platform:\n" - foreach k [lsort -dictionary [array names ::tcl_platform]] { - append report " $k = $::tcl_platform($k)\n" - } - append report "TWAPI version: [get_version -patchlevel]\n" - array set a [get_build_config] - append report "TWAPI config:\n" - foreach k [lsort -dictionary [array names a]] { - append report " $k = $a($k)\n" - } - append report "\nDebug log:\n[join [debuglog_get] \n]\n" -} - - -# Returns a list of raw Windows API functions supported -proc twapi::list_raw_api {} { - set rawapi [list ] - foreach fn [info commands ::twapi::*] { - if {[regexp {^::twapi::([A-Z][^_]*)$} $fn ignore fn]} { - lappend rawapi $fn - } - } - return $rawapi -} - - -# Wait for $wait_ms milliseconds or until $script returns $guard. $gap_ms is -# time between retries to call $script -# TBD - write a version that will allow other events to be processed -proc twapi::wait {script guard wait_ms {gap_ms 10}} { - if {$gap_ms == 0} { - set gap_ms 10 - } - set end_ms [expr {[clock clicks -milliseconds] + $wait_ms}] - while {[clock clicks -milliseconds] < $end_ms} { - set script_result [uplevel $script] - if {[string equal $script_result $guard]} { - return 1 - } - after $gap_ms - } - # Reached limit, one last try - return [string equal [uplevel $script] $guard] -} - -# Get twapi version -proc twapi::get_version {args} { - variable version - array set opts [parseargs args {patchlevel}] - if {$opts(patchlevel)} { - return $version(twapi) - } else { - # Only return major, minor - set ver $version(twapi) - regexp {^([[:digit:]]+\.[[:digit:]]+)[.ab]} $version(twapi) - ver - return $ver - } -} - -# Set all elements of the array to specified value -proc twapi::_array_set_all {v_arr val} { - upvar $v_arr arr - foreach e [array names arr] { - set arr($e) $val - } -} - -# Check if any of the specified array elements are non-0 -proc twapi::_array_non_zero_entry {v_arr indices} { - upvar $v_arr arr - foreach i $indices { - if {$arr($i)} { - return 1 - } - } - return 0 -} - -# Check if any of the specified array elements are non-0 -# and return them as a list of options (preceded with -) -proc twapi::_array_non_zero_switches {v_arr indices all} { - upvar $v_arr arr - set result [list ] - foreach i $indices { - if {$all || ([info exists arr($i)] && $arr($i))} { - lappend result -$i - } - } - return $result -} - - -# Bitmask operations on 32bit values -# The int() casts are to deal with hex-decimal sign extension issues -proc twapi::setbits {v_bits mask} { - upvar $v_bits bits - set bits [expr {int($bits) | int($mask)}] - return $bits -} -proc twapi::resetbits {v_bits mask} { - upvar $v_bits bits - set bits [expr {int($bits) & int(~ $mask)}] - return $bits -} - -# Return a bitmask corresponding to a list of symbolic and integer values -# If symvals is a single item, it is an array else a list of sym bitmask pairs -proc twapi::_parse_symbolic_bitmask {syms symvals} { - if {[llength $symvals] == 1} { - upvar $symvals lookup - } else { - array set lookup $symvals - } - set bits 0 - foreach sym $syms { - if {[info exists lookup($sym)]} { - set bits [expr {$bits | $lookup($sym)}] - } else { - set bits [expr {$bits | $sym}] - } - } - return $bits -} - -# Return a list of symbols corresponding to a bitmask -proc twapi::_make_symbolic_bitmask {bits symvals {append_unknown 1}} { - if {[llength $symvals] == 1} { - upvar $symvals lookup - set map [array get lookup] - } else { - set map $symvals - } - set symbits 0 - set symmask [list ] - foreach {sym val} $map { - if {$bits & $val} { - set symbits [expr {$symbits | $val}] - lappend symmask $sym - } - } - - # Get rid of bits that mapped to symbols - set bits [expr {$bits & ~$symbits}] - # If any left over, add them - if {$bits && $append_unknown} { - lappend symmask $bits - } - return $symmask -} - -# Return a bitmask corresponding to a list of symbolic and integer values -# If symvals is a single item, it is an array else a list of sym bitmask pairs -# Ditto for switches - an array or flat list of switch boolean pairs -proc twapi::_switches_to_bitmask {switches symvals {bits 0}} { - if {[llength $symvals] == 1} { - upvar $symvals lookup - } else { - array set lookup $symvals - } - if {[llength $switches] == 1} { - upvar $switches swtable - } else { - array set swtable $switches - } - - foreach {switch bool} [array get swtable] { - if {$bool} { - set bits [expr {$bits | $lookup($switch)}] - } else { - set bits [expr {$bits & ~ $lookup($switch)}] - } - } - return $bits -} - -# Return a list of switche bool pairs corresponding to a bitmask -proc twapi::_bitmask_to_switches {bits symvals} { - if {[llength $symvals] == 1} { - upvar $symvals lookup - set map [array get lookup] - } else { - set map $symvals - } - set symbits 0 - set symmask [list ] - foreach {sym val} $map { - if {$bits & $val} { - set symbits [expr {$symbits | $val}] - lappend symmask $sym 1 - } else { - lappend symmask $sym 0 - } - } - - return $symmask -} - -# Make and return a keyed list -proc twapi::kl_create {args} { - if {[llength $args] & 1} { - error "No value specified for keyed list field [lindex $args end]. A keyed list must have an even number of elements." - } - return $args -} - -# Make a keyed list given fields and values -interp alias {} twapi::kl_create2 {} twapi::twine - -# Set a key value -proc twapi::kl_set {kl field newval} { - set i 0 - foreach {fld val} $kl { - if {[string equal $fld $field]} { - incr i - return [lreplace $kl $i $i $newval] - } - incr i 2 - } - lappend kl $field $newval - return $kl -} - -# Check if a field exists in the keyed list -proc twapi::kl_vget {kl field varname} { - upvar $varname var - return [expr {! [catch {set var [kl_get $kl $field]}]}] -} - -# Remote/unset a key value -proc twapi::kl_unset {kl field} { - array set arr $kl - unset -nocomplain arr($field) - return [array get arr] -} - -# Compare two keyed lists -proc twapi::kl_equal {kl_a kl_b} { - array set a $kl_a - foreach {kb valb} $kl_b { - if {[info exists a($kb)] && ($a($kb) == $valb)} { - unset a($kb) - } else { - return 0 - } - } - if {[array size a]} { - return 0 - } else { - return 1 - } -} - -# Return the field names in a keyed list in the same order that they -# occured -proc twapi::kl_fields {kl} { - set fields [list ] - foreach {fld val} $kl { - lappend fields $fld - } - return $fields -} - -# Returns a flat list of the $field fields from a list -# of keyed lists -proc twapi::kl_flatten {list_of_kl args} { - set result {} - foreach kl $list_of_kl { - foreach field $args { - lappend result [kl_get $kl $field] - } - } - return $result -} - - -# Return an array as a list of -index value pairs -proc twapi::_get_array_as_options {v_arr} { - upvar $v_arr arr - set result [list ] - foreach {index value} [array get arr] { - lappend result -$index $value - } - return $result -} - -# Parse a list of two integers or a x,y pair and return a list of two integers -# Generate exception on format error using msg -proc twapi::_parse_integer_pair {pair {msg "Invalid integer pair"}} { - if {[llength $pair] == 2} { - lassign $pair first second - if {[string is integer -strict $first] && - [string is integer -strict $second]} { - return [list $first $second] - } - } elseif {[regexp {^([[:digit:]]+),([[:digit:]]+)$} $pair dummy first second]} { - return [list $first $second] - } - - error "$msg: '$pair'. Should be a list of two integers or in the form 'x,y'" -} - - -# Convert file names by substituting \SystemRoot and \??\ sequences -proc twapi::_normalize_path {path} { - # Get rid of \??\ prefixes - regsub {^[\\/]\?\?[\\/](.*)} $path {\1} path - - # Replace leading \SystemRoot with real system root - if {[string match -nocase {[\\/]Systemroot*} $path] && - ([string index $path 11] in [list "" / \\])} { - return [file join [twapi::GetSystemWindowsDirectory] [string range $path 12 end]] - } else { - return [file normalize $path] - } -} - - -# Convert seconds to a list {Year Month Day Hour Min Sec Ms} -# (Ms will always be zero). -proc twapi::_seconds_to_timelist {secs {gmt 0}} { - # For each field, we need to trim the leading zeroes - set result [list ] - foreach x [clock format $secs -format "%Y %m %e %k %M %S 0" -gmt $gmt] { - lappend result [scan $x %d] - } - return $result -} - -# Convert local time list {Year Month Day Hour Min Sec Ms} to seconds -# (Ms field is ignored) -# TBD - fix this gmt issue - not clear whether caller expects gmt time -proc twapi::_timelist_to_seconds {timelist} { - return [clock scan [_timelist_to_timestring $timelist] -gmt false] -} - -# Convert local time list {Year Month Day Hour Min Sec Ms} to a time string -# (Ms field is ignored) -proc twapi::_timelist_to_timestring {timelist} { - if {[llength $timelist] < 6} { - error "Invalid time list format" - } - - return "[lindex $timelist 0]-[lindex $timelist 1]-[lindex $timelist 2] [lindex $timelist 3]:[lindex $timelist 4]:[lindex $timelist 5]" -} - -# Convert a time string to a time list -proc twapi::_timestring_to_timelist {timestring} { - return [_seconds_to_timelist [clock scan $timestring -gmt false]] -} - -# Parse raw memory like binary scan command -proc twapi::mem_binary_scan {mem off mem_sz args} { - uplevel [list binary scan [Twapi_ReadMemory 1 $mem $off $mem_sz]] $args -} - - -# Validate guid syntax -proc twapi::_validate_guid {guid} { - if {![Twapi_IsValidGUID $guid]} { - error "Invalid GUID syntax: '$guid'" - } -} - -# Validate uuid syntax -proc twapi::_validate_uuid {uuid} { - if {![regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$} $uuid]} { - error "Invalid UUID syntax: '$uuid'" - } -} - -# Extract a UCS-16 string from a binary. Cannot directly use -# encoding convertfrom because that will not stop at the terminating -# null. The UCS-16 assumed to be little endian. -proc twapi::_ucs16_binary_to_string {bin {off 0}} { - set bin [string range $bin $off end] - - # Find the terminating null. - set off [string first \0\0 $bin] - while {$off > 0 && ($off & 1)} { - # Offset off is odd and so crosses a char boundary, so not the - # terminating null. Step to the char boundary and start search again - incr off - set off [string first \0\0 $bin $off] - } - # off is offset of terminating UCS-16 null, or -1 if not found - if {$off < 0} { - # No terminator - return [encoding convertfrom unicode $bin] - } else { - return [encoding convertfrom unicode [string range $bin 0 $off-1]] - } -} - -# Extract a string from a binary. Cannot directly use -# encoding convertfrom because that will not stop at the terminating -# null. -proc twapi::_ascii_binary_to_string {bin {off 0}} { - set bin [string range $bin $off end] - - # Find the terminating null. - set off [string first \0 $bin] - - # off is offset of terminating null, or -1 if not found - if {$off < 0} { - # No terminator - return [encoding convertfrom ascii $bin] - } else { - return [encoding convertfrom ascii [string range $bin 0 $off-1]] - } -} - - -# Given a binary, return a GUID. The formatting is done as per the -# Windows StringFromGUID2 convention used by COM -proc twapi::_binary_to_guid {bin {off 0}} { - if {[binary scan $bin "@$off i s s H4 H12" g1 g2 g3 g4 g5] != 5} { - error "Invalid GUID binary" - } - - return [format "{%8.8X-%2.2hX-%2.2hX-%s}" $g1 $g2 $g3 [string toupper "$g4-$g5"]] -} - -# Given a guid string, return a GUID in binary form -proc twapi::_guid_to_binary {guid} { - _validate_guid $guid - lassign [split [string range $guid 1 end-1] -] g1 g2 g3 g4 g5 - return [binary format "i s s H4 H12" 0x$g1 0x$g2 0x$g3 $g4 $g5] -} - -# Return a guid from raw memory -proc twapi::_decode_mem_guid {mem {off 0}} { - return [_binary_to_guid [Twapi_ReadMemory 1 $mem $off 16]] -} - -# Convert a Windows registry value to Tcl form. mem is a raw -# memory object. off is the offset into the memory object to read. -# $type is a integer corresponding -# to the registry types -proc twapi::_decode_mem_registry_value {type mem len {off 0}} { - set type [expr {$type}]; # Convert hex etc. to decimal form - switch -exact -- $type { - 1 - - 2 { - return [list [expr {$type == 2 ? "expand_sz" : "sz"}] \ - [Twapi_ReadMemory 3 $mem $off $len 1]] - } - 7 { - # Collect strings until we come across an empty string - # Note two nulls right at the start will result in - # an empty list. Should it result in a list with - # one empty string element? Most code on the web treats - # it as the former so we do too. - set multi [list ] - while {1} { - set str [Twapi_ReadMemory 3 $mem $off -1] - set n [string length $str] - # Check for out of bounds. Cannot check for this before - # actually reading the string since we do not know size - # of the string. - if {($len != -1) && ($off+$n+1) > $len} { - error "Possible memory corruption: read memory beyond specified memory size." - } - if {$n == 0} { - return [list multi_sz $multi] - } - lappend multi $str - # Move offset by length of the string and terminating null - # (times 2 since unicode and we want byte offset) - incr off [expr {2*($n+1)}] - } - } - 4 { - if {$len < 4} { - error "Insufficient number of bytes to convert to integer." - } - return [list dword [Twapi_ReadMemory 0 $mem $off]] - } - 5 { - if {$len < 4} { - error "Insufficient number of bytes to convert to big-endian integer." - } - set type "dword_big_endian" - set scanfmt "I" - set len 4 - } - 11 { - if {$len < 8} { - error "Insufficient number of bytes to convert to wide integer." - } - set type "qword" - set scanfmt "w" - set len 8 - } - 0 { set type "none" } - 6 { set type "link" } - 8 { set type "resource_list" } - 3 { set type "binary" } - default { - error "Unsupported registry value type '$type'" - } - } - - set val [Twapi_ReadMemory 1 $mem $off $len] - if {[info exists scanfmt]} { - if {[binary scan $val $scanfmt val] != 1} { - error "Could not convert from binary value using scan format $scanfmt" - } - } - - return [list $type $val] -} - - -proc twapi::_log_timestamp {} { - return [clock format [clock seconds] -format "%a %T"] -} - - -# Helper for Net*Enum type functions taking a common set of arguments -proc twapi::_net_enum_helper {function args} { - if {[llength $args] == 1} { - set args [lindex $args 0] - } - - # -namelevel is used internally to indicate what level is to be used - # to retrieve names. -preargs and -postargs are used internally to - # add additional arguments at specific positions in the generic call. - array set opts [parseargs args { - {system.arg ""} - level.int - resume.int - filter.int - {namelevel.int 0} - {preargs.arg {}} - {postargs.arg {}} - {namefield.int 0} - fields.arg - } -maxleftover 0] - - if {[info exists opts(level)]} { - set level $opts(level) - if {! [info exists opts(fields)]} { - badargs! "Option -fields must be specified if -level is specified" - } - } else { - set level $opts(namelevel) - } - - # Note later we need to know if opts(resume) was specified so - # don't change this to just default -resume to 0 above - if {[info exists opts(resume)]} { - set resumehandle $opts(resume) - } else { - set resumehandle 0 - } - - set moredata 1 - set result {} - while {$moredata} { - if {[info exists opts(filter)]} { - lassign [$function $opts(system) {*}$opts(preargs) $level $opts(filter) {*}$opts(postargs) $resumehandle] moredata resumehandle totalentries entries - } else { - lassign [$function $opts(system) {*}$opts(preargs) $level {*}$opts(postargs) $resumehandle] moredata resumehandle totalentries entries - } - # If caller does not want all data in one lump stop here - if {[info exists opts(resume)]} { - if {[info exists opts(level)]} { - return [list $moredata $resumehandle $totalentries [list $opts(fields) $entries]] - } else { - # Return flat list of names - return [list $moredata $resumehandle $totalentries [lpick $entries $opts(namefield)]] - } - } - - lappend result {*}$entries - } - - # Return what we have. Format depend on caller options. - if {[info exists opts(level)]} { - return [list $opts(fields) $result] - } else { - return [lpick $result $opts(namefield)] - } -} - -# If we are not being sourced from a executable resource, need to -# source the remaining support files. In the former case, they are -# automatically combined into one so the sourcing is not needed. -if {![info exists twapi::twapi_base_rc_sourced]} { - apply {{filelist} { - set dir [file dirname [info script]] - foreach f $filelist { - uplevel #0 [list source [file join $dir $f]] - } - }} {base.tcl handle.tcl win.tcl adsi.tcl} -} - -# Used in various matcher callbacks to signify always include etc. -# TBD - document -proc twapi::true {args} { - return true -} - - -namespace eval twapi { - # Get a handle to ourselves. This handle never need be closed - variable my_process_handle [GetCurrentProcess] -} - -# Only used internally for test validation. -# NOT the same as export_public_commands -proc twapi::_get_public_commands {} { - variable exports; # Populated via pkgIndex.tcl - if {[info exists exports]} { - return [concat {*}[dict values $exports]] - } else { - set cmds {} - foreach cmd [lsearch -regexp -inline -all [info commands [namespace current]::*] {::twapi::[a-z].*}] { - lappend cmds [namespace tail $cmd] - } - return $cmds - } -} - -proc twapi::export_public_commands {} { - variable exports; # Populated via pkgIndex.tcl - if {[info exists exports]} { - # Only export commands under twapi (e.g. not metoo) - dict for {ns cmds} $exports { - if {[regexp {^::twapi($|::)} $ns]} { - uplevel #0 [list namespace eval $ns [list namespace export {*}$cmds] -] - } - } - } else { - set cmds {} - foreach cmd [lsearch -regexp -inline -all [info commands [namespace current]::*] {::twapi::[a-z].*}] { - lappend cmds [namespace tail $cmd] - } - namespace eval [namespace current] "namespace export {*}$cmds" - } -} - -proc twapi::import_commands {} { - export_public_commands - uplevel namespace import twapi::* -} - +# +# Copyright (c) 2003-2018, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# General definitions and procs used by all TWAPI modules + +package require Tcl 8.6- +package require registry + +namespace eval twapi { + # Get rid of this ugliness - TBD + # Note this is different from NULL or {0 VOID} etc. It is more like + # a null token passed to functions that expect ptr to strings and + # allow the ptr to be NULL. + variable nullptr "__null__" + + variable scriptdir [file dirname [info script]] + + # Name of the var holding log messages in reflected in the C + # code, don't change it! + variable log_messages {} + + ################################################################ + # Following procs are used early in init process so defined here + + # Throws a bad argument error that appears to come from caller's invocation + # (if default level is 2) + proc badargs! {msg {level 2}} { + return -level $level -code error -errorcode [list TWAPI BADARGS $msg] $msg + } + + proc lambda {arglist body {ns {}}} { + return [list ::apply [list $arglist $body $ns]] + } + + # Similar to lambda but takes additional parameters to be passed + # to the anonymous functin + proc lambda* {arglist body {ns {}} args} { + return [list ::apply [list $arglist $body $ns] {*}$args] + } + + # Rethrow original exception from inside a trap + proc rethrow {} { + return -code error -level 0 -options [twapi::trapoptions] [twapi::trapresult] + } + + # Dict lookup, returns default (from args) if not in dict and + # key itself if no defaults specified + proc dict* {d key args} { + if {[dict exists $d $key]} { + return [dict get $d $key] + } elseif {[llength $args]} { + return [lindex $args 0] + } else { + return $key + } + } + + proc dict! {d key {frame 0}} { + if {[dict exists $d $key]} { + return [dict get $d $key] + } else { + # frame is how must above the caller errorInfo must appear + return [badargs! "Bad value \"$key\". Must be one of [join [dict keys $d] {, }]" [incr frame 2]] + } + } + + + # Defines a proc with some initialization code + proc proc* {procname arglist initcode body} { + if {![string match ::* $procname]} { + set ns [uplevel 1 {namespace current}] + set procname ${ns}::$procname + } + set proc_def [format {proc %s {%s} {%s ; proc %s {%s} {%s} ; uplevel 1 [list %s] [lrange [info level 0] 1 end]}} $procname $arglist $initcode $procname $arglist $body $procname] + uplevel 1 $proc_def + } + + # Swap keys and values + proc swapl {l} { + set swapped {} + foreach {a b} $l { + lappend swapped $b $a + } + return $swapped + } + + # TBD - see if C would make faster + # Returns a list consisting of n'th index within each sublist element + # Should we allow n to be a nested index ? C impl may be harder + proc lpick {l {n 0}} { + set result {} + foreach e $l { + lappend result [lindex $e $n] + } + return $result + } + + # Simple helper to treat lists as a stack + proc lpop {vl} { + upvar 1 $vl l + set top [lindex $l end] + # K combinator trick to reset l to allow lreplace to work in place + set l [lreplace $l [set l end] end] + return $top + } + + # twine list of n items + proc ntwine {fields l} { + set ntwine {} + foreach e $l { + lappend ntwine [twine $fields $e] + } + return $ntwine + } + + # Qualifies a name in context of caller's caller + proc callerns {name} { + if {[string match "::*" $name]} { + return $name + } + if {[info level] > 2} { + return [uplevel 2 namespace current]::$name + } else { + return ::$name + } + } +} + +# +# log for tracing / debug messages. +proc twapi::debuglog_clear {} { + variable log_messages + set log_messages {} +} + +proc twapi::debuglog_enable {} { + catch {rename [namespace current]::debuglog {}} + interp alias {} [namespace current]::debuglog {} [namespace current]::Twapi_AppendLog +} + +proc twapi::debuglog_disable {} { + proc [namespace current]::debuglog {args} {} +} + +proc twapi::debuglog_get {} { + variable log_messages + return $log_messages +} + +# Logging disabled by default +twapi::debuglog_disable + +proc twapi::get_build_config {{key ""}} { + variable build_ids + array set config [GetTwapiBuildInfo] + + # This is actually a runtime config and might not have been initialized + if {[info exists ::twapi::use_tcloo_for_com]} { + if {$::twapi::use_tcloo_for_com} { + set config(comobj_ootype) tcloo + } else { + set config(comobj_ootype) metoo + } + } else { + set config(comobj_ootype) uninitialized + } + + if {$key eq ""} { + return [array get config] + } else { + if {![info exists config($key)]} { + error "key not known"; # Matches tcl::pkgconfig error message + } + return $config($key) + } +} + +# This matches the pkgconfig command as defined by Tcl_RegisterConfig +# TBD - Doc and test +proc twapi::pkgconfig {subcommand {arg {}}} { + if {$subcommand eq "list"} { + if {$arg ne ""} { + error {wrong # args: should be "twapi::pkgconfig list"} + } + return [dict keys [get_build_config]] + } elseif {$subcommand eq "get"} { + if {$arg eq ""} { + error {wrong # args: should be "twapi::pkgconfig get key"} + } + return [get_build_config $arg] + } else { + error {wrong # args: should be "tcl::pkgconfig subcommand ?arg?"} + } +} + +# TBD - document +proc twapi::support_report {} { + set report "Operating system: [get_os_description]\n" + append report "Processors: [get_processor_count]\n" + append report "WOW64: [wow64_process]\n" + append report "Virtualized: [virtualized_process]\n" + append report "System locale: [get_system_default_lcid], [get_system_default_langid]\n" + append report "User locale: [get_user_default_lcid], [get_user_default_langid]\n" + append report "Tcl version: [info patchlevel]\n" + append report "tcl_platform:\n" + foreach k [lsort -dictionary [array names ::tcl_platform]] { + append report " $k = $::tcl_platform($k)\n" + } + append report "TWAPI version: [get_version -patchlevel]\n" + array set a [get_build_config] + append report "TWAPI config:\n" + foreach k [lsort -dictionary [array names a]] { + append report " $k = $a($k)\n" + } + append report "\nDebug log:\n[join [debuglog_get] \n]\n" +} + + +# Returns a list of raw Windows API functions supported +proc twapi::list_raw_api {} { + set rawapi [list ] + foreach fn [info commands ::twapi::*] { + if {[regexp {^::twapi::([A-Z][^_]*)$} $fn ignore fn]} { + lappend rawapi $fn + } + } + return $rawapi +} + + +# Wait for $wait_ms milliseconds or until $script returns $guard. $gap_ms is +# time between retries to call $script +# TBD - write a version that will allow other events to be processed +proc twapi::wait {script guard wait_ms {gap_ms 10}} { + if {$gap_ms == 0} { + set gap_ms 10 + } + set end_ms [expr {[clock clicks -milliseconds] + $wait_ms}] + while {[clock clicks -milliseconds] < $end_ms} { + set script_result [uplevel $script] + if {[string equal $script_result $guard]} { + return 1 + } + after $gap_ms + } + # Reached limit, one last try + return [string equal [uplevel $script] $guard] +} + +# Get twapi version +proc twapi::get_version {args} { + variable version + array set opts [parseargs args {patchlevel}] + if {$opts(patchlevel)} { + return $version + } else { + # Only return major, minor + set ver $version + regexp {^([[:digit:]]+\.[[:digit:]]+)[.ab]} $version - ver + return $ver + } +} + +# Set all elements of the array to specified value +proc twapi::_array_set_all {v_arr val} { + upvar $v_arr arr + foreach e [array names arr] { + set arr($e) $val + } +} + +# Check if any of the specified array elements are non-0 +proc twapi::_array_non_zero_entry {v_arr indices} { + upvar $v_arr arr + foreach i $indices { + if {$arr($i)} { + return 1 + } + } + return 0 +} + +# Check if any of the specified array elements are non-0 +# and return them as a list of options (preceded with -) +proc twapi::_array_non_zero_switches {v_arr indices all} { + upvar $v_arr arr + set result [list ] + foreach i $indices { + if {$all || ([info exists arr($i)] && $arr($i))} { + lappend result -$i + } + } + return $result +} + + +# Bitmask operations on 32bit values +# The int() casts are to deal with hex-decimal sign extension issues +proc twapi::setbits {v_bits mask} { + upvar $v_bits bits + set bits [expr {int($bits) | int($mask)}] + return $bits +} +proc twapi::resetbits {v_bits mask} { + upvar $v_bits bits + set bits [expr {int($bits) & int(~ $mask)}] + return $bits +} + +# Return a bitmask corresponding to a list of symbolic and integer values +# If symvals is a single item, it is an array else a list of sym bitmask pairs +proc twapi::_parse_symbolic_bitmask {syms symvals} { + if {[llength $symvals] == 1} { + upvar $symvals lookup + } else { + array set lookup $symvals + } + set bits 0 + foreach sym $syms { + if {[info exists lookup($sym)]} { + set bits [expr {$bits | $lookup($sym)}] + } else { + set bits [expr {$bits | $sym}] + } + } + return $bits +} + +# Return a list of symbols corresponding to a bitmask +proc twapi::_make_symbolic_bitmask {bits symvals {append_unknown 1}} { + if {[llength $symvals] == 1} { + upvar $symvals lookup + set map [array get lookup] + } else { + set map $symvals + } + set symbits 0 + set symmask [list ] + foreach {sym val} $map { + if {$bits & $val} { + set symbits [expr {$symbits | $val}] + lappend symmask $sym + } + } + + # Get rid of bits that mapped to symbols + set bits [expr {$bits & ~$symbits}] + # If any left over, add them + if {$bits && $append_unknown} { + lappend symmask $bits + } + return $symmask +} + +# Return a bitmask corresponding to a list of symbolic and integer values +# If symvals is a single item, it is an array else a list of sym bitmask pairs +# Ditto for switches - an array or flat list of switch boolean pairs +proc twapi::_switches_to_bitmask {switches symvals {bits 0}} { + if {[llength $symvals] == 1} { + upvar $symvals lookup + } else { + array set lookup $symvals + } + if {[llength $switches] == 1} { + upvar $switches swtable + } else { + array set swtable $switches + } + + foreach {switch bool} [array get swtable] { + if {$bool} { + set bits [expr {$bits | $lookup($switch)}] + } else { + set bits [expr {$bits & ~ $lookup($switch)}] + } + } + return $bits +} + +# Return a list of switche bool pairs corresponding to a bitmask +proc twapi::_bitmask_to_switches {bits symvals} { + if {[llength $symvals] == 1} { + upvar $symvals lookup + set map [array get lookup] + } else { + set map $symvals + } + set symbits 0 + set symmask [list ] + foreach {sym val} $map { + if {$bits & $val} { + set symbits [expr {$symbits | $val}] + lappend symmask $sym 1 + } else { + lappend symmask $sym 0 + } + } + + return $symmask +} + +# Make and return a keyed list +proc twapi::kl_create {args} { + if {[llength $args] & 1} { + error "No value specified for keyed list field [lindex $args end]. A keyed list must have an even number of elements." + } + return $args +} + +# Make a keyed list given fields and values +interp alias {} twapi::kl_create2 {} twapi::twine + +# Set a key value +proc twapi::kl_set {kl field newval} { + set i 0 + foreach {fld val} $kl { + if {[string equal $fld $field]} { + incr i + return [lreplace $kl $i $i $newval] + } + incr i 2 + } + lappend kl $field $newval + return $kl +} + +# Check if a field exists in the keyed list +proc twapi::kl_vget {kl field varname} { + upvar $varname var + return [expr {! [catch {set var [kl_get $kl $field]}]}] +} + +# Remote/unset a key value +proc twapi::kl_unset {kl field} { + array set arr $kl + unset -nocomplain arr($field) + return [array get arr] +} + +# Compare two keyed lists +proc twapi::kl_equal {kl_a kl_b} { + array set a $kl_a + foreach {kb valb} $kl_b { + if {[info exists a($kb)] && ($a($kb) == $valb)} { + unset a($kb) + } else { + return 0 + } + } + if {[array size a]} { + return 0 + } else { + return 1 + } +} + +# Return the field names in a keyed list in the same order that they +# occured +proc twapi::kl_fields {kl} { + set fields [list ] + foreach {fld val} $kl { + lappend fields $fld + } + return $fields +} + +# Returns a flat list of the $field fields from a list +# of keyed lists +proc twapi::kl_flatten {list_of_kl args} { + set result {} + foreach kl $list_of_kl { + foreach field $args { + lappend result [kl_get $kl $field] + } + } + return $result +} + + +# Return an array as a list of -index value pairs +proc twapi::_get_array_as_options {v_arr} { + upvar $v_arr arr + set result [list ] + foreach {index value} [array get arr] { + lappend result -$index $value + } + return $result +} + +# Parse a list of two integers or a x,y pair and return a list of two integers +# Generate exception on format error using msg +proc twapi::_parse_integer_pair {pair {msg "Invalid integer pair"}} { + if {[llength $pair] == 2} { + lassign $pair first second + if {[string is integer -strict $first] && + [string is integer -strict $second]} { + return [list $first $second] + } + } elseif {[regexp {^([[:digit:]]+),([[:digit:]]+)$} $pair dummy first second]} { + return [list $first $second] + } + + error "$msg: '$pair'. Should be a list of two integers or in the form 'x,y'" +} + + +# Convert file names by substituting \SystemRoot and \??\ sequences +proc twapi::_normalize_path {path} { + # Get rid of \??\ prefixes + regsub {^[\\/]\?\?[\\/](.*)} $path {\1} path + + # Replace leading \SystemRoot with real system root + if {[string match -nocase {[\\/]Systemroot*} $path] && + ([string index $path 11] in [list "" / \\])} { + return [file join [twapi::GetSystemWindowsDirectory] [string range $path 12 end]] + } else { + return [file normalize $path] + } +} + + +# Convert seconds to a list {Year Month Day Hour Min Sec Ms} +# (Ms will always be zero). +proc twapi::_seconds_to_timelist {secs {gmt 0}} { + # For each field, we need to trim the leading zeroes + set result [list ] + foreach x [clock format $secs -format "%Y %m %e %k %M %S 0" -gmt $gmt] { + lappend result [scan $x %d] + } + return $result +} + +# Convert local time list {Year Month Day Hour Min Sec Ms} to seconds +# (Ms field is ignored) +# TBD - fix this gmt issue - not clear whether caller expects gmt time +proc twapi::_timelist_to_seconds {timelist} { + return [clock scan [_timelist_to_timestring $timelist] -gmt false] +} + +# Convert local time list {Year Month Day Hour Min Sec Ms} to a time string +# (Ms field is ignored) +proc twapi::_timelist_to_timestring {timelist} { + if {[llength $timelist] < 6} { + error "Invalid time list format" + } + + return "[lindex $timelist 0]-[lindex $timelist 1]-[lindex $timelist 2] [lindex $timelist 3]:[lindex $timelist 4]:[lindex $timelist 5]" +} + +# Convert a time string to a time list +proc twapi::_timestring_to_timelist {timestring} { + return [_seconds_to_timelist [clock scan $timestring -gmt false]] +} + +# Parse raw memory like binary scan command +proc twapi::mem_binary_scan {mem off mem_sz args} { + uplevel [list binary scan [Twapi_ReadMemory 1 $mem $off $mem_sz]] $args +} + + +# Validate guid syntax +proc twapi::_validate_guid {guid} { + if {![Twapi_IsValidGUID $guid]} { + error "Invalid GUID syntax: '$guid'" + } +} + +# Validate uuid syntax +proc twapi::_validate_uuid {uuid} { + if {![regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$} $uuid]} { + error "Invalid UUID syntax: '$uuid'" + } +} + +# Extract a UCS-16 string from a binary. Cannot directly use +# encoding convertfrom because that will not stop at the terminating +# null. The UCS-16 assumed to be little endian. +proc twapi::_ucs16_binary_to_string {bin {off 0}} { + set bin [string range $bin $off end] + + # Find the terminating null. + set off [string first \0\0 $bin] + while {$off > 0 && ($off & 1)} { + # Offset off is odd and so crosses a char boundary, so not the + # terminating null. Step to the char boundary and start search again + incr off + set off [string first \0\0 $bin $off] + } + # off is offset of terminating UCS-16 null, or -1 if not found + if {$off < 0} { + # No terminator + return [encoding convertfrom unicode $bin] + } else { + return [encoding convertfrom unicode [string range $bin 0 $off-1]] + } +} + +# Extract a string from a binary. Cannot directly use +# encoding convertfrom because that will not stop at the terminating +# null. +proc twapi::_ascii_binary_to_string {bin {off 0}} { + set bin [string range $bin $off end] + + # Find the terminating null. + set off [string first \0 $bin] + + # off is offset of terminating null, or -1 if not found + if {$off < 0} { + # No terminator + return [encoding convertfrom ascii $bin] + } else { + return [encoding convertfrom ascii [string range $bin 0 $off-1]] + } +} + + +# Given a binary, return a GUID. The formatting is done as per the +# Windows StringFromGUID2 convention used by COM +proc twapi::_binary_to_guid {bin {off 0}} { + if {[binary scan $bin "@$off i s s H4 H12" g1 g2 g3 g4 g5] != 5} { + error "Invalid GUID binary" + } + + return [format "{%8.8X-%2.2hX-%2.2hX-%s}" $g1 $g2 $g3 [string toupper "$g4-$g5"]] +} + +# Given a guid string, return a GUID in binary form +proc twapi::_guid_to_binary {guid} { + _validate_guid $guid + lassign [split [string range $guid 1 end-1] -] g1 g2 g3 g4 g5 + return [binary format "i s s H4 H12" 0x$g1 0x$g2 0x$g3 $g4 $g5] +} + +# Return a guid from raw memory +proc twapi::_decode_mem_guid {mem {off 0}} { + return [_binary_to_guid [Twapi_ReadMemory 1 $mem $off 16]] +} + +# Convert a Windows registry value to Tcl form. mem is a raw +# memory object. off is the offset into the memory object to read. +# $type is a integer corresponding +# to the registry types +proc twapi::_decode_mem_registry_value {type mem len {off 0}} { + set type [expr {$type}]; # Convert hex etc. to decimal form + switch -exact -- $type { + 1 - + 2 { + return [list [expr {$type == 2 ? "expand_sz" : "sz"}] \ + [Twapi_ReadMemory 3 $mem $off $len 1]] + } + 7 { + # Collect strings until we come across an empty string + # Note two nulls right at the start will result in + # an empty list. Should it result in a list with + # one empty string element? Most code on the web treats + # it as the former so we do too. + set multi [list ] + while {1} { + set str [Twapi_ReadMemory 3 $mem $off -1] + set n [string length $str] + # Check for out of bounds. Cannot check for this before + # actually reading the string since we do not know size + # of the string. + if {($len != -1) && ($off+$n+1) > $len} { + error "Possible memory corruption: read memory beyond specified memory size." + } + if {$n == 0} { + return [list multi_sz $multi] + } + lappend multi $str + # Move offset by length of the string and terminating null + # (times 2 since unicode and we want byte offset) + incr off [expr {2*($n+1)}] + } + } + 4 { + if {$len < 4} { + error "Insufficient number of bytes to convert to integer." + } + return [list dword [Twapi_ReadMemory 0 $mem $off]] + } + 5 { + if {$len < 4} { + error "Insufficient number of bytes to convert to big-endian integer." + } + set type "dword_big_endian" + set scanfmt "I" + set len 4 + } + 11 { + if {$len < 8} { + error "Insufficient number of bytes to convert to wide integer." + } + set type "qword" + set scanfmt "w" + set len 8 + } + 0 { set type "none" } + 6 { set type "link" } + 8 { set type "resource_list" } + 3 { set type "binary" } + default { + error "Unsupported registry value type '$type'" + } + } + + set val [Twapi_ReadMemory 1 $mem $off $len] + if {[info exists scanfmt]} { + if {[binary scan $val $scanfmt val] != 1} { + error "Could not convert from binary value using scan format $scanfmt" + } + } + + return [list $type $val] +} + + +proc twapi::_log_timestamp {} { + return [clock format [clock seconds] -format "%a %T"] +} + + +# Helper for Net*Enum type functions taking a common set of arguments +proc twapi::_net_enum_helper {function args} { + if {[llength $args] == 1} { + set args [lindex $args 0] + } + + # -namelevel is used internally to indicate what level is to be used + # to retrieve names. -preargs and -postargs are used internally to + # add additional arguments at specific positions in the generic call. + array set opts [parseargs args { + {system.arg ""} + level.int + resume.int + filter.int + {namelevel.int 0} + {preargs.arg {}} + {postargs.arg {}} + {namefield.int 0} + fields.arg + } -maxleftover 0] + + if {[info exists opts(level)]} { + set level $opts(level) + if {! [info exists opts(fields)]} { + badargs! "Option -fields must be specified if -level is specified" + } + } else { + set level $opts(namelevel) + } + + # Note later we need to know if opts(resume) was specified so + # don't change this to just default -resume to 0 above + if {[info exists opts(resume)]} { + set resumehandle $opts(resume) + } else { + set resumehandle 0 + } + + set moredata 1 + set result {} + while {$moredata} { + if {[info exists opts(filter)]} { + lassign [$function $opts(system) {*}$opts(preargs) $level $opts(filter) {*}$opts(postargs) $resumehandle] moredata resumehandle totalentries entries + } else { + lassign [$function $opts(system) {*}$opts(preargs) $level {*}$opts(postargs) $resumehandle] moredata resumehandle totalentries entries + } + # If caller does not want all data in one lump stop here + if {[info exists opts(resume)]} { + if {[info exists opts(level)]} { + return [list $moredata $resumehandle $totalentries [list $opts(fields) $entries]] + } else { + # Return flat list of names + return [list $moredata $resumehandle $totalentries [lpick $entries $opts(namefield)]] + } + } + + lappend result {*}$entries + } + + # Return what we have. Format depend on caller options. + if {[info exists opts(level)]} { + return [list $opts(fields) $result] + } else { + return [lpick $result $opts(namefield)] + } +} + +# If we are not being sourced from a executable resource, need to +# source the remaining support files. In the former case, they are +# automatically combined into one so the sourcing is not needed. +if {![info exists twapi::twapi_base_rc_sourced]} { + apply {{filelist} { + set dir [file dirname [info script]] + foreach f $filelist { + uplevel #0 [list source [file join $dir $f]] + } + }} {base.tcl handle.tcl win.tcl adsi.tcl} +} + +# Used in various matcher callbacks to signify always include etc. +# TBD - document +proc twapi::true {args} { + return true +} + + +namespace eval twapi { + # Get a handle to ourselves. This handle never need be closed + variable my_process_handle [GetCurrentProcess] +} + +# Only used internally for test validation. +# NOT the same as export_public_commands +proc twapi::_get_public_commands {} { + variable exports; # Populated via pkgIndex.tcl + if {[info exists exports]} { + return [concat {*}[dict values $exports]] + } else { + set cmds {} + foreach cmd [lsearch -regexp -inline -all [info commands [namespace current]::*] {::twapi::[a-z].*}] { + lappend cmds [namespace tail $cmd] + } + return $cmds + } +} + +proc twapi::export_public_commands {} { + variable exports; # Populated via pkgIndex.tcl + if {[info exists exports]} { + # Only export commands under twapi (e.g. not metoo) + dict for {ns cmds} $exports { + if {[regexp {^::twapi($|::)} $ns]} { + uplevel #0 [list namespace eval $ns [list namespace export {*}$cmds] +] + } + } + } else { + set cmds {} + foreach cmd [lsearch -regexp -inline -all [info commands [namespace current]::*] {::twapi::[a-z].*}] { + lappend cmds [namespace tail $cmd] + } + namespace eval [namespace current] "namespace export {*}$cmds" + } +} + +proc twapi::import_commands {} { + export_public_commands + uplevel namespace import twapi::* +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/ui.tcl b/src/vendorlib_tcl8/twapi-5.0b1/ui.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/ui.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/ui.tcl index bfced989..07582e9b 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/ui.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/ui.tcl @@ -1,1430 +1,1430 @@ -# -# Copyright (c) 2003-2012 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# TBD - define a C function and way to implement window callback so -# that SetWindowLong(GWL_WNDPROC) can be implemente -# - - -# TBD - document the following class names -# SciCalc CALC.EXE -# CalWndMain CALENDAR.EXE -# Cardfile CARDFILE.EXE -# Clipboard CLIPBOARD.EXE -# Clock CLOCK.EXE -# CtlPanelClass CONTROL.EXE -# XLMain EXCEL.EXE -# Session MS-DOS.EXE -# Notepad NOTEPAD.EXE -# pbParent PBRUSH.EXE -# Pif PIFEDIT.EXE -# PrintManager PRINTMAN.EXE -# Progman PROGMAN.EXE (Windows Program Manager) -# Recorder RECORDER.EXE -# Reversi REVERSI.EXE -# #32770 SETUP.EXE -# Solitaire SOL.EXE -# Terminal TERMINAL.EXE -# WFS_Frame WINFILE.EXE -# MW_WINHELP WINHELP.EXE -# #32770 WINVER.EXE -# OpusApp WINWORD.EXE -# MSWRITE_MENU WRITE.EXE -# OMain Microsoft Access -# XLMAIN Microsoft Excel -# rctrl_renwnd32 Microsoft Outlook -# PP97FrameClass Microsoft PowerPoint -# OpusApp Microsoft Word - -namespace eval twapi { - struct POINT {LONG x; LONG y;} - struct RECT { LONG left; LONG top; LONG right; LONG bottom; } - struct WINDOWPLACEMENT { - UINT cbSize; - UINT flags; - UINT showCmd; - struct POINT ptMinPosition; - struct POINT ptMaxPosition; - struct RECT rcNormalPosition; - } -} - -proc twapi::get_window_placement {hwin} { - GetWindowPlacement $hwin [WINDOWPLACEMENT] -} - -# Set the focus to the given window -proc twapi::set_focus {hwin} { - return [_return_window [_attach_hwin_and_eval $hwin {SetFocus $hwin}]] -} - -# Enumerate toplevel windows -proc twapi::get_toplevel_windows {args} { - - array set opts [parseargs args { - {pid.arg} - {pids.arg} - }] - - set toplevels [twapi::EnumWindows] - - if {[info exists opts(pids)]} { - set pids $opts(pids) - } elseif {[info exists opts(pid)]} { - set pids [list $opts(pid)] - } else { - return $toplevels - } - - set process_toplevels [list ] - foreach toplevel $toplevels { - set pid [get_window_process $toplevel] - if {[lsearch -exact -integer $pids $pid] >= 0} { - lappend process_toplevels $toplevel - } - } - - return $process_toplevels -} - - -# Find a window based on given criteria -proc twapi::find_windows {args} { - # TBD - would incorporating FindWindowEx be faster - # TBD - apparently on Windows 8, you need to use FindWindowEx to - # get non-toplevel Metro windows - - array set opts [parseargs args { - ancestor.arg - caption.bool - child.bool - class.arg - {match.arg string {string glob regexp}} - maximize.bool - maximizebox.bool - messageonlywindow.bool - minimize.bool - minimizebox.bool - overlapped.bool - pids.arg - popup.bool - single - style.arg - text.arg - toplevel.bool - visible.bool - } -maxleftover 0] - - if {[info exists opts(style)] - ||[info exists opts(overlapped)] - || [info exists opts(popup)] - || [info exists opts(child)] - || [info exists opts(minimizebox)] - || [info exists opts(maximizebox)] - || [info exists opts(minimize)] - || [info exists opts(maximize)] - || [info exists opts(visible)] - || [info exists opts(caption)] - } { - set need_style 1 - } else { - set need_style 0 - } - - # Figure out the type of match if -text specified - if {[info exists opts(text)]} { - switch -exact -- $opts(match) { - glob { - set text_compare [list string match -nocase $opts(text)] - } - string { - set text_compare [list string equal -nocase $opts(text)] - } - regexp { - set text_compare [list regexp -nocase $opts(text)] - } - default { - error "Invalid value '$opts(match)' specified for -match option" - } - } - } - - # First build a list of potential candidates. There are two main - # categories we have to look at - ordinary windows and message-only - # windows. Normally, both are included. However, if -messageonlywindow - # is specified, then we only include the former or the latter - # depending on the value of the -messageonlywindow option - - set include_ordinary true - if {[info exists opts(messageonlywindow)]} { - if {$opts(messageonlywindow)} { - if {[info exists opts(toplevel)] && $opts(toplevel)} { - error "Options -toplevel and -messageonlywindow cannot be both specified as true" - } - if {[info exists opts(text)]} { - # See bug 3213001 - error "Option -text cannot be specified if -messageonlywindow is specified as true" - } - if {[info exists opts(ancestor)]} { - error "Option -ancestor cannot be specified if -messageonlywindow is specified as true" - } - set include_ordinary false - } - set include_messageonly $opts(messageonlywindow) - } else { - # -messageonlywindow not specified at all. Only include - # messageonly windows if toplevel is not specified as true - # Also, if opts(text) is specified, will never match messageonly - # so set it to false to we do not pick up messageonly windows - # (which will hang if we go looking for them with -text : see - # bug 3213001). - if {([info exists opts(toplevel)] && $opts(toplevel)) || - [info exists opts(ancestor)] || [info exists opts(text)] - } { - set include_messageonly false - } else { - set include_messageonly true - } - } - - if {$include_messageonly} { - set class "" - if {[info exists opts(class)]} { - set class $opts(class) - } - set text "" - if {[info exists opts(text)] && - $opts(match) eq "string"} { - set text $opts(text) - } - set messageonly_candidates [_get_message_only_windows] - } else { - set messageonly_candidates [list ] - } - - if {$include_ordinary} { - # TBD - make use of FindWindowEx function if possible - - # If only interested in toplevels, just start from there - if {[info exists opts(toplevel)]} { - if {$opts(toplevel)} { - set ordinary_candidates [get_toplevel_windows] - if {[info exists opts(ancestor)]} { - error "Option -ancestor may not be specified together with -toplevel true" - } - } else { - # We do not want windows to be toplevels. Remember list - # so we can check below. - set toplevels [get_toplevel_windows] - } - } - - if {![info exists ordinary_candidates]} { - # -toplevel TRuE not specified. - # If ancestor is not specified, we start from the desktop window - # Note ancestor, if specified, is never included in the search - if {[info exists opts(ancestor)] && ![pointer_null? $opts(ancestor)]} { - set ordinary_candidates [get_descendent_windows $opts(ancestor)] - } else { - set desktop [get_desktop_window] - set ordinary_candidates [concat [list $desktop] [get_descendent_windows $desktop]] - } - } - } else { - set ordinary_candidates [list ] - } - - - set matches [list ] - foreach win [concat $messageonly_candidates $ordinary_candidates] { - # Why are we not using a trap here instead of catch ? TBD - set status [catch { - if {[info exists toplevels]} { - # We do NOT want toplevels - if {[lsearch -exact $toplevels $win] >= 0} { - # This is toplevel, which we don't want - continue - } - } - - # TBD - what is the right order to check from a performance - # point of view - - if {$need_style} { - set win_styles [get_window_style $win] - set win_style [lindex $win_styles 0] - set win_exstyle [lindex $win_styles 1] - set win_styles [lrange $win_styles 2 end] - } - - if {[info exists opts(style)] && [llength $opts(style)]} { - lassign $opts(style) style exstyle - if {[string length $style] && ($style != $win_style)} continue - if {[string length $exstyle] && ($exstyle != $win_exstyle)} continue - } - - set match 1 - foreach opt {visible overlapped popup child minimizebox - maximizebox minimize maximize caption - } { - if {[info exists opts($opt)]} { - if {(! $opts($opt)) == ([lsearch -exact $win_styles $opt] >= 0)} { - set match 0 - break - } - } - } - if {! $match} continue - - # TBD - should we use get_window_class or get_window_real_class - if {[info exists opts(class)] && - [string compare -nocase $opts(class) [get_window_class $win]]} { - continue - } - - if {[info exists opts(pids)]} { - set pid [get_window_process $win] - if {[lsearch -exact -integer $opts(pids) $pid] < 0} continue - } - - if {[info exists opts(text)]} { - set text [get_window_text $win] - if {![eval $text_compare [list [get_window_text $win]]]} continue - } - # Matches all criteria. If we only want one, return it, else - # add to match list - if {$opts(single)} { - return $win - } - lappend matches $win - } result ] - - switch -exact -- $status { - 0 { - # No error, just keep going - } - 1 { - # Error, see if error code is no window and if so, ignore - lassign $::errorCode subsystem code msg - if {$subsystem == "TWAPI_WIN32"} { - # Window has disappeared so just do not include it - # Cannot just actual code since many different codes - # might be returned in this case - } else { - error $result $::errorInfo $::errorCode - } - } - 2 { - return $result; # Block executed a return - } - 3 { - break; # Block executed a break - } - 4 { - continue; # Block executed a continue - } - } - } - - return $matches - -} - - -# Return all descendent windows -proc twapi::get_descendent_windows {parent_hwin} { - return [EnumChildWindows $parent_hwin] -} - -# Return the parent window -proc twapi::get_parent_window {hwin} { - # Note - we use GetAncestor and not GetParent because the latter - # will return the owner in the case of a toplevel window - # 1 -> GA_PARENT -> 1 - return [_return_window [GetAncestor $hwin 1]] -} - -# Return owner window -proc twapi::get_owner_window {hwin} { - # GW_OWNER -> 4 - return [_return_window [twapi::GetWindow $hwin 4]] -} - -# Return immediate children of a window (not all children) -proc twapi::get_child_windows {hwin} { - set children [list ] - # TBD - maybe get_first_child/get_next_child would be more efficient - foreach w [get_descendent_windows $hwin] { - if {[_same_window $hwin [get_parent_window $w]]} { - lappend children $w - } - } - return $children -} - -# Return first child in z-order -proc twapi::get_first_child {hwin} { - # GW_CHILD -> 5 - return [_return_window [twapi::GetWindow $hwin 5]] -} - - -# Return the next sibling window in z-order -proc twapi::get_next_sibling_window {hwin} { - # GW_HWNDNEXT -> 2 - return [_return_window [twapi::GetWindow $hwin 2]] -} - -# Return the previous sibling window in z-order -proc twapi::get_prev_sibling_window {hwin} { - # GW_HWNDPREV -> 3 - return [_return_window [twapi::GetWindow $hwin 3]] -} - -# Return the sibling window that is highest in z-order -proc twapi::get_first_sibling_window {hwin} { - # GW_HWNDFIRST -> 0 - return [_return_window [twapi::GetWindow $hwin 0]] -} - -# Return the sibling window that is lowest in z-order -proc twapi::get_last_sibling_window {hwin} { - # GW_HWNDLAST -> 1 - return [_return_window [twapi::GetWindow $hwin 1]] -} - -# Return the desktop window -proc twapi::get_desktop_window {} { - return [_return_window [twapi::GetDesktopWindow]] -} - -# Return the shell window -proc twapi::get_shell_window {} { - return [_return_window [twapi::GetShellWindow]] -} - -# Return the pid for a window -proc twapi::get_window_process {hwin} { - return [lindex [GetWindowThreadProcessId $hwin] 1] -} - -# Return the thread for a window -proc twapi::get_window_thread {hwin} { - return [lindex [GetWindowThreadProcessId $hwin] 0] -} - -# Return the style of the window. Returns a list of two integers -# the first contains the style bits, the second the extended style bits -proc twapi::get_window_style {hwin} { - # GWL_STYLE -> -16, GWL_EXSTYLE -20 - set style [GetWindowLongPtr $hwin -16] - set exstyle [GetWindowLongPtr $hwin -20] - return [concat [list $style $exstyle] [_style_mask_to_symbols $style $exstyle]] -} - - -# Set the style of the window. Returns a list of two integers -# the first contains the original style bits, the second the -# original extended style bits -proc twapi::set_window_style {hwin style exstyle} { - # GWL_STYLE -> -16, GWL_EXSTYLE -20 - set style [SetWindowLongPtr $hwin -16 $style] - set exstyle [SetWindowLongPtr $hwin -20 $exstyle] - - redraw_window_frame $hwin - return -} - - -# Return the class of the window -proc twapi::get_window_class {hwin} { - return [GetClassName $hwin] -} - -# Return the real class of the window -proc twapi::get_window_real_class {hwin} { - return [RealGetWindowClass $hwin] -} - -# Return the identifier corrpsonding to the application instance -proc twapi::get_window_application {hwin} { - # GWL_HINSTANCE -> -6 - return [GetWindowLongPtr $hwin -6] -} - -# Return the window id (this is different from the handle!) -proc twapi::get_window_id {hwin} { - # GWL_ID -> -12 - return [GetWindowLongPtr $hwin -12] -} - -# Return the user data associated with a window -proc twapi::get_window_userdata {hwin} { - # GWL_USERDATA -> -21 - return [GetWindowLongPtr $hwin -21] -} - - -# Get the foreground window -proc twapi::get_foreground_window {} { - return [_return_window [GetForegroundWindow]] -} - -# Set the foreground window - returns 1/0 on success/fail -proc twapi::set_foreground_window {hwin} { - return [SetForegroundWindow $hwin] -} - - -# Activate a window - this is only brought the foreground if its application -# is in the foreground -proc twapi::set_active_window_for_thread {hwin} { - return [_return_window [_attach_hwin_and_eval $hwin {SetActiveWindow $hwin}]] -} - -# Get active window for an application -proc twapi::get_active_window_for_thread {tid} { - return [_return_window [_get_gui_thread_info $tid hwndActive]] -} - - -# Get focus window for an application -proc twapi::get_focus_window_for_thread {tid} { - return [_get_gui_thread_info $tid hwndFocus] -} - -# Get active window for current thread -proc twapi::get_active_window_for_current_thread {} { - return [_return_window [GetActiveWindow]] -} - -# Update the frame - needs to be called after setting certain style bits -proc twapi::redraw_window_frame {hwin} { - # 0x4037 -> SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | - # SWP_NOMOVE | SWP_NOSIZE | - # SWP_NOZORDER | SWP_FRAMECHANGED - SetWindowPos $hwin 0 0 0 0 0 0x4037 -} - -# Redraw the window -proc twapi::redraw_window {hwin {opt ""}} { - if {[string length $opt]} { - if {[string compare $opt "-force"]} { - error "Invalid option '$opt'" - } - invalidate_screen_region -hwin $hwin -rect [list ] -bgerase - } - - UpdateWindow $hwin - return -} - -# Set the window position -proc twapi::move_window {hwin x y args} { - array set opts [parseargs args { - {sync} - }] - - # Not using MoveWindow because that will require knowing the width - # and height (or retrieving it) - # 0x15 -> SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER - set flags 0x15 - if {! $opts(sync)} { - setbits flags 0x4000; # SWP_ASYNCWINDOWPOS - } - SetWindowPos $hwin 0 $x $y 0 0 $flags -} - -# Resize window -proc twapi::resize_window {hwin w h args} { - array set opts [parseargs args { - {sync} - }] - - - # Not using MoveWindow because that will require knowing the x and y pos - # (or retrieving them) - # 0x16 -> SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER - set flags 0x16 - if {! $opts(sync)} { - setbits flags 0x4000; # SWP_ASYNCWINDOWPOS - } - SetWindowPos $hwin 0 0 0 $w $h $flags -} - -# Sets the window's z-order position -# pos is either window handle or a symbol -proc twapi::set_window_zorder {hwin pos} { - switch -exact -- $pos { - top { - set pos [pointer_from_address 0 HWND]; #HWND_TOP - } - bottom { - set pos [pointer_from_address 1 HWND]; #HWND_BOTTOM - } - toplayer { - set pos [pointer_from_address -1 HWND]; #HWND_TOPMOST - } - bottomlayer { - set pos [pointer_from_address -2 HWND]; #HWND_NOTOPMOST - } - } - - # 0x4013 -> SWP_ASYNCWINDOWPOS|SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE - SetWindowPos $hwin $pos 0 0 0 0 0x4013 -} - - -# Show the given window. Returns 1 if window was previously visible, else 0 -proc twapi::show_window {hwin args} { - array set opts [parseargs args {sync activate normal startup}] - - set show 0 - if {$opts(startup)} { - set show 10; #SW_SHOWDEFAULT - } else { - if {$opts(activate)} { - if {$opts(normal)} { - set show 1; #SW_SHOWNORMAL - } else { - set show 5; #SW_SHOW - } - } else { - if {$opts(normal)} { - set show 4; #SW_SHOWNOACTIVATE - } else { - set show 8; #SW_SHOWNA - } - } - } - - _show_window $hwin $show $opts(sync) -} - -# Hide the given window. Returns 1 if window was previously visible, else 0 -proc twapi::hide_window {hwin args} { - array set opts [parseargs args {sync}] - _show_window $hwin 0 $opts(sync); # 0 -> SW_HIDE -} - -# Restore the given window. Returns 1 if window was previously visible, else 0 -proc twapi::restore_window {hwin args} { - array set opts [parseargs args {sync activate}] - if {$opts(activate)} { - _show_window $hwin 9 $opts(sync); # 9 -> SW_RESTORE - } else { - OpenIcon $hwin - } -} - -# Maximize the given window. Returns 1 if window was previously visible, else 0 -proc twapi::maximize_window {hwin args} { - array set opts [parseargs args {sync}] - _show_window $hwin 3 $opts(sync); # 3 -> SW_SHOWMAXIMIZED -} - - -# Minimize the given window. Returns 1 if window was previously visible, else 0 -proc twapi::minimize_window {hwin args} { - array set opts [parseargs args {sync activate shownext}] - - # TBD - when should we use SW_FORCEMINIMIZE ? - # TBD - do we need to attach to the window's thread? - # TBD - when should we use CloseWindow instead? - - if $opts(activate) { - set show 2; #SW_SHOWMINIMIZED - } else { - if {$opts(shownext)} { - set show 6; #SW_MINIMIZE - } else { - set show 7; #SW_SHOWMINNOACTIVE - } - } - - _show_window $hwin $show $opts(sync) -} - - -# Hides popup windows -proc twapi::hide_owned_popups {hwin} { - ShowOwnedPopups $hwin 0 -} - -# Show hidden popup windows -proc twapi::show_owned_popups {hwin} { - ShowOwnedPopups $hwin 1 -} - -# Close a window -proc twapi::close_window {hwin args} { - array set opts [parseargs args { - block - {wait.int 10} - } -maxleftover 0] - - if {0} { - Cannot close Explorer windows using SendMessage* - if {$opts(block)} { - set block 3; #SMTO_BLOCK|SMTO_ABORTIFHUNG - } else { - set block 2; #SMTO_NORMAL|SMTO_ABORTIFHUNG - } - - # WM_CLOSE -> 0x10 - if {[catch {SendMessageTimeout $hwin 0x10 0 0 $block $opts(wait)} msg]} { - # Do no treat timeout as an error - set erCode $::errorCode - set erInfo $::errorInfo - if {[lindex $erCode 0] != "TWAPI_WIN32" || - ([lindex $erCode 1] != 0 && [lindex $erCode 1] != 1460)} { - error $msg $erInfo $erCode - } - } - } else { - # Implement using PostMessage since that allows closing of - # Explorer windows - - # Note - opts(block) is ignored here - - # 0x10 -> WM_CLOSE - PostMessage $hwin 0x10 0 0 - if {$opts(wait)} { - wait [list ::twapi::window_exists $hwin] 0 $opts(wait) - } - } - return [twapi::window_exists $hwin] -} - -# CHeck if window is minimized -proc twapi::window_minimized {hwin} { - return [IsIconic $hwin] -} - -# CHeck if window is maximized -proc twapi::window_maximized {hwin} { - return [IsZoomed $hwin] -} - -# Check if window is visible -proc twapi::window_visible {hwin} { - return [IsWindowVisible $hwin] -} - -# Check if a window exists -proc twapi::window_exists {hwin} { - return [IsWindow $hwin] -} - -# CHeck if window input is enabled -proc twapi::window_unicode_enabled {hwin} { - return [IsWindowUnicode $hwin] -} - -# Check if child is a child of parent -proc twapi::window_is_child {parent child} { - return [IsChild $parent $child] -} - -# Flash the given window -proc twapi::flash_window_caption {hwin args} { - array set opts [parseargs args {toggle}] - - return [FlashWindow $hwin $opts(toggle)] -} - -# FlashWindow not in binary any more, emulate it -proc twapi::FlashWindow {hwin toggle} { - FlashWindowEx [list $hwin 1 $toggle 0] -} - -# Flash the given window and/or the taskbar icon -proc twapi::flash_window {hwin args} { - array set opts [parseargs args { - period.int - count.int - nocaption - notaskbar - start - stop - untilforeground - } -maxleftover 0 -nulldefault] - - set flags 0 - - if {! $opts(stop)} { - # Flash title bar? - if {! $opts(nocaption)} { - incr flags 1; # FLASHW_CAPTION - } - - # Flash taskbar icon ? - if {! $opts(notaskbar)} { - incr flags 2; # FLASHW_TRAY - } - - # Continuous modes ? - if {$opts(untilforeground)} { - # Continuous until foreground window - # NOTE : FLASHW_TIMERNOFG is no implemented because it seems to be - # broken - it only flashes once, at least on Windows XP. Keep - # it in case other platforms work correctly. - incr flags 0xc; # FLASHW_TIMERNOFG - } elseif {$opts(start)} { - # Continuous until stopped - incr flags 4; # FLASHW_TIMER - } elseif {$opts(count) == 0} { - set opts(count) 1 - } - } - - return [FlashWindowEx [list $hwin $flags $opts(count) $opts(period)]] -} - - -# Show/hide window caption buttons. hwin must be a toplevel -proc twapi::configure_window_titlebar {hwin args} { - - array set opts [parseargs args { - visible.bool - sysmenu.bool - minimizebox.bool - maximizebox.bool - contexthelp.bool - } -maxleftover 0] - - # Get the current style setting - lassign [get_window_style $hwin] style exstyle - - # See if each option is specified. Else use current setting - # 0x00080000 -> WS_SYSMENU - # 0x00020000 -> WS_MINIMIZEBOX - # 0x00010000 -> WS_MAXIMIZEBOX - # 0x00C00000 -> WS_CAPTION - foreach {opt def} { - sysmenu 0x00080000 - minimizebox 0x00020000 - maximizebox 0x00010000 - visible 0x00C00000 - } { - if {[info exists opts($opt)]} { - set $opt [expr {$opts($opt) ? $def : 0}] - } else { - set $opt [expr {$style & $def}] - } - } - - # Ditto for extended style and context help - if {[info exists opts(contexthelp)]} { - # WS_EX_CONTEXTHELP -> 0x00000400 - set contexthelp [expr {$opts(contexthelp) ? 0x00000400 : 0}] - } else { - set contexthelp [expr {$exstyle & 0x00000400}] - } - - # The min/max/help buttons all depend on sysmenu being set. - if {($minimizebox || $maximizebox || $contexthelp) && ! $sysmenu} { - # Don't bother raising error, since the underlying API allows it - #error "Cannot enable minimize, maximize and context help buttons unless system menu is present" - } - - # Reset existing sysmenu,minimizebox,maximizebox,caption - set style [expr {$style & 0xff34ffff}] - ; # Add back new settings - set style [expr {$style | $sysmenu | $minimizebox | $maximizebox | $visible}] - - # Reset contexthelp and add new setting back - set exstyle [expr {$exstyle & 0xfffffbff}] - set exstyle [expr {$exstyle | $contexthelp}] - - set_window_style $hwin $style $exstyle -} - -# Arrange window icons -proc twapi::arrange_icons {{hwin ""}} { - if {$hwin == ""} { - set hwin [get_desktop_window] - } - ArrangeIconicWindows $hwin -} - -# Get the window text/caption -proc twapi::get_window_text {hwin} { - # TBD - see https://devblogs.microsoft.com/oldnewthing/20030821-00/?p=42833 - twapi::GetWindowText $hwin -} - -# Set the window text/caption -proc twapi::set_window_text {hwin text} { - twapi::SetWindowText $hwin $text -} - -# Get size of client area -proc twapi::get_window_client_area_size {hwin} { - return [lrange [GetClientRect $hwin] 2 3] -} - -# Get window coordinates -proc twapi::get_window_coordinates {hwin} { - return [GetWindowRect $hwin] -} - -# Get the window under the point -proc twapi::get_window_at_location {x y} { - return [WindowFromPoint [list $x $y]] -} - -# Marks a screen region as invalid forcing a redraw -proc twapi::invalidate_screen_region {args} { - array set opts [parseargs args { - {hwin.arg 0} - rect.arg - bgerase - } -nulldefault -maxleftover 0] - - InvalidateRect $opts(hwin) $opts(rect) $opts(bgerase) -} - -# Get the caret blink time -proc twapi::get_caret_blink_time {} { - return [GetCaretBlinkTime] -} - -# Set the caret blink time -proc twapi::set_caret_blink_time {ms} { - return [SetCaretBlinkTime $ms] -} - -# Hide the caret -proc twapi::hide_caret {} { - HideCaret 0 -} - -# Show the caret -proc twapi::show_caret {} { - ShowCaret 0 -} - -# Get the caret position -proc twapi::get_caret_location {} { - return [GetCaretPos] -} - -# Get the caret position -proc twapi::set_caret_location {point} { - return [SetCaretPos [lindex $point 0] [lindex $point 1]] -} - - -# Get display size -proc twapi::get_display_size {} { - return [lrange [get_window_coordinates [get_desktop_window]] 2 3] -} - - -# Get path to the desktop wallpaper -interp alias {} twapi::get_desktop_wallpaper {} twapi::get_system_parameters_info SPI_GETDESKWALLPAPER - - -# Set desktop wallpaper -proc twapi::set_desktop_wallpaper {path args} { - - array set opts [parseargs args { - persist - }] - - if {$opts(persist)} { - set flags 3; # Notify all windows + persist - } else { - set flags 2; # Notify all windows - } - - if {$path == "default"} { - SystemParametersInfo 0x14 0 NULL 0 - return - } - - if {$path == "none"} { - set path "" - } - - set mem_size [expr {2 * ([string length $path] + 1)}] - set mem [malloc $mem_size] - trap { - twapi::Twapi_WriteMemory 3 $mem 0 $mem_size $path - SystemParametersInfo 0x14 0 $mem $flags - } finally { - free $mem - } -} - -# Get desktop work area -interp alias {} twapi::get_desktop_workarea {} twapi::get_system_parameters_info SPI_GETWORKAREA - - - -# Get the color depth of the display -proc twapi::get_color_depth {{hwin 0}} { - set h [GetDC $hwin] - trap { - return [GetDeviceCaps $h 12] - } finally { - ReleaseDC $hwin $h - } -} - - -# Enumerate the display adapters in a system -proc twapi::get_display_devices {} { - set devs [list ] - for {set i 0} {true} {incr i} { - trap { - set dev [EnumDisplayDevices "" $i 0] - } onerror {TWAPI_WIN32} { - # We don't check for a specific error since experimentation - # shows the error code returned at the end of enumeration - # is not fixed - can be 2, 18, 87 and maybe others - break - } - lappend devs [_format_display_device $dev] - } - - return $devs -} - -# Enumerate the display monitors for an display device -proc twapi::get_display_monitors {args} { - array set opts [parseargs args { - device.arg - activeonly - } -maxleftover 0] - - if {[info exists opts(device)]} { - set devs [list $opts(device)] - } else { - set devs [list ] - foreach dev [get_display_devices] { - lappend devs [kl_get $dev -name] - } - } - - set monitors [list ] - foreach dev $devs { - for {set i 0} {true} {incr i} { - trap { - set monitor [EnumDisplayDevices $dev $i 0] - } onerror {} { - # We don't check for a specific error since experimentation - # shows the error code returned at the end of enumeration - # is not fixed - can be 2, 18, 87 and maybe others - break - } - if {(! $opts(activeonly)) || - ([lindex $monitor 2] & 1)} { - lappend monitors [_format_display_monitor $monitor] - } - } - } - - return $monitors -} - -# Return the monitor corresponding to a window -proc twapi::get_display_monitor_from_window {hwin args} { - array set opts [parseargs args { - default.arg - } -maxleftover 0] - - # hwin may be a window id or a Tk window. On error we assume it is - # a window id - catch { - set hwin [pointer_from_address [winfo id $hwin] HWND] - } - - set flags 0 - if {[info exists opts(default)]} { - switch -exact -- $opts(default) { - primary { set flags 1 } - nearest { set flags 2 } - default { error "Invalid value '$opts(default)' for -default option" } - } - } - - trap { - return [MonitorFromWindow $hwin $flags] - } onerror {TWAPI_WIN32 0} { - win32_error 1461 "Window does not map to a monitor." - } -} - -# Return the monitor corresponding to a screen cocordinates -proc twapi::get_display_monitor_from_point {x y args} { - array set opts [parseargs args { - default.arg - } -maxleftover 0] - - set flags 0 - if {[info exists opts(default)]} { - switch -exact -- $opts(default) { - primary { set flags 1 } - nearest { set flags 2 } - default { error "Invalid value '$opts(default)' for -default option" } - } - } - - trap { - return [MonitorFromPoint [list $x $y] $flags] - } onerror {TWAPI_WIN32 0} { - win32_error 1461 "Virtual screen coordinates ($x,$y) do not map to a monitor." - } -} - - -# Return the monitor corresponding to a screen rectangle -proc twapi::get_display_monitor_from_rect {rect args} { - array set opts [parseargs args { - default.arg - } -maxleftover 0] - - set flags 0 - if {[info exists opts(default)]} { - switch -exact -- $opts(default) { - primary { set flags 1 } - nearest { set flags 2 } - default { error "Invalid value '$opts(default)' for -default option" } - } - } - - trap { - return [MonitorFromRect $rect $flags] - } onerror {TWAPI_WIN32 0} { - win32_error 1461 "Virtual screen rectangle <[join $rect ,]> does not map to a monitor." - } -} - -proc twapi::get_display_monitor_info {hmon} { - return [_format_monitor_info [GetMonitorInfo $hmon]] -} - -proc twapi::get_multiple_display_monitor_info {} { - set result [list ] - foreach elem [EnumDisplayMonitors NULL ""] { - lappend result [get_display_monitor_info [lindex $elem 0]] - } - return $result -} - - -proc twapi::tkpath_to_hwnd {tkpath} { - return [cast_handle [winfo id $tkpath] HWND] -} - -# TBD - document -proc twapi::high_contrast_on {} { - set hc [lindex [get_system_parameters_info SPI_GETHIGHCONTRAST] 1] - return [expr {$hc & 1}] -} - -################################################################ -# Utility routines - -# Helper function to wrap GetGUIThreadInfo -# Returns the value of the given fields. If a single field is requested, -# returns it as a scalar else returns a flat list of FIELD VALUE pairs -proc twapi::_get_gui_thread_info {tid args} { - array set gtinfo [GetGUIThreadInfo $tid] - set result [list ] - foreach field $args { - set value $gtinfo($field) - switch -exact -- $field { - cbSize { } - rcCaret { - set value [list $value(left) \ - $value(top) \ - $value(right) \ - $value(bottom)] - } - } - lappend result $value - } - - if {[llength $args] == 1} { - return [lindex $result 0] - } else { - return $result - } -} - - -# if $hwin corresponds to a null window handle, returns an empty string -proc twapi::_return_window {hwin} { - if {[pointer_null? $hwin HWND]} { - return $twapi::null_hwin - } - return $hwin -} - -# Return 1 if same window -proc twapi::_same_window {hwin1 hwin2} { - # If either is a empty/null handle, no match, even if both empty/null - if {[string length $hwin1] == 0 || [string length $hwin2] == 0} { - return 0 - } - if {[pointer_null? $hwin1] || [pointer_null? $hwin2]} { - return 0 - } - - # Need integer compare - return [pointer_equal? $hwin1 $hwin2] -} - -# Helper function for showing/hiding windows -proc twapi::_show_window {hwin cmd {wait 0}} { - # If either our thread owns the window or we want to wait for it to - # process the command, use the synchrnous form of the function - if {$wait || ([get_window_thread $hwin] == [GetCurrentThreadId])} { - ShowWindow $hwin $cmd - } else { - ShowWindowAsync $hwin $cmd - } -} - - - -# Map style bits to a style symbol list -proc twapi::_style_mask_to_symbols {style exstyle} { - set attrs [list ] - if {$style & 0x80000000} { - lappend attrs popup - if {$style & 0x00020000} { lappend attrs group } - if {$style & 0x00010000} { lappend attrs tabstop } - } else { - if {$style & 0x40000000} { - lappend attrs child - } else { - lappend attrs overlapped - } - if {$style & 0x00020000} { lappend attrs minimizebox } - if {$style & 0x00010000} { lappend attrs maximizebox } - } - - # Note WS_BORDER, WS_DLGFRAME and WS_CAPTION use same bits - if {$style & 0x00C00000} { - lappend attrs caption - } else { - if {$style & 0x00800000} { lappend attrs border } - if {$style & 0x00400000} { lappend attrs dlgframe } - } - - foreach {sym mask} { - minimize 0x20000000 - visible 0x10000000 - disabled 0x08000000 - clipsiblings 0x04000000 - clipchildren 0x02000000 - maximize 0x01000000 - vscroll 0x00200000 - hscroll 0x00100000 - sysmenu 0x00080000 - thickframe 0x00040000 - } { - if {$style & $mask} { - lappend attrs $sym - } - } - - if {$exstyle & 0x00001000} { - lappend attrs right - } else { - lappend attrs left - } - if {$exstyle & 0x00002000} { - lappend attrs rtlreading - } else { - lappend attrs ltrreading - } - if {$exstyle & 0x00004000} { - lappend attrs leftscrollbar - } else { - lappend attrs rightscrollbar - } - - foreach {sym mask} { - dlgmodalframe 0x00000001 - noparentnotify 0x00000004 - topmost 0x00000008 - acceptfiles 0x00000010 - transparent 0x00000020 - mdichild 0x00000040 - toolwindow 0x00000080 - windowedge 0x00000100 - clientedge 0x00000200 - contexthelp 0x00000400 - controlparent 0x00010000 - staticedge 0x00020000 - appwindow 0x00040000 - } { - if {$exstyle & $mask} { - lappend attrs $sym - } - } - - return $attrs -} - - -# Test proc for displaying all colors for a class -proc twapi::_show_theme_colors {class part {state ""}} { - set w [toplevel .themetest$class$part$state] - - set h [OpenThemeData [tkpath_to_hwnd $w] $class] - wm title $w "$class Colors" - - label $w.title -text "$class, $part, $state" -bg white - grid $w.title - - - if {![string is integer -strict $part]} { - set part [TwapiGetThemeDefine $part] - } - - if {![string is integer -strict $state]} { - set state [TwapiGetThemeDefine $state] - } - - foreach x {BORDERCOLOR FILLCOLOR TEXTCOLOR EDGELIGHTCOLOR EDGESHADOWCOLOR EDGEFILLCOLOR TRANSPARENTCOLOR GRADIENTCOLOR1 GRADIENTCOLOR2 GRADIENTCOLOR3 GRADIENTCOLOR4 GRADIENTCOLOR5 SHADOWCOLOR GLOWCOLOR TEXTBORDERCOLOR TEXTSHADOWCOLOR GLYPHTEXTCOLOR FILLCOLORHINT BORDERCOLORHINT ACCENTCOLORHINT BLENDCOLOR} { - set prop [TwapiGetThemeDefine TMT_$x] - if {![catch {GetThemeColor $h $part $state $prop} color]} { - label $w.l-$x -text $x - label $w.c-$x -text $color -bg $color - grid $w.l-$x $w.c-$x - } else { - label $w.l-$x -text $x - label $w.c-$x -text "Not defined" - grid $w.l-$x $w.c-$x - } - } - CloseThemeData $h -} - -# Test proc for displaying all sys colors for a class -# class might be "WINDOW" -proc twapi::_show_theme_syscolors {class} { - destroy .themetest$class - set w [toplevel .themetest$class] - - set h [OpenThemeData [tkpath_to_hwnd $w] $class] - wm title $w "$class SysColors" - - label $w.title -text "$class" -bg white - grid $w.title - - - - for {set x 0} {$x <= 30} {incr x} { - if {![catch {GetThemeSysColor $h $x} color]} { - set color #[format %6.6x $color] - label $w.l-$x -text $x - label $w.c-$x -text $color -bg $color - grid $w.l-$x $w.c-$x - } else { - label $w.l-$x -text $x - label $w.c-$x -text "Not defined" - grid $w.l-$x $w.c-$x - } - } - CloseThemeData $h -} - -# Test proc for displaying all fonts for a class -proc twapi::_show_theme_fonts {class part {state ""}} { - set w [toplevel .themetest$class$part$state] - - set h [OpenThemeData [tkpath_to_hwnd $w] $class] - wm title $w "$class fonts" - - label $w.title -text "$class, $part, $state" -bg white - grid $w.title - - - set part [TwapiGetThemeDefine $part] - set state [TwapiGetThemeDefine $state] - - foreach x {GLYPHTYPE FONT} { - set prop [TwapiGetThemeDefine TMT_$x] - if {![catch {GetThemeFont $h NULL $part $state $prop} font]} { - label $w.l-$x -text $x - label $w.c-$x -text $font - grid $w.l-$x $w.c-$x - } - } - CloseThemeData $h -} - - - -# Formats a display device as returned by C into a keyed list -proc twapi::_format_display_device {dev} { - - # Field names - SAME ORDER AS IN $dev!! - set fields {-name -description -flags -id -key} - - set flags [lindex $dev 2] - foreach {opt flag} { - desktop 0x00000001 - multidriver 0x00000002 - primary 0x00000004 - mirroring 0x00000008 - vgacompatible 0x00000010 - removable 0x00000020 - modespruned 0x08000000 - remote 0x04000000 - disconnect 0x02000000 - } { - lappend fields -$opt - lappend dev [expr { $flags & $flag ? true : false }] - } - - return [kl_create2 $fields $dev] -} - -# Formats a display monitor as returned by C into a keyed list -proc twapi::_format_display_monitor {dev} { - - # Field names - SAME ORDER AS IN $dev!! - set fields {-name -description -flags -id -key} - - set flags [lindex $dev 2] - foreach {opt flag} { - active 0x00000001 - attached 0x00000002 - } { - lappend fields -$opt - lappend dev [expr { $flags & $flag ? true : false }] - } - - return [kl_create2 $fields $dev] -} - -# Format a monitor info struct -proc twapi::_format_monitor_info {hmon} { - return [kl_create2 {-extent -workarea -primary -name} $hmon] -} - -# Get message-only windows -proc twapi::_get_message_only_windows {} { - - set wins [list ] - set prev 0 - # -3 -> HWND_MESSAGE windows - - while true { - set win [FindWindowEx [list -3 HWND] $prev "" ""] - if {[pointer_null? $win]} break - lappend wins $win - set prev $win - } - - return $wins -} - +# +# Copyright (c) 2003-2012 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# TBD - define a C function and way to implement window callback so +# that SetWindowLong(GWL_WNDPROC) can be implemente +# + + +# TBD - document the following class names +# SciCalc CALC.EXE +# CalWndMain CALENDAR.EXE +# Cardfile CARDFILE.EXE +# Clipboard CLIPBOARD.EXE +# Clock CLOCK.EXE +# CtlPanelClass CONTROL.EXE +# XLMain EXCEL.EXE +# Session MS-DOS.EXE +# Notepad NOTEPAD.EXE +# pbParent PBRUSH.EXE +# Pif PIFEDIT.EXE +# PrintManager PRINTMAN.EXE +# Progman PROGMAN.EXE (Windows Program Manager) +# Recorder RECORDER.EXE +# Reversi REVERSI.EXE +# #32770 SETUP.EXE +# Solitaire SOL.EXE +# Terminal TERMINAL.EXE +# WFS_Frame WINFILE.EXE +# MW_WINHELP WINHELP.EXE +# #32770 WINVER.EXE +# OpusApp WINWORD.EXE +# MSWRITE_MENU WRITE.EXE +# OMain Microsoft Access +# XLMAIN Microsoft Excel +# rctrl_renwnd32 Microsoft Outlook +# PP97FrameClass Microsoft PowerPoint +# OpusApp Microsoft Word + +namespace eval twapi { + struct POINT {LONG x; LONG y;} + struct RECT { LONG left; LONG top; LONG right; LONG bottom; } + struct WINDOWPLACEMENT { + UINT cbSize; + UINT flags; + UINT showCmd; + struct POINT ptMinPosition; + struct POINT ptMaxPosition; + struct RECT rcNormalPosition; + } +} + +proc twapi::get_window_placement {hwin} { + GetWindowPlacement $hwin [WINDOWPLACEMENT] +} + +# Set the focus to the given window +proc twapi::set_focus {hwin} { + return [_return_window [_attach_hwin_and_eval $hwin {SetFocus $hwin}]] +} + +# Enumerate toplevel windows +proc twapi::get_toplevel_windows {args} { + + array set opts [parseargs args { + {pid.arg} + {pids.arg} + }] + + set toplevels [twapi::EnumWindows] + + if {[info exists opts(pids)]} { + set pids $opts(pids) + } elseif {[info exists opts(pid)]} { + set pids [list $opts(pid)] + } else { + return $toplevels + } + + set process_toplevels [list ] + foreach toplevel $toplevels { + set pid [get_window_process $toplevel] + if {[lsearch -exact -integer $pids $pid] >= 0} { + lappend process_toplevels $toplevel + } + } + + return $process_toplevels +} + + +# Find a window based on given criteria +proc twapi::find_windows {args} { + # TBD - would incorporating FindWindowEx be faster + # TBD - apparently on Windows 8, you need to use FindWindowEx to + # get non-toplevel Metro windows + + array set opts [parseargs args { + ancestor.arg + caption.bool + child.bool + class.arg + {match.arg string {string glob regexp}} + maximize.bool + maximizebox.bool + messageonlywindow.bool + minimize.bool + minimizebox.bool + overlapped.bool + pids.arg + popup.bool + single + style.arg + text.arg + toplevel.bool + visible.bool + } -maxleftover 0] + + if {[info exists opts(style)] + ||[info exists opts(overlapped)] + || [info exists opts(popup)] + || [info exists opts(child)] + || [info exists opts(minimizebox)] + || [info exists opts(maximizebox)] + || [info exists opts(minimize)] + || [info exists opts(maximize)] + || [info exists opts(visible)] + || [info exists opts(caption)] + } { + set need_style 1 + } else { + set need_style 0 + } + + # Figure out the type of match if -text specified + if {[info exists opts(text)]} { + switch -exact -- $opts(match) { + glob { + set text_compare [list string match -nocase $opts(text)] + } + string { + set text_compare [list string equal -nocase $opts(text)] + } + regexp { + set text_compare [list regexp -nocase $opts(text)] + } + default { + error "Invalid value '$opts(match)' specified for -match option" + } + } + } + + # First build a list of potential candidates. There are two main + # categories we have to look at - ordinary windows and message-only + # windows. Normally, both are included. However, if -messageonlywindow + # is specified, then we only include the former or the latter + # depending on the value of the -messageonlywindow option + + set include_ordinary true + if {[info exists opts(messageonlywindow)]} { + if {$opts(messageonlywindow)} { + if {[info exists opts(toplevel)] && $opts(toplevel)} { + error "Options -toplevel and -messageonlywindow cannot be both specified as true" + } + if {[info exists opts(text)]} { + # See bug 3213001 + error "Option -text cannot be specified if -messageonlywindow is specified as true" + } + if {[info exists opts(ancestor)]} { + error "Option -ancestor cannot be specified if -messageonlywindow is specified as true" + } + set include_ordinary false + } + set include_messageonly $opts(messageonlywindow) + } else { + # -messageonlywindow not specified at all. Only include + # messageonly windows if toplevel is not specified as true + # Also, if opts(text) is specified, will never match messageonly + # so set it to false to we do not pick up messageonly windows + # (which will hang if we go looking for them with -text : see + # bug 3213001). + if {([info exists opts(toplevel)] && $opts(toplevel)) || + [info exists opts(ancestor)] || [info exists opts(text)] + } { + set include_messageonly false + } else { + set include_messageonly true + } + } + + if {$include_messageonly} { + set class "" + if {[info exists opts(class)]} { + set class $opts(class) + } + set text "" + if {[info exists opts(text)] && + $opts(match) eq "string"} { + set text $opts(text) + } + set messageonly_candidates [_get_message_only_windows] + } else { + set messageonly_candidates [list ] + } + + if {$include_ordinary} { + # TBD - make use of FindWindowEx function if possible + + # If only interested in toplevels, just start from there + if {[info exists opts(toplevel)]} { + if {$opts(toplevel)} { + set ordinary_candidates [get_toplevel_windows] + if {[info exists opts(ancestor)]} { + error "Option -ancestor may not be specified together with -toplevel true" + } + } else { + # We do not want windows to be toplevels. Remember list + # so we can check below. + set toplevels [get_toplevel_windows] + } + } + + if {![info exists ordinary_candidates]} { + # -toplevel TRuE not specified. + # If ancestor is not specified, we start from the desktop window + # Note ancestor, if specified, is never included in the search + if {[info exists opts(ancestor)] && ![pointer_null? $opts(ancestor)]} { + set ordinary_candidates [get_descendent_windows $opts(ancestor)] + } else { + set desktop [get_desktop_window] + set ordinary_candidates [concat [list $desktop] [get_descendent_windows $desktop]] + } + } + } else { + set ordinary_candidates [list ] + } + + + set matches [list ] + foreach win [concat $messageonly_candidates $ordinary_candidates] { + # Why are we not using a trap here instead of catch ? TBD + set status [catch { + if {[info exists toplevels]} { + # We do NOT want toplevels + if {[lsearch -exact $toplevels $win] >= 0} { + # This is toplevel, which we don't want + continue + } + } + + # TBD - what is the right order to check from a performance + # point of view + + if {$need_style} { + set win_styles [get_window_style $win] + set win_style [lindex $win_styles 0] + set win_exstyle [lindex $win_styles 1] + set win_styles [lrange $win_styles 2 end] + } + + if {[info exists opts(style)] && [llength $opts(style)]} { + lassign $opts(style) style exstyle + if {[string length $style] && ($style != $win_style)} continue + if {[string length $exstyle] && ($exstyle != $win_exstyle)} continue + } + + set match 1 + foreach opt {visible overlapped popup child minimizebox + maximizebox minimize maximize caption + } { + if {[info exists opts($opt)]} { + if {(! $opts($opt)) == ([lsearch -exact $win_styles $opt] >= 0)} { + set match 0 + break + } + } + } + if {! $match} continue + + # TBD - should we use get_window_class or get_window_real_class + if {[info exists opts(class)] && + [string compare -nocase $opts(class) [get_window_class $win]]} { + continue + } + + if {[info exists opts(pids)]} { + set pid [get_window_process $win] + if {[lsearch -exact -integer $opts(pids) $pid] < 0} continue + } + + if {[info exists opts(text)]} { + set text [get_window_text $win] + if {![eval $text_compare [list [get_window_text $win]]]} continue + } + # Matches all criteria. If we only want one, return it, else + # add to match list + if {$opts(single)} { + return $win + } + lappend matches $win + } result ] + + switch -exact -- $status { + 0 { + # No error, just keep going + } + 1 { + # Error, see if error code is no window and if so, ignore + lassign $::errorCode subsystem code msg + if {$subsystem == "TWAPI_WIN32"} { + # Window has disappeared so just do not include it + # Cannot just actual code since many different codes + # might be returned in this case + } else { + error $result $::errorInfo $::errorCode + } + } + 2 { + return $result; # Block executed a return + } + 3 { + break; # Block executed a break + } + 4 { + continue; # Block executed a continue + } + } + } + + return $matches + +} + + +# Return all descendent windows +proc twapi::get_descendent_windows {parent_hwin} { + return [EnumChildWindows $parent_hwin] +} + +# Return the parent window +proc twapi::get_parent_window {hwin} { + # Note - we use GetAncestor and not GetParent because the latter + # will return the owner in the case of a toplevel window + # 1 -> GA_PARENT -> 1 + return [_return_window [GetAncestor $hwin 1]] +} + +# Return owner window +proc twapi::get_owner_window {hwin} { + # GW_OWNER -> 4 + return [_return_window [twapi::GetWindow $hwin 4]] +} + +# Return immediate children of a window (not all children) +proc twapi::get_child_windows {hwin} { + set children [list ] + # TBD - maybe get_first_child/get_next_child would be more efficient + foreach w [get_descendent_windows $hwin] { + if {[_same_window $hwin [get_parent_window $w]]} { + lappend children $w + } + } + return $children +} + +# Return first child in z-order +proc twapi::get_first_child {hwin} { + # GW_CHILD -> 5 + return [_return_window [twapi::GetWindow $hwin 5]] +} + + +# Return the next sibling window in z-order +proc twapi::get_next_sibling_window {hwin} { + # GW_HWNDNEXT -> 2 + return [_return_window [twapi::GetWindow $hwin 2]] +} + +# Return the previous sibling window in z-order +proc twapi::get_prev_sibling_window {hwin} { + # GW_HWNDPREV -> 3 + return [_return_window [twapi::GetWindow $hwin 3]] +} + +# Return the sibling window that is highest in z-order +proc twapi::get_first_sibling_window {hwin} { + # GW_HWNDFIRST -> 0 + return [_return_window [twapi::GetWindow $hwin 0]] +} + +# Return the sibling window that is lowest in z-order +proc twapi::get_last_sibling_window {hwin} { + # GW_HWNDLAST -> 1 + return [_return_window [twapi::GetWindow $hwin 1]] +} + +# Return the desktop window +proc twapi::get_desktop_window {} { + return [_return_window [twapi::GetDesktopWindow]] +} + +# Return the shell window +proc twapi::get_shell_window {} { + return [_return_window [twapi::GetShellWindow]] +} + +# Return the pid for a window +proc twapi::get_window_process {hwin} { + return [lindex [GetWindowThreadProcessId $hwin] 1] +} + +# Return the thread for a window +proc twapi::get_window_thread {hwin} { + return [lindex [GetWindowThreadProcessId $hwin] 0] +} + +# Return the style of the window. Returns a list of two integers +# the first contains the style bits, the second the extended style bits +proc twapi::get_window_style {hwin} { + # GWL_STYLE -> -16, GWL_EXSTYLE -20 + set style [GetWindowLongPtr $hwin -16] + set exstyle [GetWindowLongPtr $hwin -20] + return [concat [list $style $exstyle] [_style_mask_to_symbols $style $exstyle]] +} + + +# Set the style of the window. Returns a list of two integers +# the first contains the original style bits, the second the +# original extended style bits +proc twapi::set_window_style {hwin style exstyle} { + # GWL_STYLE -> -16, GWL_EXSTYLE -20 + set style [SetWindowLongPtr $hwin -16 $style] + set exstyle [SetWindowLongPtr $hwin -20 $exstyle] + + redraw_window_frame $hwin + return +} + + +# Return the class of the window +proc twapi::get_window_class {hwin} { + return [GetClassName $hwin] +} + +# Return the real class of the window +proc twapi::get_window_real_class {hwin} { + return [RealGetWindowClass $hwin] +} + +# Return the identifier corrpsonding to the application instance +proc twapi::get_window_application {hwin} { + # GWL_HINSTANCE -> -6 + return [GetWindowLongPtr $hwin -6] +} + +# Return the window id (this is different from the handle!) +proc twapi::get_window_id {hwin} { + # GWL_ID -> -12 + return [GetWindowLongPtr $hwin -12] +} + +# Return the user data associated with a window +proc twapi::get_window_userdata {hwin} { + # GWL_USERDATA -> -21 + return [GetWindowLongPtr $hwin -21] +} + + +# Get the foreground window +proc twapi::get_foreground_window {} { + return [_return_window [GetForegroundWindow]] +} + +# Set the foreground window - returns 1/0 on success/fail +proc twapi::set_foreground_window {hwin} { + return [SetForegroundWindow $hwin] +} + + +# Activate a window - this is only brought the foreground if its application +# is in the foreground +proc twapi::set_active_window_for_thread {hwin} { + return [_return_window [_attach_hwin_and_eval $hwin {SetActiveWindow $hwin}]] +} + +# Get active window for an application +proc twapi::get_active_window_for_thread {tid} { + return [_return_window [_get_gui_thread_info $tid hwndActive]] +} + + +# Get focus window for an application +proc twapi::get_focus_window_for_thread {tid} { + return [_get_gui_thread_info $tid hwndFocus] +} + +# Get active window for current thread +proc twapi::get_active_window_for_current_thread {} { + return [_return_window [GetActiveWindow]] +} + +# Update the frame - needs to be called after setting certain style bits +proc twapi::redraw_window_frame {hwin} { + # 0x4037 -> SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | + # SWP_NOMOVE | SWP_NOSIZE | + # SWP_NOZORDER | SWP_FRAMECHANGED + SetWindowPos $hwin 0 0 0 0 0 0x4037 +} + +# Redraw the window +proc twapi::redraw_window {hwin {opt ""}} { + if {[string length $opt]} { + if {[string compare $opt "-force"]} { + error "Invalid option '$opt'" + } + invalidate_screen_region -hwin $hwin -rect [list ] -bgerase + } + + UpdateWindow $hwin + return +} + +# Set the window position +proc twapi::move_window {hwin x y args} { + array set opts [parseargs args { + {sync} + }] + + # Not using MoveWindow because that will require knowing the width + # and height (or retrieving it) + # 0x15 -> SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER + set flags 0x15 + if {! $opts(sync)} { + setbits flags 0x4000; # SWP_ASYNCWINDOWPOS + } + SetWindowPos $hwin 0 $x $y 0 0 $flags +} + +# Resize window +proc twapi::resize_window {hwin w h args} { + array set opts [parseargs args { + {sync} + }] + + + # Not using MoveWindow because that will require knowing the x and y pos + # (or retrieving them) + # 0x16 -> SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER + set flags 0x16 + if {! $opts(sync)} { + setbits flags 0x4000; # SWP_ASYNCWINDOWPOS + } + SetWindowPos $hwin 0 0 0 $w $h $flags +} + +# Sets the window's z-order position +# pos is either window handle or a symbol +proc twapi::set_window_zorder {hwin pos} { + switch -exact -- $pos { + top { + set pos [pointer_from_address 0 HWND]; #HWND_TOP + } + bottom { + set pos [pointer_from_address 1 HWND]; #HWND_BOTTOM + } + toplayer { + set pos [pointer_from_address -1 HWND]; #HWND_TOPMOST + } + bottomlayer { + set pos [pointer_from_address -2 HWND]; #HWND_NOTOPMOST + } + } + + # 0x4013 -> SWP_ASYNCWINDOWPOS|SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE + SetWindowPos $hwin $pos 0 0 0 0 0x4013 +} + + +# Show the given window. Returns 1 if window was previously visible, else 0 +proc twapi::show_window {hwin args} { + array set opts [parseargs args {sync activate normal startup}] + + set show 0 + if {$opts(startup)} { + set show 10; #SW_SHOWDEFAULT + } else { + if {$opts(activate)} { + if {$opts(normal)} { + set show 1; #SW_SHOWNORMAL + } else { + set show 5; #SW_SHOW + } + } else { + if {$opts(normal)} { + set show 4; #SW_SHOWNOACTIVATE + } else { + set show 8; #SW_SHOWNA + } + } + } + + _show_window $hwin $show $opts(sync) +} + +# Hide the given window. Returns 1 if window was previously visible, else 0 +proc twapi::hide_window {hwin args} { + array set opts [parseargs args {sync}] + _show_window $hwin 0 $opts(sync); # 0 -> SW_HIDE +} + +# Restore the given window. Returns 1 if window was previously visible, else 0 +proc twapi::restore_window {hwin args} { + array set opts [parseargs args {sync activate}] + if {$opts(activate)} { + _show_window $hwin 9 $opts(sync); # 9 -> SW_RESTORE + } else { + OpenIcon $hwin + } +} + +# Maximize the given window. Returns 1 if window was previously visible, else 0 +proc twapi::maximize_window {hwin args} { + array set opts [parseargs args {sync}] + _show_window $hwin 3 $opts(sync); # 3 -> SW_SHOWMAXIMIZED +} + + +# Minimize the given window. Returns 1 if window was previously visible, else 0 +proc twapi::minimize_window {hwin args} { + array set opts [parseargs args {sync activate shownext}] + + # TBD - when should we use SW_FORCEMINIMIZE ? + # TBD - do we need to attach to the window's thread? + # TBD - when should we use CloseWindow instead? + + if $opts(activate) { + set show 2; #SW_SHOWMINIMIZED + } else { + if {$opts(shownext)} { + set show 6; #SW_MINIMIZE + } else { + set show 7; #SW_SHOWMINNOACTIVE + } + } + + _show_window $hwin $show $opts(sync) +} + + +# Hides popup windows +proc twapi::hide_owned_popups {hwin} { + ShowOwnedPopups $hwin 0 +} + +# Show hidden popup windows +proc twapi::show_owned_popups {hwin} { + ShowOwnedPopups $hwin 1 +} + +# Close a window +proc twapi::close_window {hwin args} { + array set opts [parseargs args { + block + {wait.int 10} + } -maxleftover 0] + + if {0} { + Cannot close Explorer windows using SendMessage* + if {$opts(block)} { + set block 3; #SMTO_BLOCK|SMTO_ABORTIFHUNG + } else { + set block 2; #SMTO_NORMAL|SMTO_ABORTIFHUNG + } + + # WM_CLOSE -> 0x10 + if {[catch {SendMessageTimeout $hwin 0x10 0 0 $block $opts(wait)} msg]} { + # Do no treat timeout as an error + set erCode $::errorCode + set erInfo $::errorInfo + if {[lindex $erCode 0] != "TWAPI_WIN32" || + ([lindex $erCode 1] != 0 && [lindex $erCode 1] != 1460)} { + error $msg $erInfo $erCode + } + } + } else { + # Implement using PostMessage since that allows closing of + # Explorer windows + + # Note - opts(block) is ignored here + + # 0x10 -> WM_CLOSE + PostMessage $hwin 0x10 0 0 + if {$opts(wait)} { + wait [list ::twapi::window_exists $hwin] 0 $opts(wait) + } + } + return [twapi::window_exists $hwin] +} + +# CHeck if window is minimized +proc twapi::window_minimized {hwin} { + return [IsIconic $hwin] +} + +# CHeck if window is maximized +proc twapi::window_maximized {hwin} { + return [IsZoomed $hwin] +} + +# Check if window is visible +proc twapi::window_visible {hwin} { + return [IsWindowVisible $hwin] +} + +# Check if a window exists +proc twapi::window_exists {hwin} { + return [IsWindow $hwin] +} + +# CHeck if window input is enabled +proc twapi::window_unicode_enabled {hwin} { + return [IsWindowUnicode $hwin] +} + +# Check if child is a child of parent +proc twapi::window_is_child {parent child} { + return [IsChild $parent $child] +} + +# Flash the given window +proc twapi::flash_window_caption {hwin args} { + array set opts [parseargs args {toggle}] + + return [FlashWindow $hwin $opts(toggle)] +} + +# FlashWindow not in binary any more, emulate it +proc twapi::FlashWindow {hwin toggle} { + FlashWindowEx [list $hwin 1 $toggle 0] +} + +# Flash the given window and/or the taskbar icon +proc twapi::flash_window {hwin args} { + array set opts [parseargs args { + period.int + count.int + nocaption + notaskbar + start + stop + untilforeground + } -maxleftover 0 -nulldefault] + + set flags 0 + + if {! $opts(stop)} { + # Flash title bar? + if {! $opts(nocaption)} { + incr flags 1; # FLASHW_CAPTION + } + + # Flash taskbar icon ? + if {! $opts(notaskbar)} { + incr flags 2; # FLASHW_TRAY + } + + # Continuous modes ? + if {$opts(untilforeground)} { + # Continuous until foreground window + # NOTE : FLASHW_TIMERNOFG is no implemented because it seems to be + # broken - it only flashes once, at least on Windows XP. Keep + # it in case other platforms work correctly. + incr flags 0xc; # FLASHW_TIMERNOFG + } elseif {$opts(start)} { + # Continuous until stopped + incr flags 4; # FLASHW_TIMER + } elseif {$opts(count) == 0} { + set opts(count) 1 + } + } + + return [FlashWindowEx [list $hwin $flags $opts(count) $opts(period)]] +} + + +# Show/hide window caption buttons. hwin must be a toplevel +proc twapi::configure_window_titlebar {hwin args} { + + array set opts [parseargs args { + visible.bool + sysmenu.bool + minimizebox.bool + maximizebox.bool + contexthelp.bool + } -maxleftover 0] + + # Get the current style setting + lassign [get_window_style $hwin] style exstyle + + # See if each option is specified. Else use current setting + # 0x00080000 -> WS_SYSMENU + # 0x00020000 -> WS_MINIMIZEBOX + # 0x00010000 -> WS_MAXIMIZEBOX + # 0x00C00000 -> WS_CAPTION + foreach {opt def} { + sysmenu 0x00080000 + minimizebox 0x00020000 + maximizebox 0x00010000 + visible 0x00C00000 + } { + if {[info exists opts($opt)]} { + set $opt [expr {$opts($opt) ? $def : 0}] + } else { + set $opt [expr {$style & $def}] + } + } + + # Ditto for extended style and context help + if {[info exists opts(contexthelp)]} { + # WS_EX_CONTEXTHELP -> 0x00000400 + set contexthelp [expr {$opts(contexthelp) ? 0x00000400 : 0}] + } else { + set contexthelp [expr {$exstyle & 0x00000400}] + } + + # The min/max/help buttons all depend on sysmenu being set. + if {($minimizebox || $maximizebox || $contexthelp) && ! $sysmenu} { + # Don't bother raising error, since the underlying API allows it + #error "Cannot enable minimize, maximize and context help buttons unless system menu is present" + } + + # Reset existing sysmenu,minimizebox,maximizebox,caption + set style [expr {$style & 0xff34ffff}] + ; # Add back new settings + set style [expr {$style | $sysmenu | $minimizebox | $maximizebox | $visible}] + + # Reset contexthelp and add new setting back + set exstyle [expr {$exstyle & 0xfffffbff}] + set exstyle [expr {$exstyle | $contexthelp}] + + set_window_style $hwin $style $exstyle +} + +# Arrange window icons +proc twapi::arrange_icons {{hwin ""}} { + if {$hwin == ""} { + set hwin [get_desktop_window] + } + ArrangeIconicWindows $hwin +} + +# Get the window text/caption +proc twapi::get_window_text {hwin} { + # TBD - see https://devblogs.microsoft.com/oldnewthing/20030821-00/?p=42833 + twapi::GetWindowText $hwin +} + +# Set the window text/caption +proc twapi::set_window_text {hwin text} { + twapi::SetWindowText $hwin $text +} + +# Get size of client area +proc twapi::get_window_client_area_size {hwin} { + return [lrange [GetClientRect $hwin] 2 3] +} + +# Get window coordinates +proc twapi::get_window_coordinates {hwin} { + return [GetWindowRect $hwin] +} + +# Get the window under the point +proc twapi::get_window_at_location {x y} { + return [WindowFromPoint [list $x $y]] +} + +# Marks a screen region as invalid forcing a redraw +proc twapi::invalidate_screen_region {args} { + array set opts [parseargs args { + {hwin.arg 0} + rect.arg + bgerase + } -nulldefault -maxleftover 0] + + InvalidateRect $opts(hwin) $opts(rect) $opts(bgerase) +} + +# Get the caret blink time +proc twapi::get_caret_blink_time {} { + return [GetCaretBlinkTime] +} + +# Set the caret blink time +proc twapi::set_caret_blink_time {ms} { + return [SetCaretBlinkTime $ms] +} + +# Hide the caret +proc twapi::hide_caret {} { + HideCaret 0 +} + +# Show the caret +proc twapi::show_caret {} { + ShowCaret 0 +} + +# Get the caret position +proc twapi::get_caret_location {} { + return [GetCaretPos] +} + +# Get the caret position +proc twapi::set_caret_location {point} { + return [SetCaretPos [lindex $point 0] [lindex $point 1]] +} + + +# Get display size +proc twapi::get_display_size {} { + return [lrange [get_window_coordinates [get_desktop_window]] 2 3] +} + + +# Get path to the desktop wallpaper +interp alias {} twapi::get_desktop_wallpaper {} twapi::get_system_parameters_info SPI_GETDESKWALLPAPER + + +# Set desktop wallpaper +proc twapi::set_desktop_wallpaper {path args} { + + array set opts [parseargs args { + persist + }] + + if {$opts(persist)} { + set flags 3; # Notify all windows + persist + } else { + set flags 2; # Notify all windows + } + + if {$path == "default"} { + SystemParametersInfo 0x14 0 NULL 0 + return + } + + if {$path == "none"} { + set path "" + } + + set mem_size [expr {2 * ([string length $path] + 1)}] + set mem [malloc $mem_size] + trap { + twapi::Twapi_WriteMemory 3 $mem 0 $mem_size $path + SystemParametersInfo 0x14 0 $mem $flags + } finally { + free $mem + } +} + +# Get desktop work area +interp alias {} twapi::get_desktop_workarea {} twapi::get_system_parameters_info SPI_GETWORKAREA + + + +# Get the color depth of the display +proc twapi::get_color_depth {{hwin 0}} { + set h [GetDC $hwin] + trap { + return [GetDeviceCaps $h 12] + } finally { + ReleaseDC $hwin $h + } +} + + +# Enumerate the display adapters in a system +proc twapi::get_display_devices {} { + set devs [list ] + for {set i 0} {true} {incr i} { + trap { + set dev [EnumDisplayDevices "" $i 0] + } onerror {TWAPI_WIN32} { + # We don't check for a specific error since experimentation + # shows the error code returned at the end of enumeration + # is not fixed - can be 2, 18, 87 and maybe others + break + } + lappend devs [_format_display_device $dev] + } + + return $devs +} + +# Enumerate the display monitors for an display device +proc twapi::get_display_monitors {args} { + array set opts [parseargs args { + device.arg + activeonly + } -maxleftover 0] + + if {[info exists opts(device)]} { + set devs [list $opts(device)] + } else { + set devs [list ] + foreach dev [get_display_devices] { + lappend devs [kl_get $dev -name] + } + } + + set monitors [list ] + foreach dev $devs { + for {set i 0} {true} {incr i} { + trap { + set monitor [EnumDisplayDevices $dev $i 0] + } onerror {} { + # We don't check for a specific error since experimentation + # shows the error code returned at the end of enumeration + # is not fixed - can be 2, 18, 87 and maybe others + break + } + if {(! $opts(activeonly)) || + ([lindex $monitor 2] & 1)} { + lappend monitors [_format_display_monitor $monitor] + } + } + } + + return $monitors +} + +# Return the monitor corresponding to a window +proc twapi::get_display_monitor_from_window {hwin args} { + array set opts [parseargs args { + default.arg + } -maxleftover 0] + + # hwin may be a window id or a Tk window. On error we assume it is + # a window id + catch { + set hwin [pointer_from_address [winfo id $hwin] HWND] + } + + set flags 0 + if {[info exists opts(default)]} { + switch -exact -- $opts(default) { + primary { set flags 1 } + nearest { set flags 2 } + default { error "Invalid value '$opts(default)' for -default option" } + } + } + + trap { + return [MonitorFromWindow $hwin $flags] + } onerror {TWAPI_WIN32 0} { + win32_error 1461 "Window does not map to a monitor." + } +} + +# Return the monitor corresponding to a screen cocordinates +proc twapi::get_display_monitor_from_point {x y args} { + array set opts [parseargs args { + default.arg + } -maxleftover 0] + + set flags 0 + if {[info exists opts(default)]} { + switch -exact -- $opts(default) { + primary { set flags 1 } + nearest { set flags 2 } + default { error "Invalid value '$opts(default)' for -default option" } + } + } + + trap { + return [MonitorFromPoint [list $x $y] $flags] + } onerror {TWAPI_WIN32 0} { + win32_error 1461 "Virtual screen coordinates ($x,$y) do not map to a monitor." + } +} + + +# Return the monitor corresponding to a screen rectangle +proc twapi::get_display_monitor_from_rect {rect args} { + array set opts [parseargs args { + default.arg + } -maxleftover 0] + + set flags 0 + if {[info exists opts(default)]} { + switch -exact -- $opts(default) { + primary { set flags 1 } + nearest { set flags 2 } + default { error "Invalid value '$opts(default)' for -default option" } + } + } + + trap { + return [MonitorFromRect $rect $flags] + } onerror {TWAPI_WIN32 0} { + win32_error 1461 "Virtual screen rectangle <[join $rect ,]> does not map to a monitor." + } +} + +proc twapi::get_display_monitor_info {hmon} { + return [_format_monitor_info [GetMonitorInfo $hmon]] +} + +proc twapi::get_multiple_display_monitor_info {} { + set result [list ] + foreach elem [EnumDisplayMonitors NULL ""] { + lappend result [get_display_monitor_info [lindex $elem 0]] + } + return $result +} + + +proc twapi::tkpath_to_hwnd {tkpath} { + return [cast_handle [winfo id $tkpath] HWND] +} + +# TBD - document +proc twapi::high_contrast_on {} { + set hc [lindex [get_system_parameters_info SPI_GETHIGHCONTRAST] 1] + return [expr {$hc & 1}] +} + +################################################################ +# Utility routines + +# Helper function to wrap GetGUIThreadInfo +# Returns the value of the given fields. If a single field is requested, +# returns it as a scalar else returns a flat list of FIELD VALUE pairs +proc twapi::_get_gui_thread_info {tid args} { + array set gtinfo [GetGUIThreadInfo $tid] + set result [list ] + foreach field $args { + set value $gtinfo($field) + switch -exact -- $field { + cbSize { } + rcCaret { + set value [list $value(left) \ + $value(top) \ + $value(right) \ + $value(bottom)] + } + } + lappend result $value + } + + if {[llength $args] == 1} { + return [lindex $result 0] + } else { + return $result + } +} + + +# if $hwin corresponds to a null window handle, returns an empty string +proc twapi::_return_window {hwin} { + if {[pointer_null? $hwin HWND]} { + return $::twapi::null_hwin + } + return $hwin +} + +# Return 1 if same window +proc twapi::_same_window {hwin1 hwin2} { + # If either is a empty/null handle, no match, even if both empty/null + if {[string length $hwin1] == 0 || [string length $hwin2] == 0} { + return 0 + } + if {[pointer_null? $hwin1] || [pointer_null? $hwin2]} { + return 0 + } + + # Need integer compare + return [pointer_equal? $hwin1 $hwin2] +} + +# Helper function for showing/hiding windows +proc twapi::_show_window {hwin cmd {wait 0}} { + # If either our thread owns the window or we want to wait for it to + # process the command, use the synchrnous form of the function + if {$wait || ([get_window_thread $hwin] == [GetCurrentThreadId])} { + ShowWindow $hwin $cmd + } else { + ShowWindowAsync $hwin $cmd + } +} + + + +# Map style bits to a style symbol list +proc twapi::_style_mask_to_symbols {style exstyle} { + set attrs [list ] + if {$style & 0x80000000} { + lappend attrs popup + if {$style & 0x00020000} { lappend attrs group } + if {$style & 0x00010000} { lappend attrs tabstop } + } else { + if {$style & 0x40000000} { + lappend attrs child + } else { + lappend attrs overlapped + } + if {$style & 0x00020000} { lappend attrs minimizebox } + if {$style & 0x00010000} { lappend attrs maximizebox } + } + + # Note WS_BORDER, WS_DLGFRAME and WS_CAPTION use same bits + if {$style & 0x00C00000} { + lappend attrs caption + } else { + if {$style & 0x00800000} { lappend attrs border } + if {$style & 0x00400000} { lappend attrs dlgframe } + } + + foreach {sym mask} { + minimize 0x20000000 + visible 0x10000000 + disabled 0x08000000 + clipsiblings 0x04000000 + clipchildren 0x02000000 + maximize 0x01000000 + vscroll 0x00200000 + hscroll 0x00100000 + sysmenu 0x00080000 + thickframe 0x00040000 + } { + if {$style & $mask} { + lappend attrs $sym + } + } + + if {$exstyle & 0x00001000} { + lappend attrs right + } else { + lappend attrs left + } + if {$exstyle & 0x00002000} { + lappend attrs rtlreading + } else { + lappend attrs ltrreading + } + if {$exstyle & 0x00004000} { + lappend attrs leftscrollbar + } else { + lappend attrs rightscrollbar + } + + foreach {sym mask} { + dlgmodalframe 0x00000001 + noparentnotify 0x00000004 + topmost 0x00000008 + acceptfiles 0x00000010 + transparent 0x00000020 + mdichild 0x00000040 + toolwindow 0x00000080 + windowedge 0x00000100 + clientedge 0x00000200 + contexthelp 0x00000400 + controlparent 0x00010000 + staticedge 0x00020000 + appwindow 0x00040000 + } { + if {$exstyle & $mask} { + lappend attrs $sym + } + } + + return $attrs +} + + +# Test proc for displaying all colors for a class +proc twapi::_show_theme_colors {class part {state ""}} { + set w [toplevel .themetest$class$part$state] + + set h [OpenThemeData [tkpath_to_hwnd $w] $class] + wm title $w "$class Colors" + + label $w.title -text "$class, $part, $state" -bg white + grid $w.title - + + if {![string is integer -strict $part]} { + set part [TwapiGetThemeDefine $part] + } + + if {![string is integer -strict $state]} { + set state [TwapiGetThemeDefine $state] + } + + foreach x {BORDERCOLOR FILLCOLOR TEXTCOLOR EDGELIGHTCOLOR EDGESHADOWCOLOR EDGEFILLCOLOR TRANSPARENTCOLOR GRADIENTCOLOR1 GRADIENTCOLOR2 GRADIENTCOLOR3 GRADIENTCOLOR4 GRADIENTCOLOR5 SHADOWCOLOR GLOWCOLOR TEXTBORDERCOLOR TEXTSHADOWCOLOR GLYPHTEXTCOLOR FILLCOLORHINT BORDERCOLORHINT ACCENTCOLORHINT BLENDCOLOR} { + set prop [TwapiGetThemeDefine TMT_$x] + if {![catch {GetThemeColor $h $part $state $prop} color]} { + label $w.l-$x -text $x + label $w.c-$x -text $color -bg $color + grid $w.l-$x $w.c-$x + } else { + label $w.l-$x -text $x + label $w.c-$x -text "Not defined" + grid $w.l-$x $w.c-$x + } + } + CloseThemeData $h +} + +# Test proc for displaying all sys colors for a class +# class might be "WINDOW" +proc twapi::_show_theme_syscolors {class} { + destroy .themetest$class + set w [toplevel .themetest$class] + + set h [OpenThemeData [tkpath_to_hwnd $w] $class] + wm title $w "$class SysColors" + + label $w.title -text "$class" -bg white + grid $w.title - + + + for {set x 0} {$x <= 30} {incr x} { + if {![catch {GetThemeSysColor $h $x} color]} { + set color #[format %6.6x $color] + label $w.l-$x -text $x + label $w.c-$x -text $color -bg $color + grid $w.l-$x $w.c-$x + } else { + label $w.l-$x -text $x + label $w.c-$x -text "Not defined" + grid $w.l-$x $w.c-$x + } + } + CloseThemeData $h +} + +# Test proc for displaying all fonts for a class +proc twapi::_show_theme_fonts {class part {state ""}} { + set w [toplevel .themetest$class$part$state] + + set h [OpenThemeData [tkpath_to_hwnd $w] $class] + wm title $w "$class fonts" + + label $w.title -text "$class, $part, $state" -bg white + grid $w.title - + + set part [TwapiGetThemeDefine $part] + set state [TwapiGetThemeDefine $state] + + foreach x {GLYPHTYPE FONT} { + set prop [TwapiGetThemeDefine TMT_$x] + if {![catch {GetThemeFont $h NULL $part $state $prop} font]} { + label $w.l-$x -text $x + label $w.c-$x -text $font + grid $w.l-$x $w.c-$x + } + } + CloseThemeData $h +} + + + +# Formats a display device as returned by C into a keyed list +proc twapi::_format_display_device {dev} { + + # Field names - SAME ORDER AS IN $dev!! + set fields {-name -description -flags -id -key} + + set flags [lindex $dev 2] + foreach {opt flag} { + desktop 0x00000001 + multidriver 0x00000002 + primary 0x00000004 + mirroring 0x00000008 + vgacompatible 0x00000010 + removable 0x00000020 + modespruned 0x08000000 + remote 0x04000000 + disconnect 0x02000000 + } { + lappend fields -$opt + lappend dev [expr { $flags & $flag ? true : false }] + } + + return [kl_create2 $fields $dev] +} + +# Formats a display monitor as returned by C into a keyed list +proc twapi::_format_display_monitor {dev} { + + # Field names - SAME ORDER AS IN $dev!! + set fields {-name -description -flags -id -key} + + set flags [lindex $dev 2] + foreach {opt flag} { + active 0x00000001 + attached 0x00000002 + } { + lappend fields -$opt + lappend dev [expr { $flags & $flag ? true : false }] + } + + return [kl_create2 $fields $dev] +} + +# Format a monitor info struct +proc twapi::_format_monitor_info {hmon} { + return [kl_create2 {-extent -workarea -primary -name} $hmon] +} + +# Get message-only windows +proc twapi::_get_message_only_windows {} { + + set wins [list ] + set prev 0 + # -3 -> HWND_MESSAGE windows + + while true { + set win [FindWindowEx [list -3 HWND] $prev "" ""] + if {[pointer_null? $win]} break + lappend wins $win + set prev $win + } + + return $wins +} + diff --git a/src/vendorlib_tcl8/twapi4.7.2/win.tcl b/src/vendorlib_tcl8/twapi-5.0b1/win.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/win.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/win.tcl index d0b62170..57e9261f 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/win.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/win.tcl @@ -1,131 +1,131 @@ -# -# Copyright (c) 2012 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Contains common windowing and notification infrastructure - -namespace eval twapi { - variable null_hwin "" - - # Windows messages that are directly accessible from script. These - # are handled by the default notifications window and passed to - # the twapi::_script_wm_handler. These messages must be in the - # range (1056 = 1024+32) - (1024+32+31) (see twapi_wm.h) - variable _wm_script_msgs - array set _wm_script_msgs { - TASKBAR_RESTART 1031 - NOTIFY_ICON_CALLBACK 1056 - } - proc _get_script_wm {tok} { - variable _wm_script_msgs - return $_wm_script_msgs($tok) - } -} - -# Backward compatibility aliases -interp alias {} twapi::GetWindowLong {} twapi::GetWindowLongPtr -interp alias {} twapi::SetWindowLong {} twapi::SetWindowLongPtr - -# Return the long value at the given index -# This is a raw function, and should generally be used only to get -# non-system defined indices -proc twapi::get_window_long {hwin index} { - return [GetWindowLongPtr $hwin $index] -} - -# Set the long value at the given index and return the previous value -# This is a raw function, and should generally be used only to get -# non-system defined indices -proc twapi::set_window_long {hwin index val} { - set oldval [SetWindowLongPtr $hwin $index $val] -} - -# Set the user data associated with a window. Returns the previous value -proc twapi::set_window_userdata {hwin val} { - # GWL_USERDATA -> -21 - return [SetWindowLongPtr $hwin -21 $val] -} - -# Attaches to the thread queue of the thread owning $hwin and executes -# script in the caller's scope -proc twapi::_attach_hwin_and_eval {hwin script} { - set me [GetCurrentThreadId] - set hwin_tid [lindex [GetWindowThreadProcessId $hwin] 0] - if {$hwin_tid == 0} { - error "Window $hwin does not exist or could not get its thread owner" - } - - # Cannot (and no need to) attach to oneself so just exec script directly - if {$me == $hwin_tid} { - return [uplevel 1 $script] - } - - trap { - if {![AttachThreadInput $me $hwin_tid 1]} { - error "Could not attach to thread input for window $hwin" - } - set result [uplevel 1 $script] - } finally { - AttachThreadInput $me $hwin_tid 0 - } - - return $result -} - -proc twapi::_register_script_wm_handler {msg cmdprefix {overwrite 0}} { - variable _wm_registrations - - # Ensure notification window exists - twapi::Twapi_GetNotificationWindow - - # The incr ensures decimal format - # The lrange ensure proper list format - if {$overwrite} { - set _wm_registrations([incr msg 0]) [list [lrange $cmdprefix 0 end]] - } else { - lappend _wm_registrations([incr msg 0]) [lrange $cmdprefix 0 end] - } -} - -proc twapi::_unregister_script_wm_handler {msg cmdprefix} { - variable _wm_registrations - - # The incr ensures decimal format - incr msg 0 - # The lrange ensure proper list format - if {[info exists _wm_registrations($msg)]} { - set _wm_registrations($msg) [lsearch -exact -inline -not -all $_wm_registrations($msg) [lrange $cmdprefix 0 end]] - } -} - -# Handles notifications from the common window for script level windows -# messages (see win.c) -proc twapi::_script_wm_handler {msg wparam lparam msgpos ticks} { - variable _wm_registrations - - set code 0 - if {[info exists _wm_registrations($msg)]} { - foreach handler $_wm_registrations($msg) { - set code [catch {uplevel #0 [linsert $handler end $msg $wparam $lparam $msgpos $ticks]} msg] - switch -exact -- $code { - 1 { - # TBD - should remaining handlers be called even on error ? - after 0 [list error $msg $::errorInfo $::errorCode] - break - } - 3 { - break; # Ignore remaining handlers - } - default { - # Keep going - } - } - } - } else { - # TBD - debuglog - no handler for $msg - } - - return -} +# +# Copyright (c) 2012 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Contains common windowing and notification infrastructure + +namespace eval twapi { + variable null_hwin "" + + # Windows messages that are directly accessible from script. These + # are handled by the default notifications window and passed to + # the twapi::_script_wm_handler. These messages must be in the + # range (1056 = 1024+32) - (1024+32+31) (see twapi_wm.h) + variable _wm_script_msgs + array set _wm_script_msgs { + TASKBAR_RESTART 1031 + NOTIFY_ICON_CALLBACK 1056 + } + proc _get_script_wm {tok} { + variable _wm_script_msgs + return $_wm_script_msgs($tok) + } +} + +# Backward compatibility aliases +interp alias {} twapi::GetWindowLong {} twapi::GetWindowLongPtr +interp alias {} twapi::SetWindowLong {} twapi::SetWindowLongPtr + +# Return the long value at the given index +# This is a raw function, and should generally be used only to get +# non-system defined indices +proc twapi::get_window_long {hwin index} { + return [GetWindowLongPtr $hwin $index] +} + +# Set the long value at the given index and return the previous value +# This is a raw function, and should generally be used only to get +# non-system defined indices +proc twapi::set_window_long {hwin index val} { + set oldval [SetWindowLongPtr $hwin $index $val] +} + +# Set the user data associated with a window. Returns the previous value +proc twapi::set_window_userdata {hwin val} { + # GWL_USERDATA -> -21 + return [SetWindowLongPtr $hwin -21 $val] +} + +# Attaches to the thread queue of the thread owning $hwin and executes +# script in the caller's scope +proc twapi::_attach_hwin_and_eval {hwin script} { + set me [GetCurrentThreadId] + set hwin_tid [lindex [GetWindowThreadProcessId $hwin] 0] + if {$hwin_tid == 0} { + error "Window $hwin does not exist or could not get its thread owner" + } + + # Cannot (and no need to) attach to oneself so just exec script directly + if {$me == $hwin_tid} { + return [uplevel 1 $script] + } + + trap { + if {![AttachThreadInput $me $hwin_tid 1]} { + error "Could not attach to thread input for window $hwin" + } + set result [uplevel 1 $script] + } finally { + AttachThreadInput $me $hwin_tid 0 + } + + return $result +} + +proc twapi::_register_script_wm_handler {msg cmdprefix {overwrite 0}} { + variable _wm_registrations + + # Ensure notification window exists + twapi::Twapi_GetNotificationWindow + + # The incr ensures decimal format + # The lrange ensure proper list format + if {$overwrite} { + set _wm_registrations([incr msg 0]) [list [lrange $cmdprefix 0 end]] + } else { + lappend _wm_registrations([incr msg 0]) [lrange $cmdprefix 0 end] + } +} + +proc twapi::_unregister_script_wm_handler {msg cmdprefix} { + variable _wm_registrations + + # The incr ensures decimal format + incr msg 0 + # The lrange ensure proper list format + if {[info exists _wm_registrations($msg)]} { + set _wm_registrations($msg) [lsearch -exact -inline -not -all $_wm_registrations($msg) [lrange $cmdprefix 0 end]] + } +} + +# Handles notifications from the common window for script level windows +# messages (see win.c) +proc twapi::_script_wm_handler {msg wparam lparam msgpos ticks} { + variable _wm_registrations + + set code 0 + if {[info exists _wm_registrations($msg)]} { + foreach handler $_wm_registrations($msg) { + set code [catch {uplevel #0 [linsert $handler end $msg $wparam $lparam $msgpos $ticks]} msg] + switch -exact -- $code { + 1 { + # TBD - should remaining handlers be called even on error ? + after 0 [list error $msg $::errorInfo $::errorCode] + break + } + 3 { + break; # Ignore remaining handlers + } + default { + # Keep going + } + } + } + } else { + # TBD - debuglog - no handler for $msg + } + + return +} diff --git a/src/vendorlib_tcl8/twapi-5.0b1/win32-ix86/tcl9twapi50b1.dll b/src/vendorlib_tcl8/twapi-5.0b1/win32-ix86/tcl9twapi50b1.dll new file mode 100644 index 0000000000000000000000000000000000000000..1573e4f8c287249861c097fd003c4d853537bcd4 GIT binary patch literal 530432 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 z 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

mYj-tWfqnxI-{$B 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/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 literal 0 HcmV?d00001 diff --git a/src/vendorlib_tcl8/twapi-5.0b1/win32-x86_64/tcl9twapi50b1.dll b/src/vendorlib_tcl8/twapi-5.0b1/win32-x86_64/tcl9twapi50b1.dll new file mode 100644 index 0000000000000000000000000000000000000000..62b25a36ca1d5cd44c20643daa2f12906be553bd GIT binary patch literal 658432 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

    TtNegE5?*ym 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

    +)irLq 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 EC&^h8@c;k- literal 0 HcmV?d00001 diff --git a/src/vendorlib_tcl8/twapi4.7.2/winlog.tcl b/src/vendorlib_tcl8/twapi-5.0b1/winlog.tcl similarity index 97% rename from src/vendorlib_tcl8/twapi4.7.2/winlog.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/winlog.tcl index d48d6cd5..a6310e98 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/winlog.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/winlog.tcl @@ -1,304 +1,304 @@ -# -# Copyright (c) 2012, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -# Routines to unify old and new Windows event log APIs - -namespace eval twapi { - # Dictionary to map eventlog consumer handles to various related info - # The primary key is the read handle to the event channel/source. - # Nested keys depend on OS version - variable _winlog_handles -} - -proc twapi::winlog_open {args} { - variable _winlog_handles - - # TBD - document -authtype - array set opts [parseargs args { - {system.arg ""} - channel.arg - file.arg - {authtype.arg 0} - {direction.arg forward {forward backward}} - } -maxleftover 0] - - if {[info exists opts(file)] && - ($opts(system) ne "" || [info exists opts(channel)])} { - error "Option '-file' cannot be used with '-channel' or '-system'" - } else { - if {![info exists opts(channel)]} { - set opts(channel) "Application" - } - } - - if {[min_os_version 6]} { - # Use new Vista APIs - if {[info exists opts(file)]} { - set hsess NULL - set hq [evt_query -file $opts(file) -ignorequeryerrors] - } else { - if {$opts(system) eq ""} { - set hsess [twapi::evt_local_session] - } else { - set hsess [evt_open_session $opts(system) -authtype $opts(authtype)] - } - # evt_query will not read new events from a channel once - # eof is reached. So if reading in forward direction, we use - # evt_subscribe. Backward it does not matter. - if {$opts(direction) eq "forward"} { - lassign [evt_subscribe $opts(channel) -session $hsess -ignorequeryerrors -includeexisting] hq signal - dict set _winlog_handles $hq signal $signal - } else { - set hq [evt_query -session $hsess -channel $opts(channel) -ignorequeryerrors -direction $opts(direction)] - } - } - - dict set _winlog_handles $hq session $hsess - } else { - if {[info exists opts(file)]} { - set hq [eventlog_open -file $opts(file)] - dict set _winlog_handles $hq channel $opts(file) - } else { - set hq [eventlog_open -system $opts(system) -source $opts(channel)] - dict set _winlog_handles $hq channel $opts(channel) - } - dict set _winlog_handles $hq direction $opts(direction) - } - return $hq -} - -proc twapi::winlog_close {hq} { - variable _winlog_handles - - if {! [dict exists $_winlog_handles $hq]} { - error "Invalid event consumer handler '$hq'" - } - - if {[dict exists $_winlog_handles $hq signal]} { - # Catch in case app has closed event directly, for - # example when returned through winlog_subscribe - catch {close_handle [dict get $_winlog_handles $hq signal]} - } - if {[min_os_version 6]} { - set hsess [dict get $_winlog_handles $hq session] - evt_close $hq - evt_close_session $hsess - } else { - eventlog_close $hq - } - - dict unset _winlog_handles $hq - return -} - -proc twapi::winlog_event_count {args} { - # TBD - document and -authtype - array set opts [parseargs args { - {system.arg ""} - channel.arg - file.arg - {authtype.arg 0} - } -maxleftover 0] - - if {[info exists opts(file)] && - ($opts(system) ne "" || [info exists opts(channel)])} { - error "Option '-file' cannot be used with '-channel' or '-system'" - } else { - if {![info exists opts(channel)]} { - set opts(channel) "Application" - } - } - - if {[min_os_version 6]} { - # Use new Vista APIs - trap { - if {[info exists opts(file)]} { - set hsess NULL - set hevl [evt_open_log_info -file $opts(file)] - } else { - if {$opts(system) eq ""} { - set hsess [twapi::evt_local_session] - } else { - set hsess [evt_open_session $opts(system) -authtype $opts(authtype)] - } - set hevl [evt_open_log_info -session $hsess -channel $opts(channel)] - } - return [lindex [evt_log_info $hevl -numberoflogrecords] 1] - } finally { - if {[info exists hsess]} { - evt_close_session $hsess - } - if {[info exists hevl]} { - evt_close $hevl - } - } - } else { - if {[info exists opts(file)]} { - set hevl [eventlog_open -file $opts(file)] - } else { - set hevl [eventlog_open -system $opts(system) -source $opts(channel)] - } - - trap { - return [eventlog_count $hevl] - } finally { - eventlog_close $hevl - } - } -} - -if {[twapi::min_os_version 6]} { - - proc twapi::winlog_read {hq args} { - parseargs args { - {lcid.int 0} - } -setvars -maxleftover 0 - - # TBD - is 10 an appropriate number of events to read? - set events [evt_next $hq -timeout 0 -count 10 -status status] - if {[llength $events]} { - trap { - set result [evt_decode_events $events -lcid $lcid -ignorestring "" -message -levelname -taskname] - } finally { - evt_close {*}$events - } - return $result - } - - # No events were returned. Check status whether it is fatal error - # or not. SUCCESS, NO_MORE_ITEMS, TIMEOUT, INVALID_OPERATION - # are acceptable. This last happens when another EvtNext is done - # after an NO_MORE_ITEMS is already returned. - if {$status == 0 || $status == 259 || $status == 1460 || $status == 4317} { - # Even though $events is empty, still pass it in so it returns - # an empty record array in the correct format. - return [evt_decode_events $events -lcid $lcid -ignorestring "" -message -levelname -taskname] - } else { - win32_error $status - } - } - - proc twapi::winlog_subscribe {channelpath} { - variable _winlog_handles - lassign [evt_subscribe $channelpath -ignorequeryerrors] hq signal - dict set _winlog_handles $hq signal $signal - dict set _winlog_handles $hq session NULL; # local session - return [list $hq $signal] - } - - interp alias {} twapi::winlog_clear {} twapi::evt_clear_log - - proc twapi::winlog_backup {channel outpath} { - evt_export_log $outpath -channel $channel - return - } - -} else { - - proc twapi::winlog_read {hq args} { - parseargs args { - {lcid.int 0} - } -setvars -maxleftover 0 - - variable _winlog_handles - set fields {-channel -taskname -message -providername -eventid -level -levelname -eventrecordid -computer -sid -timecreated} - set values {} - set channel [dict get $_winlog_handles $hq channel] - foreach evl [eventlog_read $hq -direction [dict get $_winlog_handles $hq direction]] { - # Note order must be same as fields above - lappend values \ - [list \ - $channel \ - [eventlog_format_category $evl -langid $lcid] \ - [eventlog_format_message $evl -langid $lcid -width -1] \ - [dict get $evl -source] \ - [dict get $evl -eventid] \ - [dict get $evl -level] \ - [dict get $evl -type] \ - [dict get $evl -recordnum] \ - [dict get $evl -system] \ - [dict get $evl -sid] \ - [secs_since_1970_to_large_system_time [dict get $evl -timewritten]]] - } - return [list $fields $values] - } - - proc twapi::winlog_subscribe {source} { - variable _winlog_handles - lassign [eventlog_subscribe $source] hq hevent - dict set _winlog_handles $hq channel $source - dict set _winlog_handles $hq direction forward - dict set _winlog_handles $hq signal $hevent - return [list $hq $hevent] - } - - proc twapi::winlog_clear {source args} { - set hevl [eventlog_open -source $source] - trap { - eventlog_clear $hevl {*}$args - } finally { - eventlog_close $hevl - } - return - } - - proc twapi::winlog_backup {source outpath} { - set hevl [eventlog_open -source $source] - trap { - eventlog_backup $hevl $outpath - } finally { - eventlog_close $hevl - } - return - } - -} - - -proc twapi::_winlog_dump_list {{channels {Application System Security}} {atomize 0}} { - set evlist {} - foreach channel $channels { - set hevl [winlog_open -channel $channel] - trap { - while {[llength [set events [winlog_read $hevl]]]} { - foreach e [recordarray getlist $events -format dict] { - if {$atomize} { - dict set ev -message [atomize [dict get $e -message]] - dict set ev -levelname [atomize [dict get $e -levelname]] - dict set ev -channel [atomize [dict get $e -channel]] - dict set ev -providername [atomize [dict get $e -providername]] - dict set ev -taskname [atomize [dict get $e -taskname]] - dict set ev -eventid [atomize [dict get $e -eventid]] - dict set ev -account [atomize [dict get $e -userid]] - } else { - dict set ev -message [dict get $e -message] - dict set ev -levelname [dict get $e -levelname] - dict set ev -channel [dict get $e -channel] - dict set ev -providername [dict get $e -providername] - dict set ev -taskname [dict get $e -taskname] - dict set ev -eventid [dict get $e -eventid] - dict set ev -account [dict get $e -userid] - } - lappend evlist $ev - } - } - } finally { - winlog_close $hevl - } - } - return $evlist -} - -proc twapi::_winlog_dump {{channel Application} {fd stdout}} { - set hevl [winlog_open -channel $channel] - while {[llength [set events [winlog_read $hevl]]]} { - # print out each record - foreach ev [recordarray getlist $events -format dict] { - puts $fd "[dict get $ev -timecreated] [dict get $ev -providername]: [dict get $ev -message]" - } - } - winlog_close $hevl -} +# +# Copyright (c) 2012, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +# Routines to unify old and new Windows event log APIs + +namespace eval twapi { + # Dictionary to map eventlog consumer handles to various related info + # The primary key is the read handle to the event channel/source. + # Nested keys depend on OS version + variable _winlog_handles +} + +proc twapi::winlog_open {args} { + variable _winlog_handles + + # TBD - document -authtype + array set opts [parseargs args { + {system.arg ""} + channel.arg + file.arg + {authtype.arg 0} + {direction.arg forward {forward backward}} + } -maxleftover 0] + + if {[info exists opts(file)] && + ($opts(system) ne "" || [info exists opts(channel)])} { + error "Option '-file' cannot be used with '-channel' or '-system'" + } else { + if {![info exists opts(channel)]} { + set opts(channel) "Application" + } + } + + if {[min_os_version 6]} { + # Use new Vista APIs + if {[info exists opts(file)]} { + set hsess NULL + set hq [evt_query -file $opts(file) -ignorequeryerrors] + } else { + if {$opts(system) eq ""} { + set hsess [twapi::evt_local_session] + } else { + set hsess [evt_open_session $opts(system) -authtype $opts(authtype)] + } + # evt_query will not read new events from a channel once + # eof is reached. So if reading in forward direction, we use + # evt_subscribe. Backward it does not matter. + if {$opts(direction) eq "forward"} { + lassign [evt_subscribe $opts(channel) -session $hsess -ignorequeryerrors -includeexisting] hq signal + dict set _winlog_handles $hq signal $signal + } else { + set hq [evt_query -session $hsess -channel $opts(channel) -ignorequeryerrors -direction $opts(direction)] + } + } + + dict set _winlog_handles $hq session $hsess + } else { + if {[info exists opts(file)]} { + set hq [eventlog_open -file $opts(file)] + dict set _winlog_handles $hq channel $opts(file) + } else { + set hq [eventlog_open -system $opts(system) -source $opts(channel)] + dict set _winlog_handles $hq channel $opts(channel) + } + dict set _winlog_handles $hq direction $opts(direction) + } + return $hq +} + +proc twapi::winlog_close {hq} { + variable _winlog_handles + + if {! [dict exists $_winlog_handles $hq]} { + error "Invalid event consumer handler '$hq'" + } + + if {[dict exists $_winlog_handles $hq signal]} { + # Catch in case app has closed event directly, for + # example when returned through winlog_subscribe + catch {close_handle [dict get $_winlog_handles $hq signal]} + } + if {[min_os_version 6]} { + set hsess [dict get $_winlog_handles $hq session] + evt_close $hq + evt_close_session $hsess + } else { + eventlog_close $hq + } + + dict unset _winlog_handles $hq + return +} + +proc twapi::winlog_event_count {args} { + # TBD - document and -authtype + array set opts [parseargs args { + {system.arg ""} + channel.arg + file.arg + {authtype.arg 0} + } -maxleftover 0] + + if {[info exists opts(file)] && + ($opts(system) ne "" || [info exists opts(channel)])} { + error "Option '-file' cannot be used with '-channel' or '-system'" + } else { + if {![info exists opts(channel)]} { + set opts(channel) "Application" + } + } + + if {[min_os_version 6]} { + # Use new Vista APIs + trap { + if {[info exists opts(file)]} { + set hsess NULL + set hevl [evt_open_log_info -file $opts(file)] + } else { + if {$opts(system) eq ""} { + set hsess [twapi::evt_local_session] + } else { + set hsess [evt_open_session $opts(system) -authtype $opts(authtype)] + } + set hevl [evt_open_log_info -session $hsess -channel $opts(channel)] + } + return [lindex [evt_log_info $hevl -numberoflogrecords] 1] + } finally { + if {[info exists hsess]} { + evt_close_session $hsess + } + if {[info exists hevl]} { + evt_close $hevl + } + } + } else { + if {[info exists opts(file)]} { + set hevl [eventlog_open -file $opts(file)] + } else { + set hevl [eventlog_open -system $opts(system) -source $opts(channel)] + } + + trap { + return [eventlog_count $hevl] + } finally { + eventlog_close $hevl + } + } +} + +if {[twapi::min_os_version 6]} { + + proc twapi::winlog_read {hq args} { + parseargs args { + {lcid.int 0} + } -setvars -maxleftover 0 + + # TBD - is 10 an appropriate number of events to read? + set events [evt_next $hq -timeout 0 -count 10 -status status] + if {[llength $events]} { + trap { + set result [evt_decode_events $events -lcid $lcid -ignorestring "" -message -levelname -taskname] + } finally { + evt_close {*}$events + } + return $result + } + + # No events were returned. Check status whether it is fatal error + # or not. SUCCESS, NO_MORE_ITEMS, TIMEOUT, INVALID_OPERATION + # are acceptable. This last happens when another EvtNext is done + # after an NO_MORE_ITEMS is already returned. + if {$status == 0 || $status == 259 || $status == 1460 || $status == 4317} { + # Even though $events is empty, still pass it in so it returns + # an empty record array in the correct format. + return [evt_decode_events $events -lcid $lcid -ignorestring "" -message -levelname -taskname] + } else { + win32_error $status + } + } + + proc twapi::winlog_subscribe {channelpath} { + variable _winlog_handles + lassign [evt_subscribe $channelpath -ignorequeryerrors] hq signal + dict set _winlog_handles $hq signal $signal + dict set _winlog_handles $hq session NULL; # local session + return [list $hq $signal] + } + + interp alias {} twapi::winlog_clear {} twapi::evt_clear_log + + proc twapi::winlog_backup {channel outpath} { + evt_export_log $outpath -channel $channel + return + } + +} else { + + proc twapi::winlog_read {hq args} { + parseargs args { + {lcid.int 0} + } -setvars -maxleftover 0 + + variable _winlog_handles + set fields {-channel -taskname -message -providername -eventid -level -levelname -eventrecordid -computer -sid -timecreated} + set values {} + set channel [dict get $_winlog_handles $hq channel] + foreach evl [eventlog_read $hq -direction [dict get $_winlog_handles $hq direction]] { + # Note order must be same as fields above + lappend values \ + [list \ + $channel \ + [eventlog_format_category $evl -langid $lcid] \ + [eventlog_format_message $evl -langid $lcid -width -1] \ + [dict get $evl -source] \ + [dict get $evl -eventid] \ + [dict get $evl -level] \ + [dict get $evl -type] \ + [dict get $evl -recordnum] \ + [dict get $evl -system] \ + [dict get $evl -sid] \ + [secs_since_1970_to_large_system_time [dict get $evl -timewritten]]] + } + return [list $fields $values] + } + + proc twapi::winlog_subscribe {source} { + variable _winlog_handles + lassign [eventlog_subscribe $source] hq hevent + dict set _winlog_handles $hq channel $source + dict set _winlog_handles $hq direction forward + dict set _winlog_handles $hq signal $hevent + return [list $hq $hevent] + } + + proc twapi::winlog_clear {source args} { + set hevl [eventlog_open -source $source] + trap { + eventlog_clear $hevl {*}$args + } finally { + eventlog_close $hevl + } + return + } + + proc twapi::winlog_backup {source outpath} { + set hevl [eventlog_open -source $source] + trap { + eventlog_backup $hevl $outpath + } finally { + eventlog_close $hevl + } + return + } + +} + + +proc twapi::_winlog_dump_list {{channels {Application System Security}} {atomize 0}} { + set evlist {} + foreach channel $channels { + set hevl [winlog_open -channel $channel] + trap { + while {[llength [set events [winlog_read $hevl]]]} { + foreach e [recordarray getlist $events -format dict] { + if {$atomize} { + dict set ev -message [atomize [dict get $e -message]] + dict set ev -levelname [atomize [dict get $e -levelname]] + dict set ev -channel [atomize [dict get $e -channel]] + dict set ev -providername [atomize [dict get $e -providername]] + dict set ev -taskname [atomize [dict get $e -taskname]] + dict set ev -eventid [atomize [dict get $e -eventid]] + dict set ev -account [atomize [dict get $e -userid]] + } else { + dict set ev -message [dict get $e -message] + dict set ev -levelname [dict get $e -levelname] + dict set ev -channel [dict get $e -channel] + dict set ev -providername [dict get $e -providername] + dict set ev -taskname [dict get $e -taskname] + dict set ev -eventid [dict get $e -eventid] + dict set ev -account [dict get $e -userid] + } + lappend evlist $ev + } + } + } finally { + winlog_close $hevl + } + } + return $evlist +} + +proc twapi::_winlog_dump {{channel Application} {fd stdout}} { + set hevl [winlog_open -channel $channel] + while {[llength [set events [winlog_read $hevl]]]} { + # print out each record + foreach ev [recordarray getlist $events -format dict] { + puts $fd "[dict get $ev -timecreated] [dict get $ev -providername]: [dict get $ev -message]" + } + } + winlog_close $hevl +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/winsta.tcl b/src/vendorlib_tcl8/twapi-5.0b1/winsta.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/winsta.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/winsta.tcl index 3383e414..6d2da11b 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/winsta.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/winsta.tcl @@ -1,113 +1,113 @@ -# -# Copyright (c) 2004-2012, Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - - -# TBD - document and test -proc twapi::get_active_console_tssession {} { - return [WTSGetActiveConsoleSessionId] -} - -proc twapi::get_current_window_station_handle {} { - return [GetProcessWindowStation] -} - -# Get the handle to a window station -proc twapi::get_window_station_handle {winsta args} { - array set opts [parseargs args { - inherit.bool - {access.arg generic_read} - } -nulldefault] - - set access_rights [_access_rights_to_mask $opts(access)] - - return [OpenWindowStation $winsta $opts(inherit) $access_rights] -} - - -# Close a window station handle -proc twapi::close_window_station_handle {hwinsta} { - # Trying to close our window station handle will generate an error - if {$hwinsta != [get_current_window_station_handle]} { - CloseWindowStation $hwinsta - } - return -} - -# List all window stations -proc twapi::find_window_stations {} { - return [EnumWindowStations] -} - - -# Enumerate desktops in a window station -proc twapi::find_desktops {args} { - array set opts [parseargs args {winsta.arg}] - - if {[info exists opts(winsta)]} { - set hwinsta [get_window_station_handle $opts(winsta)] - } else { - set hwinsta [get_current_window_station_handle] - } - - trap { - return [EnumDesktops $hwinsta] - } finally { - # Note close_window_station_handle protects against - # hwinsta being the current window station handle so - # we do not need to do that check here - close_window_station_handle $hwinsta - } -} - - -# Get the handle to a desktop -proc twapi::get_desktop_handle {desk args} { - array set opts [parseargs args { - inherit.bool - allowhooks.bool - {access.arg generic_read} - } -nulldefault] - - set access_mask [_access_rights_to_mask $opts(access)] - - # If certain access rights are specified, we must add certain other - # access rights. See OpenDesktop SDK docs - set access_rights [_access_mask_to_rights $access_mask] - if {"read_control" in $access_rights || - "write_dacl" in $access_rights || - "write_owner" in $access_rights} { - lappend access_rights desktop_readobject desktop_writeobjects - set access_mask [_access_rights_to_mask $opts(access)] - } - - return [OpenDesktop $desk $opts(allowhooks) $opts(inherit) $access_mask] -} - -# Close the desktop handle -proc twapi::close_desktop_handle {hdesk} { - CloseDesktop $hdesk -} - -# Set the process window station -proc twapi::set_process_window_station {hwinsta} { - SetProcessWindowStation $hwinsta -} - -proc twapi::get_desktop_user_sid {hdesk} { - return [GetUserObjectInformation $hdesk 4] -} - -proc twapi::get_window_station_user_sid {hwinsta} { - return [GetUserObjectInformation $hwinsta 4] -} - -proc twapi::get_desktop_name {hdesk} { - return [GetUserObjectInformation $hdesk 2] -} - -proc twapi::get_window_station_name {hwinsta} { - return [GetUserObjectInformation $hwinsta 2] -} +# +# Copyright (c) 2004-2012, Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + + +# TBD - document and test +proc twapi::get_active_console_tssession {} { + return [WTSGetActiveConsoleSessionId] +} + +proc twapi::get_current_window_station_handle {} { + return [GetProcessWindowStation] +} + +# Get the handle to a window station +proc twapi::get_window_station_handle {winsta args} { + array set opts [parseargs args { + inherit.bool + {access.arg generic_read} + } -nulldefault] + + set access_rights [_access_rights_to_mask $opts(access)] + + return [OpenWindowStation $winsta $opts(inherit) $access_rights] +} + + +# Close a window station handle +proc twapi::close_window_station_handle {hwinsta} { + # Trying to close our window station handle will generate an error + if {$hwinsta != [get_current_window_station_handle]} { + CloseWindowStation $hwinsta + } + return +} + +# List all window stations +proc twapi::find_window_stations {} { + return [EnumWindowStations] +} + + +# Enumerate desktops in a window station +proc twapi::find_desktops {args} { + array set opts [parseargs args {winsta.arg}] + + if {[info exists opts(winsta)]} { + set hwinsta [get_window_station_handle $opts(winsta)] + } else { + set hwinsta [get_current_window_station_handle] + } + + trap { + return [EnumDesktops $hwinsta] + } finally { + # Note close_window_station_handle protects against + # hwinsta being the current window station handle so + # we do not need to do that check here + close_window_station_handle $hwinsta + } +} + + +# Get the handle to a desktop +proc twapi::get_desktop_handle {desk args} { + array set opts [parseargs args { + inherit.bool + allowhooks.bool + {access.arg generic_read} + } -nulldefault] + + set access_mask [_access_rights_to_mask $opts(access)] + + # If certain access rights are specified, we must add certain other + # access rights. See OpenDesktop SDK docs + set access_rights [_access_mask_to_rights $access_mask] + if {"read_control" in $access_rights || + "write_dacl" in $access_rights || + "write_owner" in $access_rights} { + lappend access_rights desktop_readobject desktop_writeobjects + set access_mask [_access_rights_to_mask $opts(access)] + } + + return [OpenDesktop $desk $opts(allowhooks) $opts(inherit) $access_mask] +} + +# Close the desktop handle +proc twapi::close_desktop_handle {hdesk} { + CloseDesktop $hdesk +} + +# Set the process window station +proc twapi::set_process_window_station {hwinsta} { + SetProcessWindowStation $hwinsta +} + +proc twapi::get_desktop_user_sid {hdesk} { + return [GetUserObjectInformation $hdesk 4] +} + +proc twapi::get_window_station_user_sid {hwinsta} { + return [GetUserObjectInformation $hwinsta 4] +} + +proc twapi::get_desktop_name {hdesk} { + return [GetUserObjectInformation $hdesk 2] +} + +proc twapi::get_window_station_name {hwinsta} { + return [GetUserObjectInformation $hwinsta 2] +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/wmi.tcl b/src/vendorlib_tcl8/twapi-5.0b1/wmi.tcl similarity index 96% rename from src/vendorlib_tcl8/twapi4.7.2/wmi.tcl rename to src/vendorlib_tcl8/twapi-5.0b1/wmi.tcl index e31debb4..61c61a08 100644 --- a/src/vendorlib_tcl8/twapi4.7.2/wmi.tcl +++ b/src/vendorlib_tcl8/twapi-5.0b1/wmi.tcl @@ -1,223 +1,223 @@ -# -# Copyright (c) 2012 Ashok P. Nadkarni -# All rights reserved. -# -# See the file LICENSE for license - -package require twapi_com - -# TBD - document? - -twapi::class create ::twapi::IMofCompilerProxy { - superclass ::twapi::IUnknownProxy - - constructor {args} { - if {[llength $args] == 0} { - set args [list [::twapi::com_create_instance "{6daf9757-2e37-11d2-aec9-00c04fb68820}" -interface IMofCompiler -raw]] - } - next {*}$args - } - - method CompileBuffer args { - my variable _ifc - return [::twapi::IMofCompiler_CompileBuffer $_ifc {*}$args] - } - - method CompileFile args { - my variable _ifc - return [::twapi::IMofCompiler_CompileFile $_ifc {*}$args] - } - - method CreateBMOF args { - my variable _ifc - return [::twapi::IMofCompiler_CreateBMOF $_ifc {*}$args] - } - - twapi_exportall -} - - -# -# Get WMI service - TBD document -proc twapi::wmi_root {args} { - array set opts [parseargs args { - {root.arg cimv2} - {impersonationlevel.arg impersonate {default anonymous identify delegate impersonate} } - } -maxleftover 0] - - # TBD - any injection attacks possible ? Need to quote ? - return [comobj_object "winmgmts:{impersonationLevel=$opts(impersonationlevel)}!//./root/$opts(root)"] -} -# Backwards compat -proc twapi::_wmi {{top cimv2}} { - return [wmi_root -root $top] -} - -# TBD - see if using ExecQuery would be faster if it supports all the options -proc twapi::wmi_collect_classes {swbemservices args} { - array set opts [parseargs args { - {ancestor.arg {}} - shallow - first - matchproperties.arg - matchsystemproperties.arg - matchqualifiers.arg - {collector.arg {lindex}} - } -maxleftover 0] - - - # Create a forward only enumerator for efficiency - # wbemFlagUseAmendedQualifiers | wbemFlagReturnImmediately | wbemFlagForwardOnly - set flags 0x20030 - if {$opts(shallow)} { - incr flags 1; # 0x1 -> wbemQueryFlagShallow - } - - set classes [$swbemservices SubclassesOf $opts(ancestor) $flags] - set matches {} - set delete_on_error {} - twapi::trap { - $classes -iterate class { - set matched 1 - foreach {opt fn} { - matchproperties Properties_ - matchsystemproperties SystemProperties_ - matchqualifiers Qualifiers_ - } { - if {[info exists opts($opt)]} { - foreach {name matcher} $opts($opt) { - if {[catch { - if {! [{*}$matcher [$class -with [list [list -get $fn] [list Item $name]] Value]]} { - set matched 0 - break; # Value does not match - } - } msg ]} { - # TBD - log debug error if not property found - # No such property or no access - set matched 0 - break - } - } - } - if {! $matched} { - # Already failed to match, no point continuing looping - break - } - } - - if {$matched} { - # Note collector code is responsible for disposing - # of $class as appropriate. But we take care of deleting - # when an error occurs after some accumulation has - # already occurred. - lappend delete_on_error $class - if {$opts(first)} { - return [{*}$opts(collector) $class] - } else { - lappend matches [{*}$opts(collector) $class] - } - } else { - $class destroy - } - } - } onerror {} { - foreach class $delete_on_error { - if {[comobj? $class]} { - $class destroy - } - } - rethrow - } finally { - $classes destroy - } - - return $matches -} - -proc twapi::wmi_extract_qualifier {qual} { - foreach prop {name value isamended propagatestoinstance propagatestosubclass isoverridable} { - dict set result $prop [$qual -get $prop] - } - return $result -} - -proc twapi::wmi_extract_property {propobj} { - foreach prop {name value cimtype isarray islocal origin} { - dict set result $prop [$propobj -get $prop] - } - - $propobj -with Qualifiers_ -iterate -cleanup qual { - set rec [wmi_extract_qualifier $qual] - dict set result qualifiers [string tolower [dict get $rec name]] $rec - } - - return $result -} - -proc twapi::wmi_extract_systemproperty {propobj} { - # Separate from wmi_extract_property because system properties do not - # have Qualifiers_ - foreach prop {name value cimtype isarray islocal origin} { - dict set result $prop [$propobj -get $prop] - } - - return $result -} - - -proc twapi::wmi_extract_method {mobj} { - foreach prop {name origin} { - dict set result $prop [$mobj -get $prop] - } - - # The InParameters and OutParameters properties are SWBEMObjects - # the properties of which describe the parameters. - foreach inout {inparameters outparameters} { - set paramsobj [$mobj -get $inout] - if {[$paramsobj -isnull]} { - dict set result $inout {} - } else { - $paramsobj -with Properties_ -iterate -cleanup pobj { - set rec [wmi_extract_property $pobj] - dict set result $inout [string tolower [dict get $rec name]] $rec - } - } - } - - $mobj -with Qualifiers_ -iterate qual { - set rec [wmi_extract_qualifier $qual] - dict set result qualifiers [string tolower [dict get $rec name]] $rec - $qual destroy - } - - return $result -} - - -proc twapi::wmi_extract_class {obj} { - - set result [dict create] - - # Class qualifiers - $obj -with Qualifiers_ -iterate -cleanup qualobj { - set rec [wmi_extract_qualifier $qualobj] - dict set result qualifiers [string tolower [dict get $rec name]] $rec - } - - $obj -with Properties_ -iterate -cleanup propobj { - set rec [wmi_extract_property $propobj] - dict set result properties [string tolower [dict get $rec name]] $rec - } - - $obj -with SystemProperties_ -iterate -cleanup propobj { - set rec [wmi_extract_systemproperty $propobj] - dict set result systemproperties [string tolower [dict get $rec name]] $rec - } - - $obj -with Methods_ -iterate -cleanup mobj { - set rec [wmi_extract_method $mobj] - dict set result methods [string tolower [dict get $rec name]] $rec - } - - return $result -} +# +# Copyright (c) 2012 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +package require twapi_com + +# TBD - document? + +twapi::class create ::twapi::IMofCompilerProxy { + superclass ::twapi::IUnknownProxy + + constructor {args} { + if {[llength $args] == 0} { + set args [list [::twapi::com_create_instance "{6daf9757-2e37-11d2-aec9-00c04fb68820}" -interface IMofCompiler -raw]] + } + next {*}$args + } + + method CompileBuffer args { + my variable _ifc + return [::twapi::IMofCompiler_CompileBuffer $_ifc {*}$args] + } + + method CompileFile args { + my variable _ifc + return [::twapi::IMofCompiler_CompileFile $_ifc {*}$args] + } + + method CreateBMOF args { + my variable _ifc + return [::twapi::IMofCompiler_CreateBMOF $_ifc {*}$args] + } + + twapi_exportall +} + + +# +# Get WMI service - TBD document +proc twapi::wmi_root {args} { + array set opts [parseargs args { + {root.arg cimv2} + {impersonationlevel.arg impersonate {default anonymous identify delegate impersonate} } + } -maxleftover 0] + + # TBD - any injection attacks possible ? Need to quote ? + return [comobj_object "winmgmts:{impersonationLevel=$opts(impersonationlevel)}!//./root/$opts(root)"] +} +# Backwards compat +proc twapi::_wmi {{top cimv2}} { + return [wmi_root -root $top] +} + +# TBD - see if using ExecQuery would be faster if it supports all the options +proc twapi::wmi_collect_classes {swbemservices args} { + array set opts [parseargs args { + {ancestor.arg {}} + shallow + first + matchproperties.arg + matchsystemproperties.arg + matchqualifiers.arg + {collector.arg {lindex}} + } -maxleftover 0] + + + # Create a forward only enumerator for efficiency + # wbemFlagUseAmendedQualifiers | wbemFlagReturnImmediately | wbemFlagForwardOnly + set flags 0x20030 + if {$opts(shallow)} { + incr flags 1; # 0x1 -> wbemQueryFlagShallow + } + + set classes [$swbemservices SubclassesOf $opts(ancestor) $flags] + set matches {} + set delete_on_error {} + twapi::trap { + $classes -iterate class { + set matched 1 + foreach {opt fn} { + matchproperties Properties_ + matchsystemproperties SystemProperties_ + matchqualifiers Qualifiers_ + } { + if {[info exists opts($opt)]} { + foreach {name matcher} $opts($opt) { + if {[catch { + if {! [{*}$matcher [$class -with [list [list -get $fn] [list Item $name]] Value]]} { + set matched 0 + break; # Value does not match + } + } msg ]} { + # TBD - log debug error if not property found + # No such property or no access + set matched 0 + break + } + } + } + if {! $matched} { + # Already failed to match, no point continuing looping + break + } + } + + if {$matched} { + # Note collector code is responsible for disposing + # of $class as appropriate. But we take care of deleting + # when an error occurs after some accumulation has + # already occurred. + lappend delete_on_error $class + if {$opts(first)} { + return [{*}$opts(collector) $class] + } else { + lappend matches [{*}$opts(collector) $class] + } + } else { + $class destroy + } + } + } onerror {} { + foreach class $delete_on_error { + if {[comobj? $class]} { + $class destroy + } + } + rethrow + } finally { + $classes destroy + } + + return $matches +} + +proc twapi::wmi_extract_qualifier {qual} { + foreach prop {name value isamended propagatestoinstance propagatestosubclass isoverridable} { + dict set result $prop [$qual -get $prop] + } + return $result +} + +proc twapi::wmi_extract_property {propobj} { + foreach prop {name value cimtype isarray islocal origin} { + dict set result $prop [$propobj -get $prop] + } + + $propobj -with Qualifiers_ -iterate -cleanup qual { + set rec [wmi_extract_qualifier $qual] + dict set result qualifiers [string tolower [dict get $rec name]] $rec + } + + return $result +} + +proc twapi::wmi_extract_systemproperty {propobj} { + # Separate from wmi_extract_property because system properties do not + # have Qualifiers_ + foreach prop {name value cimtype isarray islocal origin} { + dict set result $prop [$propobj -get $prop] + } + + return $result +} + + +proc twapi::wmi_extract_method {mobj} { + foreach prop {name origin} { + dict set result $prop [$mobj -get $prop] + } + + # The InParameters and OutParameters properties are SWBEMObjects + # the properties of which describe the parameters. + foreach inout {inparameters outparameters} { + set paramsobj [$mobj -get $inout] + if {[$paramsobj -isnull]} { + dict set result $inout {} + } else { + $paramsobj -with Properties_ -iterate -cleanup pobj { + set rec [wmi_extract_property $pobj] + dict set result $inout [string tolower [dict get $rec name]] $rec + } + } + } + + $mobj -with Qualifiers_ -iterate qual { + set rec [wmi_extract_qualifier $qual] + dict set result qualifiers [string tolower [dict get $rec name]] $rec + $qual destroy + } + + return $result +} + + +proc twapi::wmi_extract_class {obj} { + + set result [dict create] + + # Class qualifiers + $obj -with Qualifiers_ -iterate -cleanup qualobj { + set rec [wmi_extract_qualifier $qualobj] + dict set result qualifiers [string tolower [dict get $rec name]] $rec + } + + $obj -with Properties_ -iterate -cleanup propobj { + set rec [wmi_extract_property $propobj] + dict set result properties [string tolower [dict get $rec name]] $rec + } + + $obj -with SystemProperties_ -iterate -cleanup propobj { + set rec [wmi_extract_systemproperty $propobj] + dict set result systemproperties [string tolower [dict get $rec name]] $rec + } + + $obj -with Methods_ -iterate -cleanup mobj { + set rec [wmi_extract_method $mobj] + dict set result methods [string tolower [dict get $rec name]] $rec + } + + return $result +} diff --git a/src/vendorlib_tcl8/twapi-5.0b1/wts.tcl b/src/vendorlib_tcl8/twapi-5.0b1/wts.tcl new file mode 100644 index 00000000..50a077d9 --- /dev/null +++ b/src/vendorlib_tcl8/twapi-5.0b1/wts.tcl @@ -0,0 +1,64 @@ + +# Copyright (c) 2021 Ashok P. Nadkarni +# All rights reserved. +# +# See the file LICENSE for license + +namespace eval twapi { + variable _wts_session_monitors + set _wts_session_monitors [dict create] +} + +proc twapi::start_wts_session_monitor {script args} { + variable _wts_session_monitors + + parseargs args { + all + } -maxleftover 0 -setvars] + + set script [lrange $script 0 end]; # Verify syntactically a list + + set id "wts#[TwapiId]" + if {[dict size $_wts_session_monitors] == 0} { + # No monitoring in progress. Start it + # 0x2B1 -> WM_WTSSESSION_CHANGE + Twapi_WTSRegisterSessionNotification $all + _register_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_change_handler] 0 + } + + dict set _wts_session_monitors $id $script + return $id +} + + +proc twapi::stop_wts_session_monitor {id} { + variable _wts_session_monitors + + if {![dict exists $_wts_session_monitors $id]} { + return + } + + dict unset _wts_session_monitors $id + if {[dict size $_wts_session_monitors] == 0} { + # 0x2B1 -> WM_WTSSESSION_CHANGE + _unregister_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_handler] + Twapi_WTSUnRegisterSessionNotification + } +} + +proc twapi::_wts_session_change_handler {msg change session_id msgpos ticks} { + variable _wts_session_monitors + + if {[dict size $_wts_session_monitors] == 0} { + return; # Not an error, could have deleted while already queued + } + + dict for {id script} $_wts_session_monitors { + set code [catch {uplevel #0 [linsert $script end $change $session_id]} msg] + if {$code == 1} { + # Error - put in background but we do not abort + after 0 [list error $msg $::errorInfo $::errorCode] + } + } + return +} diff --git a/src/vendorlib_tcl8/twapi4.7.2/metoo.tcl b/src/vendorlib_tcl8/twapi4.7.2/metoo.tcl deleted file mode 100644 index 91a32e5a..00000000 --- a/src/vendorlib_tcl8/twapi4.7.2/metoo.tcl +++ /dev/null @@ -1,605 +0,0 @@ -# MeTOO stands for "MeTOO Emulates TclOO" (at a superficial syntactic level) -# -# Implements a *tiny*, but useful, subset of TclOO, primarily for use -# with Tcl 8.4. Intent is that if you write code using MeToo, it should work -# unmodified with TclOO in 8.5/8.6. Obviously, don't try going the other way! -# -# Emulation is superficial, don't try to be too clever in usage. -# Doing funky, or even non-funky, things with object namespaces will -# not work as you would expect. -# -# See the metoo::demo proc for sample usage. Calling this proc -# with parameter "oo" will use the TclOO commands. Else the metoo:: -# commands. Note the demo code remains the same for both. -# -# The following fragment uses MeToo only if TclOO is not available: -# if {[llength [info commands oo::*]]} { -# namespace import oo::* -# } else { -# source metoo.tcl -# namespace import metoo::class -# } -# class create C {...} -# -# Summary of the TclOO subset implemented - see TclOO docs for detail : -# -# Creating a new class: -# metoo::class create CLASSNAME CLASSDEFINITION -# -# Destroying a class: -# CLASSNAME destroy -# - this also destroys objects of that class and recursively destroys -# child classes. NOTE: deleting the class namespace or renaming -# the CLASSNAME command to "" will NOT call object destructors. -# -# CLASSDEFINITION: Following may appear in CLASSDEFINTION -# method METHODNAME params METHODBODY -# - same as TclOO -# constructor params METHODBODY -# - same syntax as TclOO -# destructor METHODBODY -# - same syntax as TclOO -# unknown METHODNAME ARGS -# - if defined, called when an undefined method is invoked -# superclass SUPER -# - inherits from SUPER. Unlike TclOO, only single inheritance. Also -# no checks for inheritance loops. You'll find out quickly enough! -# All other commands within a CLASSDEFINITION will either raise error or -# work differently from TclOO. Actually you can use pretty much any -# Tcl command inside CLASSDEFINITION but the results may not be what you -# expect. Best to avoid this. -# -# METHODBODY: The following method-internal TclOO commands are available: -# my METHODNAME ARGS -# - to call another method METHODNAME -# my variable VAR1 ?VAR2...? -# - brings object-specific variables into scope -# next ?ARGS? -# - calls the superclass method of the same name -# self -# self object -# - returns the object name (usable as a command) -# self class -# - returns class of this object -# self namespace -# - returns namespace of this object -# -# Creating objects: -# CLASSNAME create OBJNAME ?ARGS? -# - creates object OBJNAME of class CLASSNAME, passing ARGS to constructor -# Returns the fully qualified object name that can be used as a command. -# CLASSNAME new ?ARGS? -# - creates a new object with an auto-generated name -# -# Destroying objects -# OBJNAME destroy -# - destroys the object calling destructors -# rename OBJNAME "" -# - same as above -# -# Renaming an object -# rename OBJNAME NEWNAME -# - the object can now be invoked using the new name. Note this is unlike -# classes which should not be renamed. -# -# -# Introspection (though different from TclOO) -# metoo::introspect object isa OBJECT ?CLASSNAME? -# - returns 1 if OBJECT is a metoo object and is of the specified class -# if CLASSNAME is specified. Returns 0 otherwise. -# metoo::introspect object list -# - returns list of all objects -# metoo::introspect class ancestors CLASSNAME -# - returns list of ancestors for a class -# -# Differences and missing features from TclOO: Everything not listed above -# is missing. Some notable differences: -# - MeTOO is class-based, not object based like TclOO, thus class instances -# (objects) cannot be modified by adding instance-specific methods etc.. -# Also a class is not itself an object. -# - Renaming classes does not work and will fail in mysterious ways -# - does not support class refinement/definition -# - no variable command at class level for automatically bringing variables -# into scope -# - no filters, forwarding, multiple-inheritance -# - no private methods (all methods are exported). - -# NOTE: file must be sourced at global level since metoo namespace is expected -# to be top level namespace - -# DO NOT DO THIS. ELSE TESTS FAIL BECAUSE they define tests in the -# metoo namespace which then get deleted by the line below when -# the package is lazy auto-loaded -# catch {namespace delete metoo} - -# TBD - variable ("my variable" is done, "variable" in method or -# class definition is not) -# TBD - default constructor and destructor to "next" (or maybe that -# is already taken care of by the inheritance code - -namespace eval metoo { - variable next_id 0 - - variable _objects; # Maps objects to its namespace - array set _objects {} - -} - -# Namespace in which commands in a class definition block are called -namespace eval metoo::define { - proc method {class_ns name params body} { - # Methods are defined in the methods subspace of the class namespace. - # We prefix with _m_ to prevent them from being directly called - # as procs, for example if the method is a Tcl command like "set" - # The first parameter to a method is always the object namespace - # denoted as the paramter "_this" - namespace eval ${class_ns}::methods [list proc _m_$name [concat [list _this] $params] $body] - - } - proc superclass {class_ns superclass} { - if {[info exists ${class_ns}::super]} { - error "Only one superclass allowed for a class" - } - set sup [uplevel 3 "namespace eval $superclass {namespace current}"] - set ${class_ns}::super $sup - # We store the subclass in the super so it can be destroyed - # if the super is destroyed. - set ${sup}::subclasses($class_ns) 1 - } - proc constructor {class_ns params body} { - method $class_ns constructor $params $body - } - proc destructor {class_ns body} { - method $class_ns destructor {} $body - } - proc export {args} { - # Nothing to do, all methods are exported anyways - # Command is here for compatibility only - } -} - -# Namespace in which commands used in objects methods are defined -# (self, my etc.) -namespace eval metoo::object { - proc next {args} { - upvar 1 _this this; # object namespace - - # Figure out what class context this is executing in. Note - # we cannot use _this in caller since that is the object namespace - # which is not necessarily related to the current class namespace. - set class_ns [namespace parent [uplevel 1 {namespace current}]] - - # Figure out the current method being called - set methodname [namespace tail [lindex [uplevel 1 {info level 0}] 0]] - - # Find the next method in the class hierarchy and call it - while {[info exists ${class_ns}::super]} { - set class_ns [set ${class_ns}::super] - if {[llength [info commands ${class_ns}::methods::$methodname]]} { - return [uplevel 1 [list ${class_ns}::methods::$methodname $this] $args] - } - } - - error "'next' command has no receiver in the hierarchy for method $methodname" - } - - proc self {{what object}} { - upvar 1 _this this - switch -exact -- $what { - class { return [namespace parent $this] } - namespace { return $this } - object { return [set ${this}::_(name)] } - default { - error "Argument '$what' not understood by self method" - } - } - } - - proc my {methodname args} { - # We insert the object namespace as the first parameter to the command. - # This is passed as the first parameter "_this" to methods. Since - # "my" can be only called from methods, we can retrieve it fro - # our caller. - upvar 1 _this this; # object namespace - - set class_ns [namespace parent $this] - - set meth [::metoo::_locate_method $class_ns $methodname] - if {$meth ne ""} { - # We need to invoke in the caller's context so upvar etc. will - # not be affected by this intermediate method dispatcher - return [uplevel 1 [list $meth $this] $args] - } - - # It is ok for constructor or destructor to be undefined. For - # the others, invoke "unknown" if it exists - if {$methodname eq "constructor" || $methodname eq "destructor"} { - return - } - - set meth [::metoo::_locate_method $class_ns "unknown"] - if {$meth ne ""} { - # We need to invoke in the caller's context so upvar etc. will - # not be affected by this intermediate method dispatcher - return [uplevel 1 [list $meth $this $methodname] $args] - } - - error "Unknown method $methodname" - } -} - -# Given a method name, locate it in the class hierarchy. Returns -# fully qualified method if found, else an empty string -proc metoo::_locate_method {class_ns methodname} { - # See if there is a method defined in this class. - # Breakage if method names with wildcard chars. Too bad - if {[llength [info commands ${class_ns}::methods::_m_$methodname]]} { - # We need to invoke in the caller's context so upvar etc. will - # not be affected by this intermediate method dispatcher - return ${class_ns}::methods::_m_$methodname - } - - # No method here, check for super class. - while {[info exists ${class_ns}::super]} { - set class_ns [set ${class_ns}::super] - if {[llength [info commands ${class_ns}::methods::_m_$methodname]]} { - return ${class_ns}::methods::_m_$methodname - } - } - - return ""; # Not found -} - -proc metoo::_new {class_ns cmd args} { - # class_ns expected to be fully qualified - variable next_id - - # IMPORTANT: - # object namespace *must* be child of class namespace. - # Saves a bit of bookkeeping. Putting it somewhere else will require - # changes to many other places in the code. - set objns ${class_ns}::o#[incr next_id] - - switch -exact -- $cmd { - create { - if {[llength $args] < 1} { - error "Insufficient args, should be: class create CLASSNAME ?args?" - } - # TBD - check if command already exists - # Note objname must always be fully qualified. Note cannot - # use namespace which here because the commmand does not - # yet exist. - set args [lassign $args objname] - if {[string compare :: [string range $objname 0 1]]} { - # Not fully qualified. Qualify based on caller namespace - set objname [uplevel 1 "namespace current"]::$objname - } - # Trip excess ":" - can happen in both above cases - set objname ::[string trimleft $objname :] - } - new { - set objname $objns - } - default { - error "Unknown command '$cmd'. Should be create or new." - } - } - - # Create the namespace. The array _ is used to hold private information - namespace eval $objns { - variable _ - } - set ${objns}::_(name) $objname - - # When invoked by its name, call the dispatcher. - interp alias {} $objname {} ${class_ns}::_call $objns - - # Register the object. We do this BEFORE running the constructor - variable _objects - set _objects($objname) $objns - - # Invoke the constructor - if {[catch { - $objname constructor {*}$args - } msg]} { - # Undo what we did - set erinfo $::errorInfo - set ercode $::errorCode - rename $objname "" - namespace delete $objns - error $msg $erinfo $ercode - } - - # TBD - does tracing cause a slowdown ? - # Set up trace to track when the object is renamed/destroyed - trace add command $objname {rename delete} [list [namespace current]::_trace_object_renames $objns] - - return $objname -} - -proc metoo::_trace_object_renames {objns oldname newname op} { - # Note the trace command fully qualifies oldname and newname - if {$op eq "rename"} { - variable _objects - set _objects($newname) $_objects($oldname) - unset _objects($oldname) - set ${objns}::_(name) $newname - } else { - $oldname destroy - } -} - -proc metoo::_class_cmd {class_ns cmd args} { - switch -exact -- $cmd { - create - - new { - return [uplevel 1 [list [namespace current]::_new $class_ns $cmd] $args] - } - destroy { - # Destroy all objects belonging to this class - foreach objns [namespace children ${class_ns} o#*] { - [set ${objns}::_(name)] destroy - } - # Destroy all classes that inherit from this - foreach child_ns [array names ${class_ns}::subclasses] { - # Child namespace is also subclass command - $child_ns destroy - } - trace remove command $class_ns {rename delete} [list ::metoo::_trace_class_renames] - namespace delete ${class_ns} - rename ${class_ns} "" - } - default { - error "Unknown command '$cmd'. Should be create, new or destroy." - } - } -} - -proc metoo::class {cmd cname definition} { - variable next_id - - if {$cmd ne "create"} { - error "Syntax: class create CLASSNAME DEFINITION" - } - - if {[uplevel 1 "namespace exists $cname"]} { - error "can't create class '$cname': namespace already exists with that name." - } - - # Resolve cname into a namespace in the caller's context - set class_ns [uplevel 1 "namespace eval $cname {namespace current}"] - - if {[llength [info commands $class_ns]]} { - # Delete the namespace we just created - namespace delete $class_ns - error "can't create class '$cname': command already exists with that name." - } - - # Define the commands/aliases that are used inside a class definition - foreach procname [info commands [namespace current]::define::*] { - interp alias {} ${class_ns}::[namespace tail $procname] {} $procname $class_ns - } - - # Define the built in commands callable within class instance methods - foreach procname [info commands [namespace current]::object::*] { - interp alias {} ${class_ns}::methods::[namespace tail $procname] {} $procname - } - - # Define the destroy method for the class object instances - namespace eval $class_ns { - method destroy {} { - set retval [my destructor] - # Remove trace on command rename/deletion. - # ${_this}::_(name) contains the object's current name on - # which the trace is set. - set me [set ${_this}::_(name)] - trace remove command $me {rename delete} [list ::metoo::_trace_object_renames $_this] - rename $me "" - unset -nocomplain ::metoo::_objects($me) - namespace delete $_this - return $retval - } - method variable {args} { - if {[llength $args]} { - set cmd [list upvar 0] - foreach varname $args { - lappend cmd ${_this}::$varname $varname - } - uplevel 1 $cmd - } - } - } - - # Define the class. Note we do this *after* the standard - # definitions (destroy etc.) above so that they can - # be overridden by the class definition. - if {[catch { - namespace eval $class_ns $definition - } msg ]} { - namespace delete $class_ns - error $msg $::errorInfo $::errorCode - } - - # Also define the call dispatcher within the class. - # TBD - not sure this is actually necessary any more - namespace eval ${class_ns} { - proc _call {objns methodname args} { - # Note this duplicates the "my" code but cannot call that as - # it adds another frame level which interferes with uplevel etc. - - set class_ns [namespace parent $objns] - - # We insert the object namespace as the first param to the command. - # This is passed as the first parameter "_this" to methods. - - set meth [::metoo::_locate_method $class_ns $methodname] - if {$meth ne ""} { - # We need to invoke in the caller's context so upvar etc. will - # not be affected by this intermediate method dispatcher - return [uplevel 1 [list $meth $objns] $args] - } - - # It is ok for constructor or destructor to be undefined. For - # the others, invoke "unknown" if it exists - - if {$methodname eq "constructor" || $methodname eq "destructor"} { - return - } - - set meth [::metoo::_locate_method $class_ns "unknown"] - if {$meth ne ""} { - # We need to invoke in the caller's context so upvar etc. will - # not be affected by this intermediate method dispatcher - return [uplevel 1 [list $meth $objns $methodname] $args] - } - - error "Unknown method $methodname" - } - } - - # The namespace is also a command used to create class instances - # TBD - check if command of that name already exists - interp alias {} $class_ns {} [namespace current]::_class_cmd $class_ns - # Set up trace to track when the class command is renamed/destroyed - trace add command $class_ns [list rename delete] ::metoo::_trace_class_renames - - return $class_ns -} - -proc metoo::_trace_class_renames {oldname newname op} { - if {$op eq "rename"} { - # TBD - this does not actually work. The rename succeeds anyways - error "MetOO classes may not be renamed" - } else { - $oldname destroy - } -} - -proc metoo::introspect {type info args} { - switch -exact -- $type { - "object" { - variable _objects - switch -exact -- $info { - "isa" { - if {[llength $args] == 0 || [llength $args] > 2} { - error "wrong # args: should be \"metoo::introspect $type $info OBJNAME ?CLASS?\"" - } - set objname [uplevel 1 [list namespace which -command [lindex $args 0]]] - if {![info exists _objects($objname)]} { - return 0 - } - if {[llength $args] == 1} { - # No class specified - return 1 - } - # passed classname assumed to be fully qualified - set objclass [namespace parent $_objects($objname)] - if {[string equal $objclass [lindex $args 1]]} { - # Direct hit - return 1 - } - - # No direct hit, check ancestors - if {[lindex $args 1] in [ancestors $objclass]} { - return 1 - } - - return 0 - } - - "list" { - if {[llength $args] > 1} { - error "wrong # args: should be \"metoo::introspect $type $info ?CLASS?" - } - variable _objects - if {[llength $args] == 0} { - return [array names _objects] - } - set objs {} - foreach obj [array names _objects] { - if {[introspect object isa $obj [lindex $args 0]]} { - lappend objs $obj - } - } - return $objs - } - default { - error "$info subcommand not supported for $type introspection" - } - } - } - - "class" { - switch -exact -- $info { - "ancestors" { - if {[llength $args] != 1} { - error "wrong # args: should be \"metoo::introspect $type $info CLASSNAME" - } - return [ancestors [lindex $args 0]] - } - default { - error "$info subcommand not supported for $type introspection" - } - } - } - default { - error "$type introspection not supported" - } - } -} - -proc metoo::ancestors {class_ns} { - # Returns ancestors of a class - - set ancestors [list ] - while {[info exists ${class_ns}::super]} { - lappend ancestors [set class_ns [set ${class_ns}::super]] - } - - return $ancestors -} - -namespace eval metoo { namespace export class } - -# Simple sample class showing all capabilities. Anything not shown here will -# probably not work. Call as "demo" to use metoo, or "demo oo" to use TclOO. -# Output should be same in both cases. -proc ::metoo::demo {{ns metoo}} { - ${ns}::class create Base { - constructor {x y} { puts "Base constructor ([self object]): $x, $y" - } - method m {} { puts "Base::m called" } - method n {args} { puts "Base::n called: [join $args {, }]"; my m } - method unknown {methodname args} { puts "Base::unknown called for $methodname [join $args {, }]"} - destructor { puts "Base::destructor ([self object])" } - } - - ${ns}::class create Derived { - superclass Base - constructor {x y} { puts "Derived constructor ([self object]): $x, $y" ; next $x $y } - destructor { puts "Derived::destructor called ([self object])" ; next } - method n {args} { puts "Derived::n ([self object]): [join $args {, }]"; next {*}$args} - method put {val} {my variable var ; set var $val} - method get {varname} {my variable var ; upvar 1 $varname retvar; set retvar $var} - } - - Base create b dum dee; # Create named object - Derived create d fee fi; # Create derived object - set o [Derived new fo fum]; # Create autonamed object - $o put 10; # Use of instance variable - $o get v; # Verify correct frame level ... - puts "v:$v"; # ...when calling methods - b m; # Direct method - b n; # Use of my to call another method - $o m; # Inherited method - $o n; # Overridden method chained to inherited - $o nosuchmethod arg1 arg2; # Invoke unknown - $o destroy; # Explicit destroy - rename b ""; # Destroy through rename - Base destroy; # Should destroy object d, Derived, Base -} - -# Hack to work with the various build configuration. -if {[info commands ::twapi::get_version] ne ""} { - package provide metoo [::twapi::get_version -patchlevel] -} diff --git a/src/vendorlib_tcl8/twapi4.7.2/pkgIndex.tcl b/src/vendorlib_tcl8/twapi4.7.2/pkgIndex.tcl deleted file mode 100644 index 1fc7471d..00000000 --- a/src/vendorlib_tcl8/twapi4.7.2/pkgIndex.tcl +++ /dev/null @@ -1,119 +0,0 @@ -# -# Tcl package index file -# - -namespace eval twapi { - variable scriptdir - proc set_scriptdir dir {variable scriptdir ; set scriptdir $dir} -} - -package ifneeded twapi_base 4.7.2 \ - [list load [file join $dir twapi472.dll] twapi_base] -package ifneeded twapi_com 4.7.2 \ - {load {} twapi_com} -package ifneeded metoo 4.7.2 \ - [list source [file join $dir metoo.tcl]] -package ifneeded twapi_com 4.7.2 \ - {load {} twapi_com} -package ifneeded twapi_msi 4.7.2 \ - [list source [file join $dir msi.tcl]] -package ifneeded twapi_power 4.7.2 \ - [list source [file join $dir power.tcl]] -package ifneeded twapi_printer 4.7.2 \ - [list source [file join $dir printer.tcl]] -package ifneeded twapi_synch 4.7.2 \ - [list source [file join $dir synch.tcl]] -package ifneeded twapi_security 4.7.2 \ - {load {} twapi_security} -package ifneeded twapi_account 4.7.2 \ - {load {} twapi_account} -package ifneeded twapi_apputil 4.7.2 \ - {load {} twapi_apputil} -package ifneeded twapi_clipboard 4.7.2 \ - {load {} twapi_clipboard} -package ifneeded twapi_console 4.7.2 \ - {load {} twapi_console} -package ifneeded twapi_crypto 4.7.2 \ - {load {} twapi_crypto} -package ifneeded twapi_device 4.7.2 \ - {load {} twapi_device} -package ifneeded twapi_etw 4.7.2 \ - {load {} twapi_etw} -package ifneeded twapi_eventlog 4.7.2 \ - {load {} twapi_eventlog} -package ifneeded twapi_mstask 4.7.2 \ - {load {} twapi_mstask} -package ifneeded twapi_multimedia 4.7.2 \ - {load {} twapi_multimedia} -package ifneeded twapi_namedpipe 4.7.2 \ - {load {} twapi_namedpipe} -package ifneeded twapi_network 4.7.2 \ - {load {} twapi_network} -package ifneeded twapi_nls 4.7.2 \ - {load {} twapi_nls} -package ifneeded twapi_os 4.7.2 \ - {load {} twapi_os} -package ifneeded twapi_pdh 4.7.2 \ - {load {} twapi_pdh} -package ifneeded twapi_process 4.7.2 \ - {load {} twapi_process} -package ifneeded twapi_rds 4.7.2 \ - {load {} twapi_rds} -package ifneeded twapi_resource 4.7.2 \ - {load {} twapi_resource} -package ifneeded twapi_service 4.7.2 \ - {load {} twapi_service} -package ifneeded twapi_share 4.7.2 \ - {load {} twapi_share} -package ifneeded twapi_shell 4.7.2 \ - {load {} twapi_shell} -package ifneeded twapi_storage 4.7.2 \ - {load {} twapi_storage} -package ifneeded twapi_ui 4.7.2 \ - {load {} twapi_ui} -package ifneeded twapi_input 4.7.2 \ - {load {} twapi_input} -package ifneeded twapi_winsta 4.7.2 \ - {load {} twapi_winsta} -package ifneeded twapi_wmi 4.7.2 \ - {load {} twapi_wmi} - -package ifneeded twapi 4.7.2 [subst { - twapi::set_scriptdir [list $dir] - package require twapi_base 4.7.2 - source [list [file join $dir twapi_entry.tcl]] - package require metoo 4.7.2 - package require twapi_com 4.7.2 - package require twapi_msi 4.7.2 - package require twapi_power 4.7.2 - package require twapi_printer 4.7.2 - package require twapi_synch 4.7.2 - package require twapi_security 4.7.2 - package require twapi_account 4.7.2 - package require twapi_apputil 4.7.2 - package require twapi_clipboard 4.7.2 - package require twapi_console 4.7.2 - package require twapi_crypto 4.7.2 - package require twapi_device 4.7.2 - package require twapi_etw 4.7.2 - package require twapi_eventlog 4.7.2 - package require twapi_mstask 4.7.2 - package require twapi_multimedia 4.7.2 - package require twapi_namedpipe 4.7.2 - package require twapi_network 4.7.2 - package require twapi_nls 4.7.2 - package require twapi_os 4.7.2 - package require twapi_pdh 4.7.2 - package require twapi_process 4.7.2 - package require twapi_rds 4.7.2 - package require twapi_resource 4.7.2 - package require twapi_service 4.7.2 - package require twapi_share 4.7.2 - package require twapi_shell 4.7.2 - package require twapi_storage 4.7.2 - package require twapi_ui 4.7.2 - package require twapi_input 4.7.2 - package require twapi_winsta 4.7.2 - package require twapi_wmi 4.7.2 - package provide twapi 4.7.2 -}] diff --git a/src/vendorlib_tcl8/twapi4.7.2/twapi472.dll b/src/vendorlib_tcl8/twapi4.7.2/twapi472.dll deleted file mode 100644 index c423d73bf82aaf539882e4942ff70abdbd3d48aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610816 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

  1. Rt$ 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 X22MfCY795)nyxqNGj76@{qO$*)VBXc diff --git a/src/vendorlib_tcl8/twapi4.7.2/twapi_entry.tcl b/src/vendorlib_tcl8/twapi4.7.2/twapi_entry.tcl deleted file mode 100644 index a30dc5eb..00000000 --- a/src/vendorlib_tcl8/twapi4.7.2/twapi_entry.tcl +++ /dev/null @@ -1,11 +0,0 @@ -# -*- tcl -*- -namespace eval twapi { - variable version - set version(twapi) 4.7.2 - variable patchlevel 4.7.2 - variable package_name twapi - variable dll_base_name twapi[string map {. {}} 4.7.2] - variable scriptdir [file dirname [info script]] -} - -source [file join $twapi::scriptdir twapi.tcl] diff --git a/src/vendorlib_tcl9/tcllib2.0/0compatibility/pkgIndex.tcl b/src/vendorlib_tcl9/tcllib2.0/0compatibility/pkgIndex.tcl new file mode 100644 index 00000000..d158493f --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/0compatibility/pkgIndex.tcl @@ -0,0 +1,8 @@ +# Compatibility wrapper for deprecated packages. +## +# Stages +# [D1] Next Release - Noted deprecated, with redirection wrappers +# [D2] Release After - Wrappers become Blockers, throwing error noting redirection +# [D3] Release Beyond - All removed. +## +# Currently in deprecation [[NONE]] diff --git a/src/vendorlib_tcl9/tcllib2.0/aes/aes.tcl b/src/vendorlib_tcl9/tcllib2.0/aes/aes.tcl new file mode 100644 index 00000000..fcb83cb5 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/aes/aes.tcl @@ -0,0 +1,625 @@ +# aes.tcl - +# +# Copyright (c) 2005 Thorsten Schloermann +# Copyright (c) 2005 Pat Thoyts +# Copyright (c) 2013 Andreas Kupries +# +# A Tcl implementation of the Advanced Encryption Standard (US FIPS PUB 197) +# +# AES is a block cipher with a block size of 128 bits and a variable +# key size of 128, 192 or 256 bits. +# The algorithm works on each block as a 4x4 state array. There are 4 steps +# in each round: +# SubBytes a non-linear substitution step using a predefined S-box +# ShiftRows cyclic transposition of rows in the state matrix +# MixColumns transformation upon columns in the state matrix +# AddRoundKey application of round specific sub-key +# +# ------------------------------------------------------------------------- +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# ------------------------------------------------------------------------- + +package require Tcl 8.5 9 + +namespace eval ::aes { + variable uid + if {![info exists uid]} { set uid 0 } + + namespace export aes + + # constants + + # S-box + variable sbox { + 0x63 0x7c 0x77 0x7b 0xf2 0x6b 0x6f 0xc5 0x30 0x01 0x67 0x2b 0xfe 0xd7 0xab 0x76 + 0xca 0x82 0xc9 0x7d 0xfa 0x59 0x47 0xf0 0xad 0xd4 0xa2 0xaf 0x9c 0xa4 0x72 0xc0 + 0xb7 0xfd 0x93 0x26 0x36 0x3f 0xf7 0xcc 0x34 0xa5 0xe5 0xf1 0x71 0xd8 0x31 0x15 + 0x04 0xc7 0x23 0xc3 0x18 0x96 0x05 0x9a 0x07 0x12 0x80 0xe2 0xeb 0x27 0xb2 0x75 + 0x09 0x83 0x2c 0x1a 0x1b 0x6e 0x5a 0xa0 0x52 0x3b 0xd6 0xb3 0x29 0xe3 0x2f 0x84 + 0x53 0xd1 0x00 0xed 0x20 0xfc 0xb1 0x5b 0x6a 0xcb 0xbe 0x39 0x4a 0x4c 0x58 0xcf + 0xd0 0xef 0xaa 0xfb 0x43 0x4d 0x33 0x85 0x45 0xf9 0x02 0x7f 0x50 0x3c 0x9f 0xa8 + 0x51 0xa3 0x40 0x8f 0x92 0x9d 0x38 0xf5 0xbc 0xb6 0xda 0x21 0x10 0xff 0xf3 0xd2 + 0xcd 0x0c 0x13 0xec 0x5f 0x97 0x44 0x17 0xc4 0xa7 0x7e 0x3d 0x64 0x5d 0x19 0x73 + 0x60 0x81 0x4f 0xdc 0x22 0x2a 0x90 0x88 0x46 0xee 0xb8 0x14 0xde 0x5e 0x0b 0xdb + 0xe0 0x32 0x3a 0x0a 0x49 0x06 0x24 0x5c 0xc2 0xd3 0xac 0x62 0x91 0x95 0xe4 0x79 + 0xe7 0xc8 0x37 0x6d 0x8d 0xd5 0x4e 0xa9 0x6c 0x56 0xf4 0xea 0x65 0x7a 0xae 0x08 + 0xba 0x78 0x25 0x2e 0x1c 0xa6 0xb4 0xc6 0xe8 0xdd 0x74 0x1f 0x4b 0xbd 0x8b 0x8a + 0x70 0x3e 0xb5 0x66 0x48 0x03 0xf6 0x0e 0x61 0x35 0x57 0xb9 0x86 0xc1 0x1d 0x9e + 0xe1 0xf8 0x98 0x11 0x69 0xd9 0x8e 0x94 0x9b 0x1e 0x87 0xe9 0xce 0x55 0x28 0xdf + 0x8c 0xa1 0x89 0x0d 0xbf 0xe6 0x42 0x68 0x41 0x99 0x2d 0x0f 0xb0 0x54 0xbb 0x16 + } + # inverse S-box + variable xobs { + 0x52 0x09 0x6a 0xd5 0x30 0x36 0xa5 0x38 0xbf 0x40 0xa3 0x9e 0x81 0xf3 0xd7 0xfb + 0x7c 0xe3 0x39 0x82 0x9b 0x2f 0xff 0x87 0x34 0x8e 0x43 0x44 0xc4 0xde 0xe9 0xcb + 0x54 0x7b 0x94 0x32 0xa6 0xc2 0x23 0x3d 0xee 0x4c 0x95 0x0b 0x42 0xfa 0xc3 0x4e + 0x08 0x2e 0xa1 0x66 0x28 0xd9 0x24 0xb2 0x76 0x5b 0xa2 0x49 0x6d 0x8b 0xd1 0x25 + 0x72 0xf8 0xf6 0x64 0x86 0x68 0x98 0x16 0xd4 0xa4 0x5c 0xcc 0x5d 0x65 0xb6 0x92 + 0x6c 0x70 0x48 0x50 0xfd 0xed 0xb9 0xda 0x5e 0x15 0x46 0x57 0xa7 0x8d 0x9d 0x84 + 0x90 0xd8 0xab 0x00 0x8c 0xbc 0xd3 0x0a 0xf7 0xe4 0x58 0x05 0xb8 0xb3 0x45 0x06 + 0xd0 0x2c 0x1e 0x8f 0xca 0x3f 0x0f 0x02 0xc1 0xaf 0xbd 0x03 0x01 0x13 0x8a 0x6b + 0x3a 0x91 0x11 0x41 0x4f 0x67 0xdc 0xea 0x97 0xf2 0xcf 0xce 0xf0 0xb4 0xe6 0x73 + 0x96 0xac 0x74 0x22 0xe7 0xad 0x35 0x85 0xe2 0xf9 0x37 0xe8 0x1c 0x75 0xdf 0x6e + 0x47 0xf1 0x1a 0x71 0x1d 0x29 0xc5 0x89 0x6f 0xb7 0x62 0x0e 0xaa 0x18 0xbe 0x1b + 0xfc 0x56 0x3e 0x4b 0xc6 0xd2 0x79 0x20 0x9a 0xdb 0xc0 0xfe 0x78 0xcd 0x5a 0xf4 + 0x1f 0xdd 0xa8 0x33 0x88 0x07 0xc7 0x31 0xb1 0x12 0x10 0x59 0x27 0x80 0xec 0x5f + 0x60 0x51 0x7f 0xa9 0x19 0xb5 0x4a 0x0d 0x2d 0xe5 0x7a 0x9f 0x93 0xc9 0x9c 0xef + 0xa0 0xe0 0x3b 0x4d 0xae 0x2a 0xf5 0xb0 0xc8 0xeb 0xbb 0x3c 0x83 0x53 0x99 0x61 + 0x17 0x2b 0x04 0x7e 0xba 0x77 0xd6 0x26 0xe1 0x69 0x14 0x63 0x55 0x21 0x0c 0x7d + } +} + +# aes::Init -- +# +# Initialise our AES state and calculate the key schedule. An initialization +# vector is maintained in the state for modes that require one. The key must +# be binary data of the correct size and the IV must be 16 bytes. +# +# Nk: columns of the key-array +# Nr: number of rounds (depends on key-length) +# Nb: columns of the text-block, is always 4 in AES +# +proc ::aes::Init {mode key iv} { + switch -exact -- $mode { + ecb - cbc { } + cfb - ofb { + return -code error "$mode mode not implemented" + } + default { + return -code error "invalid mode \"$mode\":\ + must be one of ecb or cbc." + } + } + + set size [expr {[string length $key] << 3}] + switch -exact -- $size { + 128 {set Nk 4; set Nr 10; set Nb 4} + 192 {set Nk 6; set Nr 12; set Nb 4} + 256 {set Nk 8; set Nr 14; set Nb 4} + default { + return -code error "invalid key size \"$size\":\ + must be one of 128, 192 or 256." + } + } + + variable uid + set Key [namespace current]::[incr uid] + upvar #0 $Key state + if {[binary scan $iv Iu4 state(I)] != 1} { + return -code error "invalid initialization vector: must be 16 bytes" + } + array set state [list M $mode K $key Nk $Nk Nr $Nr Nb $Nb W {}] + ExpandKey $Key + return $Key +} + +# aes::Reset -- +# +# Reset the initialization vector for the specified key. This permits the +# key to be reused for encryption or decryption without the expense of +# re-calculating the key schedule. +# +proc ::aes::Reset {Key iv} { + upvar #0 $Key state + if {[binary scan $iv Iu4 state(I)] != 1} { + return -code error "invalid initialization vector: must be 16 bytes" + } + return +} + +# aes::Final -- +# +# Clean up the key state +# +proc ::aes::Final {Key} { + # FRINK: nocheck + unset $Key +} + +# ------------------------------------------------------------------------- + +# 5.1 Cipher: Encipher a single block of 128 bits. +proc ::aes::EncryptBlock {Key block} { + upvar #0 $Key state + if {[binary scan $block Iu4 data] != 1} { + return -code error "invalid block size: blocks must be 16 bytes" + } + + if {$state(M) eq {cbc}} { + # Loop unrolled. + lassign $data d0 d1 d2 d3 + lassign $state(I) s0 s1 s2 s3 + set data [list \ + [expr {$d0 ^ $s0}] \ + [expr {$d1 ^ $s1}] \ + [expr {$d2 ^ $s2}] \ + [expr {$d3 ^ $s3}] ] + } + + set data [AddRoundKey $Key 0 $data] + for {set n 1} {$n < $state(Nr)} {incr n} { + set data [AddRoundKey $Key $n [MixColumns [ShiftRows [SubBytes $data]]]] + } + set data [AddRoundKey $Key $n [ShiftRows [SubBytes $data]]] + + # Bug 2993029: + # Force all elements of data into the 32bit range. + # Loop unrolled + set res [Clamp32 $data] + + set state(I) $res + binary format Iu4 $res +} + +# 5.3: Inverse Cipher: Decipher a single 128 bit block. +proc ::aes::DecryptBlock {Key block} { + upvar #0 $Key state + if {[binary scan $block Iu4 data] != 1} { + return -code error "invalid block size: block must be 16 bytes" + } + set iv $data + + set n $state(Nr) + set data [AddRoundKey $Key $state(Nr) $data] + for {incr n -1} {$n > 0} {incr n -1} { + set data [InvMixColumns [AddRoundKey $Key $n [InvSubBytes [InvShiftRows $data]]]] + } + set data [AddRoundKey $Key $n [InvSubBytes [InvShiftRows $data]]] + + if {$state(M) eq {cbc}} { + lassign $data d0 d1 d2 d3 + lassign $state(I) s0 s1 s2 s3 + set data [list \ + [expr {($d0 ^ $s0) & 0xffffffff}] \ + [expr {($d1 ^ $s1) & 0xffffffff}] \ + [expr {($d2 ^ $s2) & 0xffffffff}] \ + [expr {($d3 ^ $s3) & 0xffffffff}] ] + } else { + # Bug 2993029: + # The integrated clamping we see above only happens for CBC mode. + set data [Clamp32 $data] + } + + set state(I) $iv + binary format Iu4 $data +} + +proc ::aes::Clamp32 {data} { + # Force all elements into 32bit range. + lassign $data d0 d1 d2 d3 + list \ + [expr {$d0 & 0xffffffff}] \ + [expr {$d1 & 0xffffffff}] \ + [expr {$d2 & 0xffffffff}] \ + [expr {$d3 & 0xffffffff}] +} + +# 5.2: KeyExpansion +proc ::aes::ExpandKey {Key} { + upvar #0 $Key state + set Rcon [list 0x00000000 0x01000000 0x02000000 0x04000000 0x08000000 \ + 0x10000000 0x20000000 0x40000000 0x80000000 0x1b000000 \ + 0x36000000 0x6c000000 0xd8000000 0xab000000 0x4d000000] + # Split the key into Nk big-endian words + binary scan $state(K) I* W + set max [expr {$state(Nb) * ($state(Nr) + 1)}] + set i $state(Nk) + set h [expr {$i - 1}] + set j 0 + for {} {$i < $max} {incr i; incr h; incr j} { + set temp [lindex $W $h] + if {($i % $state(Nk)) == 0} { + set sub [SubWord [RotWord $temp]] + set rc [lindex $Rcon [expr {$i/$state(Nk)}]] + set temp [expr {$sub ^ $rc}] + } elseif {$state(Nk) > 6 && ($i % $state(Nk)) == 4} { + set temp [SubWord $temp] + } + lappend W [expr {[lindex $W $j] ^ $temp}] + } + set state(W) $W +} + +# 5.2: Key Expansion: Apply S-box to each byte in the 32 bit word +proc ::aes::SubWord {w} { + variable sbox + set s3 [lindex $sbox [expr {($w >> 24) & 255}]] + set s2 [lindex $sbox [expr {($w >> 16) & 255}]] + set s1 [lindex $sbox [expr {($w >> 8 ) & 255}]] + set s0 [lindex $sbox [expr { $w & 255}]] + return [expr {($s3 << 24) | ($s2 << 16) | ($s1 << 8) | $s0}] +} + +proc ::aes::InvSubWord {w} { + variable xobs + set s3 [lindex $xobs [expr {($w >> 24) & 255}]] + set s2 [lindex $xobs [expr {($w >> 16) & 255}]] + set s1 [lindex $xobs [expr {($w >> 8 ) & 255}]] + set s0 [lindex $xobs [expr { $w & 255}]] + return [expr {($s3 << 24) | ($s2 << 16) | ($s1 << 8) | $s0}] +} + +# 5.2: Key Expansion: Rotate a 32bit word by 8 bits +proc ::aes::RotWord {w} { + return [expr {(($w << 8) | (($w >> 24) & 0xff)) & 0xffffffff}] +} + +# 5.1.1: SubBytes() Transformation +proc ::aes::SubBytes {words} { + lassign $words w0 w1 w2 w3 + list [SubWord $w0] [SubWord $w1] [SubWord $w2] [SubWord $w3] +} + +# 5.3.2: InvSubBytes() Transformation +proc ::aes::InvSubBytes {words} { + lassign $words w0 w1 w2 w3 + list [InvSubWord $w0] [InvSubWord $w1] [InvSubWord $w2] [InvSubWord $w3] +} + +# 5.1.2: ShiftRows() Transformation +proc ::aes::ShiftRows {words} { + for {set n0 0} {$n0 < 4} {incr n0} { + set n1 [expr {($n0 + 1) % 4}] + set n2 [expr {($n0 + 2) % 4}] + set n3 [expr {($n0 + 3) % 4}] + lappend r [expr {( [lindex $words $n0] & 0xff000000) + | ([lindex $words $n1] & 0x00ff0000) + | ([lindex $words $n2] & 0x0000ff00) + | ([lindex $words $n3] & 0x000000ff) + }] + } + return $r +} + + +# 5.3.1: InvShiftRows() Transformation +proc ::aes::InvShiftRows {words} { + for {set n0 0} {$n0 < 4} {incr n0} { + set n1 [expr {($n0 + 1) % 4}] + set n2 [expr {($n0 + 2) % 4}] + set n3 [expr {($n0 + 3) % 4}] + lappend r [expr {( [lindex $words $n0] & 0xff000000) + | ([lindex $words $n3] & 0x00ff0000) + | ([lindex $words $n2] & 0x0000ff00) + | ([lindex $words $n1] & 0x000000ff) + }] + } + return $r +} + +# 5.1.3: MixColumns() Transformation +proc ::aes::MixColumns {words} { + set r {} + foreach w $words { + set r0 [expr {(($w >> 24) & 255)}] + set r1 [expr {(($w >> 16) & 255)}] + set r2 [expr {(($w >> 8 ) & 255)}] + set r3 [expr {( $w & 255)}] + + set s0 [expr {[GFMult2 $r0] ^ [GFMult3 $r1] ^ $r2 ^ $r3}] + set s1 [expr {$r0 ^ [GFMult2 $r1] ^ [GFMult3 $r2] ^ $r3}] + set s2 [expr {$r0 ^ $r1 ^ [GFMult2 $r2] ^ [GFMult3 $r3]}] + set s3 [expr {[GFMult3 $r0] ^ $r1 ^ $r2 ^ [GFMult2 $r3]}] + + lappend r [expr {($s0 << 24) | ($s1 << 16) | ($s2 << 8) | $s3}] + } + return $r +} + +# 5.3.3: InvMixColumns() Transformation +proc ::aes::InvMixColumns {words} { + set r {} + foreach w $words { + set r0 [expr {(($w >> 24) & 255)}] + set r1 [expr {(($w >> 16) & 255)}] + set r2 [expr {(($w >> 8 ) & 255)}] + set r3 [expr {( $w & 255)}] + + set s0 [expr {[GFMult0e $r0] ^ [GFMult0b $r1] ^ [GFMult0d $r2] ^ [GFMult09 $r3]}] + set s1 [expr {[GFMult09 $r0] ^ [GFMult0e $r1] ^ [GFMult0b $r2] ^ [GFMult0d $r3]}] + set s2 [expr {[GFMult0d $r0] ^ [GFMult09 $r1] ^ [GFMult0e $r2] ^ [GFMult0b $r3]}] + set s3 [expr {[GFMult0b $r0] ^ [GFMult0d $r1] ^ [GFMult09 $r2] ^ [GFMult0e $r3]}] + + lappend r [expr {($s0 << 24) | ($s1 << 16) | ($s2 << 8) | $s3}] + } + return $r +} + +# 5.1.4: AddRoundKey() Transformation +proc ::aes::AddRoundKey {Key round words} { + upvar #0 $Key state + set r {} + set n [expr {$round * $state(Nb)}] + foreach w $words { + lappend r [expr {$w ^ [lindex $state(W) $n]}] + incr n + } + return $r +} + +# ------------------------------------------------------------------------- +# ::aes::GFMult* +# +# some needed functions for multiplication in a Galois-field +# +proc ::aes::GFMult2 {number} { + # this is a tabular representation of xtime (multiplication by 2) + # it is used instead of calculation to prevent timing attacks + set xtime { + 0x00 0x02 0x04 0x06 0x08 0x0a 0x0c 0x0e 0x10 0x12 0x14 0x16 0x18 0x1a 0x1c 0x1e + 0x20 0x22 0x24 0x26 0x28 0x2a 0x2c 0x2e 0x30 0x32 0x34 0x36 0x38 0x3a 0x3c 0x3e + 0x40 0x42 0x44 0x46 0x48 0x4a 0x4c 0x4e 0x50 0x52 0x54 0x56 0x58 0x5a 0x5c 0x5e + 0x60 0x62 0x64 0x66 0x68 0x6a 0x6c 0x6e 0x70 0x72 0x74 0x76 0x78 0x7a 0x7c 0x7e + 0x80 0x82 0x84 0x86 0x88 0x8a 0x8c 0x8e 0x90 0x92 0x94 0x96 0x98 0x9a 0x9c 0x9e + 0xa0 0xa2 0xa4 0xa6 0xa8 0xaa 0xac 0xae 0xb0 0xb2 0xb4 0xb6 0xb8 0xba 0xbc 0xbe + 0xc0 0xc2 0xc4 0xc6 0xc8 0xca 0xcc 0xce 0xd0 0xd2 0xd4 0xd6 0xd8 0xda 0xdc 0xde + 0xe0 0xe2 0xe4 0xe6 0xe8 0xea 0xec 0xee 0xf0 0xf2 0xf4 0xf6 0xf8 0xfa 0xfc 0xfe + 0x1b 0x19 0x1f 0x1d 0x13 0x11 0x17 0x15 0x0b 0x09 0x0f 0x0d 0x03 0x01 0x07 0x05 + 0x3b 0x39 0x3f 0x3d 0x33 0x31 0x37 0x35 0x2b 0x29 0x2f 0x2d 0x23 0x21 0x27 0x25 + 0x5b 0x59 0x5f 0x5d 0x53 0x51 0x57 0x55 0x4b 0x49 0x4f 0x4d 0x43 0x41 0x47 0x45 + 0x7b 0x79 0x7f 0x7d 0x73 0x71 0x77 0x75 0x6b 0x69 0x6f 0x6d 0x63 0x61 0x67 0x65 + 0x9b 0x99 0x9f 0x9d 0x93 0x91 0x97 0x95 0x8b 0x89 0x8f 0x8d 0x83 0x81 0x87 0x85 + 0xbb 0xb9 0xbf 0xbd 0xb3 0xb1 0xb7 0xb5 0xab 0xa9 0xaf 0xad 0xa3 0xa1 0xa7 0xa5 + 0xdb 0xd9 0xdf 0xdd 0xd3 0xd1 0xd7 0xd5 0xcb 0xc9 0xcf 0xcd 0xc3 0xc1 0xc7 0xc5 + 0xfb 0xf9 0xff 0xfd 0xf3 0xf1 0xf7 0xf5 0xeb 0xe9 0xef 0xed 0xe3 0xe1 0xe7 0xe5 + } + lindex $xtime $number +} + +proc ::aes::GFMult3 {number} { + # multliply by 2 (via GFMult2) and add the number again on the result (via XOR) + expr {$number ^ [GFMult2 $number]} +} + +proc ::aes::GFMult09 {number} { + # 09 is: (02*02*02) + 01 + expr {[GFMult2 [GFMult2 [GFMult2 $number]]] ^ $number} +} + +proc ::aes::GFMult0b {number} { + # 0b is: (02*02*02) + 02 + 01 + #return [expr [GFMult2 [GFMult2 [GFMult2 $number]]] ^ [GFMult2 $number] ^ $number] + #set g0 [GFMult2 $number] + expr {[GFMult09 $number] ^ [GFMult2 $number]} +} + +proc ::aes::GFMult0d {number} { + # 0d is: (02*02*02) + (02*02) + 01 + set temp [GFMult2 [GFMult2 $number]] + expr {[GFMult2 $temp] ^ ($temp ^ $number)} +} + +proc ::aes::GFMult0e {number} { + # 0e is: (02*02*02) + (02*02) + 02 + set temp [GFMult2 [GFMult2 $number]] + expr {[GFMult2 $temp] ^ ($temp ^ [GFMult2 $number])} +} + +# ------------------------------------------------------------------------- + +# aes::Encrypt -- +# +# Encrypt a blocks of plain text and returns blocks of cipher text. +# The input data must be a multiple of the block size (16). +# +proc ::aes::Encrypt {Key data} { + set len [string length $data] + if {($len % 16) != 0} { + return -code error "invalid block size: AES requires 16 byte blocks" + } + + set result {} + for {set i 0} {$i < $len} {incr i 1} { + set block [string range $data $i [incr i 15]] + append result [EncryptBlock $Key $block] + } + return $result +} + +# aes::Decrypt -- +# +# Decrypt blocks of cipher text and returns blocks of plain text. +# The input data must be a multiple of the block size (16). +# +proc ::aes::Decrypt {Key data} { + set len [string length $data] + if {($len % 16) != 0} { + return -code error "invalid block size: AES requires 16 byte blocks" + } + + set result {} + for {set i 0} {$i < $len} {incr i 1} { + set block [string range $data $i [incr i 15]] + append result [DecryptBlock $Key $block] + } + return $result +} + +# ------------------------------------------------------------------------- +# chan event handler for chunked file reading. +# +proc ::aes::Chunk {Key in {out {}} {chunksize 4096}} { + upvar #0 $Key state + + #puts ||CHUNK.X||i=$in|o=$out|c=$chunksize|eof=[eof $in] + + if {[eof $in]} { + chan event $in readable {} + set state(reading) 0 + } + + set data [read $in $chunksize] + + #puts ||CHUNK.R||i=$in|o=$out|c=$chunksize|eof=[eof $in]||[string length $data]||$data|| + + # Do nothing when data was read at all. + if {$data eq {}} return + + if {[eof $in]} { + #puts CHUNK.Z + set data [Pad $data 16] + } + + #puts ||CHUNK.P||i=$in|o=$out|c=$chunksize|eof=[eof $in]||[string length $data]||$data|| + + if {$out eq {}} { + append state(output) [$state(cmd) $Key $data] + } else { + puts -nonewline $out [$state(cmd) $Key $data] + } +} + +proc ::aes::SetOneOf {lst item} { + set ndx [lsearch -glob $lst "${item}*"] + if {$ndx == -1} { + set err [join $lst ", "] + return -code error "invalid mode \"$item\": must be one of $err" + } + lindex $lst $ndx +} + +proc ::aes::CheckSize {what size thing} { + if {[string length $thing] != $size} { + return -code error "invalid value for $what: must be $size bytes long" + } + return $thing +} + +proc ::aes::Pad {data blocksize {fill \0}} { + set len [string length $data] + if {$len == 0} { + set data [string repeat $fill $blocksize] + } elseif {($len % $blocksize) != 0} { + set pad [expr {$blocksize - ($len % $blocksize)}] + append data [string repeat $fill $pad] + } + return $data +} + +proc ::aes::Pop {varname {nth 0}} { + upvar 1 $varname args + set r [lindex $args $nth] + set args [lreplace $args $nth $nth] + return $r +} + +proc ::aes::aes {args} { + array set opts {-dir encrypt -mode cbc -key {} -in {} -out {} -chunksize 4096 -hex 0} + set opts(-iv) [string repeat \0 16] + set modes {ecb cbc} + set dirs {encrypt decrypt} + while {([llength $args] > 1) && [string match -* [set option [lindex $args 0]]]} { + switch -exact -- $option { + -mode { set opts(-mode) [SetOneOf $modes [Pop args 1]] } + -dir { set opts(-dir) [SetOneOf $dirs [Pop args 1]] } + -iv { set opts(-iv) [CheckSize -iv 16 [Pop args 1]] } + -key { set opts(-key) [Pop args 1] } + -in { set opts(-in) [Pop args 1] } + -out { set opts(-out) [Pop args 1] } + -chunksize { set opts(-chunksize) [Pop args 1] } + -hex { set opts(-hex) 1 } + -- { Pop args ; break } + default { + set err [join [lsort [array names opts]] ", "] + return -code error "bad option \"$option\":\ + must be one of $err" + } + } + Pop args + } + + if {$opts(-key) eq {}} { + return -code error "no key provided: the -key option is required" + } + + set r {} + if {$opts(-in) eq {}} { + + if {[llength $args] != 1} { + return -code error "wrong \# args:\ + should be \"aes ?options...? -key keydata plaintext\"" + } + + set data [Pad [lindex $args 0] 16] + set Key [Init $opts(-mode) $opts(-key) $opts(-iv)] + if {[string equal $opts(-dir) "encrypt"]} { + set r [Encrypt $Key $data] + } else { + set r [Decrypt $Key $data] + } + + if {$opts(-out) ne {}} { + puts -nonewline $opts(-out) $r + set r {} + } + Final $Key + + } else { + + if {[llength $args] != 0} { + return -code error "wrong \# args:\ + should be \"aes ?options...? -key keydata -in channel\"" + } + + set Key [Init $opts(-mode) $opts(-key) $opts(-iv)] + + set readcmd [list [namespace origin Chunk] \ + $Key $opts(-in) $opts(-out) \ + $opts(-chunksize)] + + upvar 1 $Key state + set state(reading) 1 + if {[string equal $opts(-dir) "encrypt"]} { + set state(cmd) Encrypt + } else { + set state(cmd) Decrypt + } + set state(output) "" + chan event $opts(-in) readable $readcmd + if {[info commands ::tkwait] != {}} { + tkwait variable [subst $Key](reading) + } else { + vwait [subst $Key](reading) + } + if {$opts(-out) == {}} { + set r $state(output) + } + Final $Key + } + + if {$opts(-hex)} { + binary scan $r H* r + } + return $r +} + +# ------------------------------------------------------------------------- + +package provide aes 1.2.2 + +# ------------------------------------------------------------------------- +# Local variables: +# mode: tcl +# indent-tabs-mode: nil +# End: diff --git a/src/vendorlib_tcl9/tcllib2.0/aes/pkgIndex.tcl b/src/vendorlib_tcl9/tcllib2.0/aes/pkgIndex.tcl new file mode 100644 index 00000000..d433abc8 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/aes/pkgIndex.tcl @@ -0,0 +1,5 @@ +if {![package vsatisfies [package provide Tcl] 8.5 9]} { + # PRAGMA: returnok + return +} +package ifneeded aes 1.2.2 [list source [file join $dir aes.tcl]] diff --git a/src/vendorlib_tcl9/tcllib2.0/amazon-s3/S3.tcl b/src/vendorlib_tcl9/tcllib2.0/amazon-s3/S3.tcl new file mode 100644 index 00000000..ebc39e7a --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/amazon-s3/S3.tcl @@ -0,0 +1,1960 @@ +# S3.tcl +# +###Abstract +# This presents an interface to Amazon's S3 service. +# The Amazon S3 service allows for reliable storage +# and retrieval of data via HTTP. +# +# Copyright (c) 2006,2008 Darren New. All Rights Reserved. +# +###Copyright +# NO WARRANTIES OF ANY TYPE ARE PROVIDED. +# COPYING OR USE INDEMNIFIES THE AUTHOR IN ALL WAYS. +# +# This software is licensed under essentially the same +# terms as Tcl. See LICENSE.txt for the terms. +# +###Revision String +# SCCS: %Z% %M% %I% %E% %U% +# +###Change history: +# 0.7.2 - added -default-bucket. +# 0.8.0 - fixed bug in getLocal using wrong prefix. +# Upgraded to Tcl 8.5 release version. +# 1.0.0 - added SetAcl, GetAcl, and -acl keep option. +# + +package require Tcl 8.5 9 + +# This is by Darren New too. +# It is a SAX package to format XML for easy retrieval. +# It should be in the same distribution as S3. +package require xsxp + +# These three are required to do the auth, so always require them. +# Note that package registry and package fileutil are required +# by the individual routines that need them. Grep for "package". +package require sha1 +package require md5 +package require base64 + +package provide S3 1.0.5 + +namespace eval S3 { + variable config ; # A dict holding the current configuration. + variable config_orig ; # Holds configuration to "reset" back to. + variable debug 0 ; # Turns on or off S3::debug + variable debuglog 0 ; # Turns on or off debugging into a file + variable bgvar_counter 0 ; # Makes unique names for bgvars. + + set config_orig [dict create \ + -reset false \ + -retries 3 \ + -accesskeyid "" -secretaccesskey "" \ + -service-access-point "s3.amazonaws.com" \ + -slop-seconds 3 \ + -use-tls false \ + -bucket-prefix "TclS3" \ + -default-compare "always" \ + -default-separator "/" \ + -default-acl "" \ + -default-bucket "" \ + ] + + set config $config_orig +} + +# Internal, for development. Print a line, and maybe log it. +proc S3::debuglogline {line} { + variable debuglog + puts $line + if {$debuglog} { + set x [open debuglog.txt a] + puts $x $line + close $x + } +} + +# Internal, for development. Print debug info properly formatted. +proc S3::debug {args} { + variable debug + variable debuglog + if {!$debug} return + set res "" + if {"-hex" == [lindex $args 0]} { + set str [lindex $args 1] + foreach ch [split $str {}] { + scan $ch %c val + append res [format %02x $val] + append res " " + } + debuglogline $res + return + } + if {"-dict" == [lindex $args 0]} { + set dict [lindex $args 1] + debuglogline "DEBUG dict:" + foreach {key val} $dict { + set val [string map [list \ + \r \\r \n \\n \0 \\0 ] $val] + debuglogline "$key=$val" + } + return + } + set x [string map [list \ + \r \\r \n \\n \0 \\0 ] $args] + debuglogline "DEBUG: $x" +} + +# Internal. Throws an error if keys have not been initialized. +proc S3::checkinit {} { + variable config + set error "S3 must be initialized with -accesskeyid and -secretaccesskey before use" + set e1 {S3 usage -accesskeyid "S3 identification not initialized"} + set e2 {S3 usage -secretaccesskey "S3 identification not initialized"} + if {[dict get $config -accesskeyid] eq ""} { + error $error "" $e1 + } + if {[dict get $config -secretaccesskey] eq ""} { + error $error "" $e2 + } +} + +# Internal. Calculates the Content-Type for a given file name. +# Naturally returns application/octet-stream if anything goes wrong. +proc S3::contenttype {fname} { + if {$::tcl_platform(platform) == "windows"} { + set extension [file extension $fname] + uplevel #0 package require registry + set key "\\\\HKEY_CLASSES_ROOT\\" + set key "HKEY_CLASSES_ROOT\\" + if {"." != [string index $extension 0]} {append key .} + append key $extension + set ct "application/octet-stream" + if {$extension != ""} { + catch {set ct [registry get $key {Content Type}]} caught + } + } else { + # Assume something like Unix. + if {[file readable /etc/mime.types]} { + set extension [string trim [file extension $fname] "."] + set f [open /etc/mime.types r] + while {-1 != [gets $f line] && ![info exists c]} { + set line [string trim $line] + if {[string match "#*" $line]} continue + if {0 == [string length $line]} continue + set items [split $line] + for {set i 1} {$i < [llength $items]} {incr i} { + if {[lindex $items $i] eq $extension} { + set c [lindex $items 0] + break + } + } + } + close $f + if {![info exists c]} { + set ct "application/octet-stream" + } else { + set ct [string trim $c] + } + } else { + # No /etc/mime.types here. + if {[catch {exec file -i $fname} res]} { + set ct "application/octet-stream" + } else { + set ct [string range $res [expr {1+[string first : $res]}] end] + if {-1 != [string first ";" $ct]} { + set ct [string range $ct 0 [string first ";" $ct]] + } + set ct [string trim $ct "; "] + } + } + } + return $ct +} + +# Change current configuration. Not object-oriented, so only one +# configuration is tracked per interpreter. +proc S3::Configure {args} { + variable config + variable config_orig + if {[llength $args] == 0} {return $config} + if {[llength $args] == 1 && ![dict exists $config [lindex $args 0]]} { + error "Bad option \"[lindex $args 0]\": must be [join [dict keys $config] ,\ ]" "" [list S3 usage [lindex $args 0] "Bad option to config"] + } + if {[llength $args] == 1} {return [dict get $config [lindex $args 0]]} + if {[llength $args] % 2 != 0} { + error "Config args must be -name val -name val" "" [list S3 usage [lindex $args end] "Odd number of config args"] + } + set new $config + foreach {tag val} $args { + if {![dict exists $new $tag]} { + error "Bad option \"$tag\": must be [join [dict keys $config] ,\ ]" "" [list S3 usage $tag "Bad option to config"] + } + dict set new $tag $val + if {$tag eq "-reset" && $val} { + set new $config_orig + } + } + if {[dict get $config -use-tls]} { + error "TLS for S3 not yet implemented!" "" \ + [list S3 notyet -use-tls $config] + } + set config $new ; # Only update if all went well + return $config +} + +# Suggest a unique bucket name based on usename and config info. +proc S3::SuggestBucket {{usename ""}} { + checkinit + if {$usename eq ""} {set usename [::S3::Configure -bucket-prefix]} + if {$usename eq ""} { + error "S3::SuggestBucket requires name or -bucket-prefix set" \ + "" [list S3 usage -bucket-prefix] + } + return $usename\.[::S3::Configure -accesskeyid] +} + +# Calculate authorization token for REST interaction. +# Doesn't work yet for "Expires" type headers. Hence, only for "REST". +# We specifically don't call checkinit because it's called in all +# callers and we don't want to throw an error inside here. +# Caveat Emptor if you expect otherwise. +# This is internal, but useful enough you might want to invoke it. +proc S3::authREST {verb resource content-type headers args} { + if {[llength $args] != 0} { + set body [lindex $args 0] ; # we use [info exists] later + } + if {${content-type} != "" && [dict exists $headers content-type]} { + set content-type [dict get $headers content-type] + } + dict unset headers content-type + set verb [string toupper $verb] + if {[info exists body]} { + set content-md5 [::base64::encode [::md5::md5 $body]] + dict set headers content-md5 ${content-md5} + dict set headers content-length [string length $body] + } elseif {[dict exists $headers content-md5]} { + set content-md5 [dict get $headers content-md5] + } else { + set content-md5 "" + } + if {[dict exists $headers x-amz-date]} { + set date "" + dict unset headers date + } elseif {[dict exists $headers date]} { + set date [dict get $headers date] + } else { + set date [clock format [clock seconds] -gmt true -format \ + "%a, %d %b %Y %T %Z"] + dict set headers date $date + } + if {${content-type} != ""} { + dict set headers content-type ${content-type} + } + dict set headers host s3.amazonaws.com + set xamz "" + foreach key [lsort [dict keys $headers x-amz-*]] { + # Assume each is seen only once, for now, and is canonical already. + append xamz \n[string trim $key]:[string trim [dict get $headers $key]] + } + set xamz [string trim $xamz] + # Hmmm... Amazon lies. No \n after xamz if xamz is empty. + if {0 != [string length $xamz]} {append xamz \n} + set signthis \ + "$verb\n${content-md5}\n${content-type}\n$date\n$xamz$resource" + S3::debug "Sign this:" $signthis ; S3::debug -hex $signthis + set sig [::sha1::hmac [S3::Configure -secretaccesskey] $signthis] + set sig [binary format H* $sig] + set sig [string trim [::base64::encode $sig]] + dict set headers authorization "AWS [S3::Configure -accesskeyid]:$sig" + return $headers +} + +# Internal. Takes resource and parameters, tacks them together. +# Useful enough you might want to invoke it yourself. +proc S3::to_url {resource parameters} { + if {0 == [llength $parameters]} {return $resource} + if {-1 == [string first "?" $resource]} { + set front ? + } else { + set front & + } + foreach {key value} $parameters { + append resource $front $key "=" $value + set front & + } + return $resource +} + +# Internal. Encode a URL, including utf-8 versions. +# Useful enough you might want to invoke it yourself. +proc S3::encode_url {orig} { + set res "" + set re {[-a-zA-Z0-9/.,_]} + foreach ch [split $orig ""] { + if {[regexp $re $ch]} { + append res $ch + } else { + foreach uch [split [encoding convertto utf-8 $ch] ""] { + append res "%" + binary scan $uch H2 hex + append res $hex + } + } + } + if {$res ne $orig} { + S3::debug "URL Encoded:" $orig $res + } + return $res +} + +# This is used internally to either queue an event-driven +# item or to simply call the next routine, depending on +# whether the current transaction is supposed to be running +# in the background or not. +proc S3::nextdo {routine thunk direction args} { + global errorCode + S3::debug "nextdo" $routine $thunk $direction $args + if {[dict get $thunk blocking]} { + return [S3::$routine $thunk] + } else { + if {[llength $args] == 2} { + # fcopy failed! + S3::fail $thunk "S3 fcopy failed: [lindex $args 1]" "" \ + [list S3 socket $errorCode] + } else { + fileevent [dict get $thunk S3chan] $direction \ + [list S3::$routine $thunk] + if {$direction == "writable"} { + fileevent [dict get $thunk S3chan] readable {} + } else { + fileevent [dict get $thunk S3chan] writable {} + } + } + } +} + +# The proverbial It. Do a REST call to Amazon S3 service. +proc S3::REST {orig} { + variable config + checkinit + set EndPoint [dict get $config -service-access-point] + + # Save the original stuff first. + set thunk [dict create orig $orig] + + # Now add to thunk's top-level the important things + if {[dict exists $thunk orig resultvar]} { + dict set thunk blocking 0 + } else { + dict set thunk blocking 1 + } + if {[dict exists $thunk orig S3chan]} { + dict set thunk S3chan [dict get $thunk orig S3chan] + } elseif {[dict get $thunk blocking]} { + dict set thunk S3chan [socket $EndPoint 80] + } else { + dict set thunk S3chan [socket -async $EndPoint 80] + } + fconfigure [dict get $thunk S3chan] -translation binary + + dict set thunk verb [dict get $thunk orig verb] + dict set thunk resource [S3::encode_url [dict get $thunk orig resource]] + if {[dict exists $orig rtype]} { + dict set thunk resource \ + [dict get $thunk resource]?[dict get $orig rtype] + } + if {[dict exists $orig headers]} { + dict set thunk headers [dict get $orig headers] + } else { + dict set thunk headers [dict create] + } + if {[dict exists $orig infile]} { + dict set thunk infile [dict get $orig infile] + } + if {[dict exists $orig content-type]} { + dict set thunk content-type [dict get $orig content-type] + } else { + if {[dict exists $thunk infile]} { + set zz [dict get $thunk infile] + } else { + set zz [dict get $thunk resource] + } + if {-1 != [string first "?" $zz]} { + set zz [string range $zz 0 [expr {[string first "?" $zz]-1}]] + set zz [string trim $zz] + } + if {$zz != ""} { + catch {dict set thunk content-type [S3::contenttype $zz]} + } else { + dict set thunk content-type application/octet-stream + dict set thunk content-type "" + } + } + set p {} + if {[dict exist $thunk orig parameters]} { + set p [dict get $thunk orig parameters] + } + dict set thunk url [S3::to_url [dict get $thunk resource] $p] + + if {[dict exists $thunk orig inbody]} { + dict set thunk headers [S3::authREST \ + [dict get $thunk verb] [dict get $thunk resource] \ + [dict get $thunk content-type] [dict get $thunk headers] \ + [dict get $thunk orig inbody] ] + } else { + dict set thunk headers [S3::authREST \ + [dict get $thunk verb] [dict get $thunk resource] \ + [dict get $thunk content-type] [dict get $thunk headers] ] + } + # Not the best place to put this code. + if {![info exists body] && [dict exists $thunk infile]} { + set size [file size [dict get $thunk infile]] + set x [dict get $thunk headers] + dict set x content-length $size + dict set thunk headers $x + } + + + # Ready to go! + return [S3::nextdo send_headers $thunk writable] +} + +# Internal. Send the headers to Amazon. Might block if you have +# really small socket buffers, but Amazon doesn't want +# data that big anyway. +proc S3::send_headers {thunk} { + S3::debug "Send-headers" $thunk + set s3 [dict get $thunk S3chan] + puts $s3 "[dict get $thunk verb] [dict get $thunk url] HTTP/1.0" + S3::debug ">> [dict get $thunk verb] [dict get $thunk url] HTTP/1.0" + foreach {key val} [dict get $thunk headers] { + puts $s3 "$key: $val" + S3::debug ">> $key: $val" + } + puts $s3 "" + flush $s3 + return [S3::nextdo send_body $thunk writable] +} + +# Internal. Send the body to Amazon. +proc S3::send_body {thunk} { + global errorCode + set s3 [dict get $thunk S3chan] + if {[dict exists $thunk orig inbody]} { + # Send a string. Let's guess that even in non-blocking + # mode, this is small enough or Tcl's smart enough that + # we don't blow up the buffer. + puts -nonewline $s3 [dict get $thunk orig inbody] + flush $s3 + return [S3::nextdo read_headers $thunk readable] + } elseif {![dict exists $thunk orig infile]} { + # No body, no file, so nothing more to do. + return [S3::nextdo read_headers $thunk readable] + } elseif {[dict get $thunk blocking]} { + # A blocking file copy. Still not too hard. + if {[catch {set inchan [open [dict get $thunk infile] r]} caught]} { + S3::fail $thunk "S3 could not open infile - $caught" "" \ + [list S3 local [dict get $thunk infile] $errorCode] + } + fconfigure $inchan -translation binary + fileevent $s3 readable {} + fileevent $s3 writable {} + if {[catch {fcopy $inchan $s3 ; flush $s3 ; close $inchan} caught]} { + S3::fail $thunk "S3 could not copy infile - $caught" "" \ + [list S3 local [dict get $thunk infile] $errorCode] + } + S3::nextdo read_headers $thunk readable + } else { + # The hard one. Background file copy. + fileevent $s3 readable {} + fileevent $s3 writable {} + if {[catch {set inchan [open [dict get $thunk infile] r]} caught]} { + S3::fail $thunk "S3 could not open infile - $caught" "" \ + [list S3 local [dict get $thunk infile] $errorCode] + } + fconfigure $inchan -buffering none -translation binary + fconfigure $s3 -buffering none -translation binary \ + -blocking 0 ; # Doesn't work without this? + dict set thunk inchan $inchan ; # So we can close it. + fcopy $inchan $s3 -command \ + [list S3::nextdo read_headers $thunk readable] + } +} + +# Internal. The first line has come back. Grab out the +# stuff we care about. +proc S3::parse_status {thunk line} { + # Got the status line + S3::debug "<< $line" + dict set thunk httpstatusline [string trim $line] + dict set thunk outheaders [dict create] + regexp {^HTTP/1.. (...) (.*)$} $line junk code message + dict set thunk httpstatus $code + dict set thunk httpmessage [string trim $message] + return $thunk +} + +# A line of header information has come back. Grab it. +# This probably is unhappy with multiple lines for one +# header. +proc S3::parse_header {thunk line} { + # Got a header line. For now, assume no continuations. + S3::debug "<< $line" + set line [string trim $line] + set left [string range $line 0 [expr {[string first ":" $line]-1}]] + set right [string range $line [expr {[string first ":" $line]+1}] end] + set left [string trim [string tolower $left]] + set right [string trim $right] + dict set thunk outheaders $left $right + return $thunk +} + +# I don't know if HTTP requires a blank line after the headers if +# there's no body. + +# Internal. Read all the headers, and throw if we get EOF before +# we get any headers at all. +proc S3::read_headers {thunk} { + set s3 [dict get $thunk S3chan] + flush $s3 + fconfigure $s3 -blocking [dict get $thunk blocking] + if {[dict get $thunk blocking]} { + # Blocking. Just read to a blank line. Otherwise, + # if we use nextdo here, we wind up nesting horribly. + # If we're not blocking, of course, we're returning + # to the event loop each time, so that's OK. + set count [gets $s3 line] + if {[eof $s3]} { + S3::fail $thunk "S3 EOF during status line read" "" "S3 socket EOF" + } + set thunk [S3::parse_status $thunk $line] + while {[string trim $line] != ""} { + set count [gets $s3 line] + if {$count == -1 && 0 == [dict size [dict get $thunk outheaders]]} { + S3::fail $thunk "S3 EOF during headers read" "" "S3 socket EOF" + } + if {[string trim $line] != ""} { + set thunk [S3::parse_header $thunk $line] + } + } + return [S3::nextdo read_body $thunk readable] + } else { + # Non-blocking, so we have to reenter for each line. + # First, fix up the file handle, tho. + if {[dict exists $thunk inchan]} { + close [dict get $thunk inchan] + dict unset thunk inchan + } + # Now get one header. + set count [gets $s3 line] + if {[eof $s3]} { + fileevent $s3 readable {} + fileevent $s3 writable {} + if {![dict exists $thunk httpstatusline]} { + S3::fail $thunk "S3 EOF during status line read" "" "S3 socket EOF" + } elseif {0 == [dict size [dict get $thunk outheaders]]} { + S3::fail $thunk "S3 EOF during header read" "" "S3 socket EOF" + } + } + if {$count < 0} return ; # Wait for a whole line + set line [string trim $line] + if {![dict exists $thunk httpstatus]} { + set thunk [S3::parse_status $thunk $line] + S3::nextdo read_headers $thunk readable ; # New thunk here. + } elseif {$line != ""} { + set thunk [S3::parse_header $thunk $line] + S3::nextdo read_headers $thunk readable ; # New thunk here. + } else { + # Got an empty line. Switch to copying the body. + S3::nextdo read_body $thunk readable + } + } +} + +# Internal. Read the body of the response. +proc S3::read_body {thunk} { + set s3 [dict get $thunk S3chan] + if {[dict get $thunk blocking]} { + # Easy. Just read it. + if {[dict exists $thunk orig outchan]} { + fcopy $s3 [dict get $thunk orig outchan] + } else { + set x [read $s3] + dict set thunk outbody $x + #S3::debug "Body: $x" -- Disable unconditional wasteful conversion to string + #Need better debug system which does this only when active. + } + return [S3::nextdo all_done $thunk readable] + } else { + # Nonblocking mode. + if {[dict exists $thunk orig outchan]} { + fileevent $s3 readable {} + fileevent $s3 writable {} + fcopy $s3 [dict get $thunk orig outchan] -command \ + [list S3::nextdo all_done $thunk readable] + } else { + dict append thunk outbody [read $s3] + if {[eof $s3]} { + # We're done. + S3::nextdo all_done $thunk readable + } else { + S3::nextdo read_body $thunk readable + } + } + } +} + +# Internal. Convenience function. +proc S3::fail {thunk error errorInfo errorCode} { + S3::all_done $thunk $error $errorInfo $errorCode +} + +# Internal. We're all done the transaction. Clean up everything, +# potentially record errors, close channels, etc etc etc. +proc S3::all_done {thunk {error ""} {errorInfo ""} {errorCode ""}} { + set s3 [dict get $thunk S3chan] + catch { + fileevent $s3 readable {} + fileevent $s3 writable {} + } + if {![dict exists $thunk orig S3chan]} { + catch {close $s3} + } + set res [dict get $thunk orig] + catch { + dict set res httpstatus [dict get $thunk httpstatus] + dict set res httpmessage [dict get $thunk httpmessage] + dict set res outheaders [dict get $thunk outheaders] + } + if {![dict exists $thunk orig outchan]} { + if {[dict exists $thunk outbody]} { + dict set res outbody [dict get $thunk outbody] + } else { + # Probably HTTP failure + dict set rest outbody {} + } + } + if {$error ne ""} { + dict set res error $error + dict set res errorInfo $errorInfo + dict set res errorCode $errorCode + } + if {![dict get $thunk blocking]} { + after 0 [list uplevel #0 \ + [list set [dict get $thunk orig resultvar] $res]] + } + if {$error eq "" || ![dict get $thunk blocking] || \ + ([dict exists $thunk orig throwsocket] && \ + "return" == [dict get $thunk orig throwsocket])} { + return $res + } else { + error $error $errorInfo $errorCode + } +} + +# Internal. Parse the lst and make sure it has only keys from the 'valid' list. +# Used to parse arguments going into the higher-level functions. +proc S3::parseargs1 {lst valid} { + if {[llength $lst] % 2 != 0} { + error "Option list must be even -name val pairs" \ + "" [list S3 usage [lindex $lst end] $lst] + } + foreach {key val} $lst { + # Sadly, lsearch applies -glob to the wrong thing for our needs + set found 0 + foreach v $valid { + if {[string match $v $key]} {set found 1 ; break} + } + if {!$found} { + error "Option list has invalid -key" \ + "" [list S3 usage $key $lst] + } + } + return $lst ; # It seems OK +} + +# Internal. Create a variable for higher-level functions to vwait. +proc S3::bgvar {} { + variable bgvar_counter + incr bgvar_counter + set name ::S3::bgvar$bgvar_counter + return $name +} + +# Internal. Given a request and the arguments, run the S3::REST in +# the foreground or the background as appropriate. Also, do retries +# for internal errors. +proc S3::maybebackground {req myargs} { + variable config + global errorCode errorInfo + set mytries [expr {1+[dict get $config -retries]}] + set delay 2000 + dict set req throwsocket return + while {1} { + if {![dict exists $myargs -blocking] || [dict get $myargs -blocking]} { + set dict [S3::REST $req] + } else { + set res [bgvar] + dict set req resultvar $res + S3::REST $req + vwait $res + set dict [set $res] + unset $res ; # clean up temps + } + if {[dict exists $dict error]} { + set code [dict get $dict errorCode] + if {"S3" != [lindex $code 0] || "socket" != [lindex $code 1]} { + error [dict get $dict error] \ + [dict get $dict errorInfo] \ + [dict get $dict errorCode] + } + } + incr mytries -1 + incr delay $delay ; if {20000 < $delay} {set delay 20000} + if {"500" ne [dict get $dict httpstatus] || $mytries <= 0} { + return $dict + } + if {![dict exists $myargs -blocking] || [dict get $myargs -blocking]} { + after $delay + } else { + set timer [bgvar] + after $delay [list set $timer 1] + vwait $timer + unset $timer + } + } +} + +# Internal. Maybe throw an HTTP error if httpstatus not in 200 range. +proc S3::throwhttp {dict} { + set hs [dict get $dict httpstatus] + if {![string match "2??" $hs]} { + error "S3 received non-OK HTTP result of $hs" "" \ + [list S3 remote $hs $dict] + } +} + +# Public. Returns the list of buckets for this user. +proc S3::ListAllMyBuckets {args} { + checkinit ; # I know this gets done later. + set myargs [S3::parseargs1 $args {-blocking -parse-xml -result-type}] + if {![dict exists $myargs -result-type]} { + dict set myargs -result-type names + } + if {![dict exists $myargs -blocking]} { + dict set myargs -blocking true + } + set restype [dict get $myargs -result-type] + if {$restype eq "REST" && [dict exists $myargs -parse-xml]} { + error "Do not use REST with -parse-xml" "" \ + [list S3 usage -parse-xml $args] + } + if {![dict exists $myargs -parse-xml]} { + # We need to fetch the results. + set req [dict create verb GET resource /] + set dict [S3::maybebackground $req $myargs] + if {$restype eq "REST"} { + return $dict ; #we're done! + } + S3::throwhttp $dict ; #make sure it worked. + set xml [dict get $dict outbody] + } else { + set xml [dict get $myargs -parse-xml] + } + # Here, we either already returned the dict, or the XML is in "xml". + if {$restype eq "xml"} {return $xml} + if {[catch {set pxml [::xsxp::parse $xml]}]} { + error "S3 invalid XML structure" "" [list S3 usage xml $xml] + } + if {$restype eq "pxml"} {return $pxml} + if {$restype eq "dict" || $restype eq "names"} { + set buckets [::xsxp::fetch $pxml "Buckets" %CHILDREN] + set names {} ; set dates {} + foreach bucket $buckets { + lappend names [::xsxp::fetch $bucket "Name" %PCDATA] + lappend dates [::xsxp::fetch $bucket "CreationDate" %PCDATA] + } + if {$restype eq "names"} { + return $names + } else { + return [dict create \ + Owner/ID [::xsxp::fetch $pxml "Owner/ID" %PCDATA] \ + Owner/DisplayName \ + [::xsxp::fetch $pxml "Owner/DisplayName" %PCDATA] \ + Bucket/Name $names Bucket/Date $dates \ + ] + } + } + if {$restype eq "owner"} { + return [list [::xsxp::fetch $pxml Owner/ID %PCDATA] \ + [::xsxp::fetch $pxml Owner/DisplayName %PCDATA] ] + } + error "ListAllMyBuckets requires -result-type to be REST, xml, pxml, dict, owner, or names" "" [list S3 usage -result-type $args] +} + +# Public. Create a bucket. +proc S3::PutBucket {args} { + checkinit + set myargs [S3::parseargs1 $args {-blocking -bucket -acl}] + if {![dict exists $myargs -acl]} { + dict set myargs -acl [S3::Configure -default-acl] + } + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict exists $myargs -bucket]} { + error "PutBucket requires -bucket" "" [list S3 usage -bucket $args] + } + + set req [dict create verb PUT resource /[dict get $myargs -bucket]] + if {[dict exists $myargs -acl]} { + dict set req headers [list x-amz-acl [dict get $myargs -acl]] + } + set dict [S3::maybebackground $req $myargs] + S3::throwhttp $dict + return "" ; # until we decide what to return. +} + +# Public. Delete a bucket. +proc S3::DeleteBucket {args} { + checkinit + set myargs [S3::parseargs1 $args {-blocking -bucket}] + if {![dict exists $myargs -bucket]} { + error "DeleteBucket requires -bucket" "" [list S3 usage -bucket $args] + } + dict set myargs -bucket [string trim [dict get $args -bucket] "/ "] + + set req [dict create verb DELETE resource /[dict get $myargs -bucket]] + set dict [S3::maybebackground $req $myargs] + S3::throwhttp $dict + return "" ; # until we decide what to return. +} + +# Internal. Suck out the one and only answer from the list, if needed. +proc S3::firstif {list myargs} { + if {[dict exists $myargs -max-keys]} { + return [lindex $list 0] + } else { + return $list + } +} + +# Public. Get the list of resources within a bucket. +proc S3::GetBucket {args} { + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -parse-xml -max-keys + -result-type -prefix -delimiter + -TEST + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "GetBucket requires -bucket" "" [list S3 usage -bucket $args] + } + if {[dict get $myargs -bucket] eq ""} { + error "GetBucket requires -bucket nonempty" "" \ + [list S3 usage -bucket $args] + } + if {![dict exists $myargs -result-type]} { + dict set myargs -result-type names + } + if {[dict get $myargs -result-type] eq "REST" && \ + [dict exists $myargs "-parse-xml"]} { + error "GetBucket can't have -parse-xml with REST result" "" \ + [list S3 usage -parse-xml $args] + } + set req [dict create verb GET resource /[dict get $myargs -bucket]] + set parameters {} + # Now, just to make test cases easier... + if {[dict exists $myargs -TEST]} { + dict set parameters max-keys [dict get $myargs -TEST] + } + # Back to your regularly scheduled argument parsing + if {[dict exists $myargs -max-keys]} { + dict set parameters max-keys [dict get $myargs -max-keys] + } + if {[dict exists $myargs -prefix]} { + set p [dict get $myargs -prefix] + if {[string match "/*" $p]} { + set p [string range $p 1 end] + } + dict set parameters prefix $p + } + if {[dict exists $myargs -delimiter]} { + dict set parameters delimiter [dict get $myargs -delimiter] + } + set nextmarker0 {} ; # We use this for -result-type dict. + if {![dict exists $myargs -parse-xml]} { + # Go fetch answers. + # Current xaction in "0" vars, with accumulation in "L" vars. + # Ultimate result of this loop is $RESTL, a list of REST results. + set RESTL [list] + while {1} { + set req0 $req ; dict set req0 parameters $parameters + set REST0 [S3::maybebackground $req0 $myargs] + S3::throwhttp $REST0 + lappend RESTL $REST0 + if {[dict exists $myargs -max-keys]} { + # We were given a limit, so just return the answer. + break + } + set pxml0 [::xsxp::parse [dict get $REST0 outbody]] + set trunc0 [expr "true" eq \ + [::xsxp::fetch $pxml0 IsTruncated %PCDATA]] + if {!$trunc0} { + # We've retrieved the final block, so go parse it. + set nextmarker0 "" ; # For later. + break + } + # Find the highest contents entry. (Would have been + # easier if Amazon always supplied NextMarker.) + set nextmarker0 {} + foreach {only tag} {Contents Key CommonPrefixes Prefix} { + set only0 [::xsxp::only $pxml0 $only] + if {0 < [llength $only0]} { + set k0 [::xsxp::fetch [lindex $only0 end] $tag %PCDATA] + if {[string compare $nextmarker0 $k0] < 0} { + set nextmarker0 $k0 + } + } + } + if {$nextmarker0 eq ""} {error "Internal Error in S3 library"} + # Here we have the next marker, so fetch the next REST + dict set parameters marker $nextmarker0 + # Note - $nextmarker0 is used way down below again! + } + # OK, at this point, the caller did not provide the xml via -parse-xml + # And now we have a list of REST results. So let's process. + if {[dict get $myargs -result-type] eq "REST"} { + return [S3::firstif $RESTL $myargs] + } + set xmlL [list] + foreach entry $RESTL { + lappend xmlL [dict get $entry outbody] + } + unset RESTL ; # just to save memory + } else { + # Well, we've parsed out the XML from the REST, + # so we're ready for -parse-xml + set xmlL [list [dict get $myargs -parse-xml]] + } + if {[dict get $myargs -result-type] eq "xml"} { + return [S3::firstif $xmlL $myargs] + } + set pxmlL [list] + foreach xml $xmlL { + lappend pxmlL [::xsxp::parse $xml] + } + unset xmlL + if {[dict get $myargs -result-type] eq "pxml"} { + return [S3::firstif $pxmlL $myargs] + } + # Here, for result types of "names" and "dict", + # we need to actually parse out all the results. + if {[dict get $myargs -result-type] eq "names"} { + # The easy one. + set names [list] + foreach pxml $pxmlL { + set con0 [::xsxp::only $pxml Contents] + set con1 [::xsxp::only $pxml CommonPrefixes] + lappend names {*}[concat [::xsxp::fetchall $con0 Key %PCDATA] \ + [::xsxp::fetchall $con1 Prefix %PCDATA]] + } + return [lsort $names] + } elseif {[dict get $myargs -result-type] eq "dict"} { + # The harder one. + set last0 [lindex $pxmlL end] + set res [dict create] + foreach thing {Name Prefix Marker MaxKeys IsTruncated} { + dict set res $thing [::xsxp::fetch $last0 $thing %PCDATA?] + } + dict set res NextMarker $nextmarker0 ; # From way up above. + set Prefix [list] + set names {Key LastModified ETag Size Owner/ID Owner/DisplayName StorageClass} + foreach name $names {set $name [list]} + foreach pxml $pxmlL { + foreach tag [::xsxp::only $pxml CommonPrefixes] { + lappend Prefix [::xsxp::fetch $tag Prefix %PCDATA] + } + foreach tag [::xsxp::only $pxml Contents] { + foreach name $names { + lappend $name [::xsxp::fetch $tag $name %PCDATA] + } + } + } + dict set res CommonPrefixes/Prefix $Prefix + foreach name $names {dict set res $name [set $name]} + return $res + } else { + # The hardest one ;-) + error "GetBucket Invalid result type, must be REST, xml, pxml, names, or dict" "" [list S3 usage -result-type $args] + } +} + +# Internal. Compare a resource to a file. +# Returns 1 if they're different, 0 if they're the same. +# Note that using If-Modified-Since and/or If-Match,If-None-Match +# might wind up being more efficient than pulling the head +# and checking. However, this allows for slop, checking both +# the etag and the date, only generating local etag if the +# date and length indicate they're the same, and so on. +# Direction is G or P for Get or Put. +# Assumes the source always exists. Obviously, Get and Put will throw if not, +# but not because of this. +proc S3::compare {myargs direction} { + variable config + global errorInfo + set compare [dict get $myargs -compare] + if {$compare ni {always never exists missing newer date checksum different}} { + error "-compare must be always, never, exists, missing, newer, date, checksum, or different" "" \ + [list S3 usage -compare $myargs] + } + if {"never" eq $compare} {return 0} + if {"always" eq $compare} {return 1} + if {[dict exists $myargs -file] && [file exists [dict get $myargs -file]]} { + set local_exists 1 + } else { + set local_exists 0 + } + # Avoid hitting S3 if we don't need to. + if {$direction eq "G" && "exists" eq $compare} {return $local_exists} + if {$direction eq "G" && "missing" eq $compare} { + return [expr !$local_exists] + } + # We need to get the headers from the resource. + set req [dict create \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource] \ + verb HEAD ] + set res [S3::maybebackground $req $myargs] + set httpstatus [dict get $res httpstatus] + if {"404" eq $httpstatus} { + set remote_exists 0 + } elseif {[string match "2??" $httpstatus]} { + set remote_exists 1 + } else { + error "S3: Neither 404 or 2xx on conditional compare" "" \ + [list S3 remote $httpstatus $res] + } + if {$direction eq "P"} { + if {"exists" eq $compare} {return $remote_exists} + if {"missing" eq $compare} {return [expr {!$remote_exists}]} + if {!$remote_exists} {return 1} + } elseif {$direction eq "G"} { + # Actually already handled above, but it never hurts... + if {"exists" eq $compare} {return $local_exists} + if {"missing" eq $compare} {return [expr {!$local_exists}]} + } + set outheaders [dict get $res outheaders] + if {[dict exists $outheaders content-length]} { + set remote_length [dict get $outheaders content-length] + } else { + set remote_length -1 + } + if {[dict exists $outheaders etag]} { + set remote_etag [string tolower \ + [string trim [dict get $outheaders etag] \"]] + } else { + set remote_etag "YYY" + } + if {[dict exists $outheaders last-modified]} { + set remote_date [clock scan [dict get $outheaders last-modified]] + } else { + set remote_date -1 + } + if {[dict exists $myargs -content]} { + # Probably should work this out better... + #set local_length [string length [encoding convert-to utf-8 \ + #[dict get $myargs -content]]] + set local_length [string length [dict get $myargs -content]] + } elseif {$local_exists} { + if {[catch {file size [dict get $myargs -file]} local_length]} { + error "S3: Couldn't stat [dict get $myargs -file]" "" \ + [list S3 local $errorInfo] + } + } else { + set local_length -2 + } + if {[dict exists $myargs -content]} { + set local_date [clock seconds] + } elseif {$local_exists} { + set local_date [file mtime [dict get $myargs -file]] + # Shouldn't throw, since [file size] worked. + } else { + set local_date -2 + } + if {$direction eq "P"} { + if {"newer" eq $compare} { + if {$remote_date < $local_date - [dict get $config -slop-seconds]} { + return 1 ; # Yes, local is newer + } else { + return 0 ; # Older, or the same + } + } + } elseif {$direction eq "G"} { + if {"newer" eq $compare} { + if {$local_date < $remote_date - [dict get $config -slop-seconds]} { + return 1 ; # Yes, remote is later. + } else { + return 0 ; # Local is older or same. + } + } + } + if {[dict get $config -slop-seconds] <= abs($local_date - $remote_date)} { + set date_diff 1 ; # Difference is greater + } else { + set date_diff 0 ; # Difference negligible + } + if {"date" eq $compare} {return $date_diff} + if {"different" eq $compare && [dict exists $myargs -file] && $date_diff} { + return 1 + } + # Date's the same, but we're also interested in content, so check the rest + # Only others to handle are checksum and different-with-matching-dates + if {$local_length != $remote_length} {return 1} ; #easy quick case + if {[dict exists $myargs -file] && $local_exists} { + if {[catch { + # Maybe deal with making this backgroundable too? + set local_etag [string tolower \ + [::md5::md5 -hex -filename [dict get $myargs -file]]] + } caught]} { + # Maybe you can stat but not read it? + error "S3 could not hash file" "" \ + [list S3 local [dict get $myargs -file] $errorInfo] + } + } elseif {[dict exists $myargs -content]} { + set local_etag [string tolower \ + [string tolower [::md5::md5 -hex [dict get $myargs -content]]]] + } else { + set local_etag "XXX" + } + # puts "local: $local_etag\nremote: $remote_etag" + if {$local_etag eq $remote_etag} {return 0} {return 1} +} + +# Internal. Calculates the ACL based on file permissions. +proc S3::calcacl {myargs} { + # How would one work this under Windows, then? + # Silly way: invoke [exec cacls $filename], + # parse the result looking for Everyone:F or Everyone:R + # Messy security if someone replaces the cacls.exe or something. + error "S3 Not Yet Implemented" "" [list S3 notyet calcacl $myargs] + set result [S3::Configure -default-acl] + catch { + set chmod [file attributes [dict get $myargs -file] -permissions] + set chmod [expr {$chmod & 6}] + if {$chmod == 0} {set result private} + if {$chmod == 2} {set result public-write} + if {$chmod == 6} {set result public-read-write} + } +} + +# Public. Put a resource into a bucket. +proc S3::Put {args} { + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -file -content -resource -acl + -content-type -x-amz-meta-* -compare + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "Put requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -blocking]} { + dict set myargs -blocking true + } + if {![dict exists $myargs -file] && ![dict exists $myargs -content]} { + error "Put requires -file or -content" "" [list S3 usage -file $args] + } + if {[dict exists $myargs -file] && [dict exists $myargs -content]} { + error "Put says -file, -content mutually exclusive" "" [list S3 usage -file $args] + } + if {![dict exists $myargs -resource]} { + error "Put requires -resource" "" [list S3 usage -resource $args] + } + if {![dict exists $myargs -compare]} { + dict set myargs -compare [S3::Configure -default-compare] + } + if {![dict exists $myargs -acl] && "" ne [S3::Configure -default-acl]} { + dict set myargs -acl [S3::Configure -default-acl] + } + if {[dict exists $myargs -file] && \ + "never" ne [dict get $myargs -compare] && \ + ![file exists [dict get $myargs -file]]} { + error "Put -file doesn't exist: [dict get $myargs -file]" \ + "" [list S3 usage -file $args] + } + # Clean up bucket, and take one leading slash (if any) off resource. + if {[string match "/*" [dict get $myargs -resource]]} { + dict set myargs -resource \ + [string range [dict get $myargs -resource] 1 end] + } + # See if we need to copy it. + set comp [S3::compare $myargs P] + if {!$comp} {return 0} ; # skip it, then. + + # Oookeydookey. At this point, we're actually going to send + # the file, so all we need to do is build the request array. + set req [dict create verb PUT \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource]] + if {[dict exists $myargs -file]} { + dict set req infile [dict get $myargs -file] + } else { + dict set req inbody [dict get $myargs -content] + } + if {[dict exists $myargs -content-type]} { + dict set req content-type [dict get $myargs -content-type] + } + set headers {} + foreach xhead [dict keys $myargs -x-amz-meta-*] { + dict set headers [string range $xhead 1 end] [dict get $myargs $xhead] + } + set xmlacl "" ; # For calc and keep + if {[dict exists $myargs -acl]} { + if {[dict get $myargs -acl] eq "calc"} { + # We could make this more complicated by + # assigning it to xmlacl after building it. + dict set myargs -acl [S3::calcacl $myargs] + } elseif {[dict get $myargs -acl] eq "keep"} { + dict set myargs -acl [S3::Configure -default-acl] + catch { + set xmlacl [S3::GetAcl \ + -bucket [dict get $myargs -bucket] \ + -resource [dict get $myargs -resource] \ + -blocking [dict get $myargs -blocking] \ + -result-type xml] + } + } + dict set headers x-amz-acl [dict get $myargs -acl] + } + dict set req headers $headers + # That should do it. + set res [S3::maybebackground $req $myargs] + S3::throwhttp $res + if {"<" == [string index $xmlacl 0]} { + # Set the saved ACL back on the new object + S3::PutAcl \ + -bucket [dict get $myargs -bucket] \ + -resource [dict get $myargs -resource] \ + -blocking [dict get $myargs -blocking] \ + -acl $xmlacl + } + return 1 ; # Yep, we copied it! +} + +# Public. Get a resource from a bucket. +proc S3::Get {args} { + global errorCode + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -file -content -resource -timestamp + -headers -compare + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "Get requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -file] && ![dict exists $myargs -content]} { + error "Get requires -file or -content" "" [list S3 usage -file $args] + } + if {[dict exists $myargs -file] && [dict exists $myargs -content]} { + error "Get says -file, -content mutually exclusive" "" [list S3 usage -file $args] + } + if {![dict exists $myargs -resource]} { + error "Get requires -resource" "" [list S3 usage -resource $args] + } + if {![dict exists $myargs -compare]} { + dict set myargs -compare [S3::Configure -default-compare] + } + # Clean up bucket, and take one leading slash (if any) off resource. + if {[string match "/*" [dict get $myargs -resource]]} { + dict set myargs -resource \ + [string range [dict get $myargs -resource] 1 end] + } + # See if we need to copy it. + if {"never" eq [dict get $myargs -compare]} {return 0} + if {[dict exists $myargs -content]} { + set comp 1 + } else { + set comp [S3::compare $myargs G] + } + if {!$comp} {return 0} ; # skip it, then. + + # Oookeydookey. At this point, we're actually going to fetch + # the file, so all we need to do is build the request array. + set req [dict create verb GET \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource]] + if {[dict exists $myargs -file]} { + set pre_exists [file exists [dict get $myargs -file]] + if {[catch { + set x [open [dict get $myargs -file] w] + fconfigure $x -translation binary + } caught]} { + error "Get could not create file [dict get $myargs -file]" "" \ + [list S3 local -file $errorCode] + } + dict set req outchan $x + } + # That should do it. + set res [S3::maybebackground $req $myargs] + if {[dict exists $req outchan]} { + catch {close [dict get $req outchan]} + if {![string match "2??" [dict get $res httpstatus]] && !$pre_exists} { + catch {file delete -force -- [dict get $myargs -file]} + } + } + S3::throwhttp $res + if {[dict exists $myargs -headers]} { + uplevel 1 \ + [list set [dict get $myargs -headers] [dict get $res outheaders]] + } + if {[dict exists $myargs -content]} { + uplevel 1 \ + [list set [dict get $myargs -content] [dict get $res outbody]] + } + if {[dict exists $myargs -timestamp] && [dict exists $myargs -file]} { + if {"aws" eq [dict get $myargs -timestamp]} { + catch { + set t [dict get $res outheaders last-modified] + set t [clock scan $t -gmt true] + file mtime [dict get $myargs -file] $t + } + } + } + return 1 ; # Yep, we copied it! +} + +# Public. Get information about a resource in a bucket. +proc S3::Head {args} { + global errorCode + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -resource -headers -dict -status + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "Head requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -resource]} { + error "Head requires -resource" "" [list S3 usage -resource $args] + } + # Clean up bucket, and take one leading slash (if any) off resource. + if {[string match "/*" [dict get $myargs -resource]]} { + dict set myargs -resource \ + [string range [dict get $myargs -resource] 1 end] + } + set req [dict create verb HEAD \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource]] + set res [S3::maybebackground $req $myargs] + if {[dict exists $myargs -dict]} { + uplevel 1 \ + [list set [dict get $myargs -dict] $res] + } + if {[dict exists $myargs -headers]} { + uplevel 1 \ + [list set [dict get $myargs -headers] [dict get $res outheaders]] + } + if {[dict exists $myargs -status]} { + set x [list [dict get $res httpstatus] [dict get $res httpmessage]] + uplevel 1 \ + [list set [dict get $myargs -status] $x] + } + return [string match "2??" [dict get $res httpstatus]] +} + +# Public. Get the full ACL from an object and parse it into something useful. +proc S3::GetAcl {args} { + global errorCode + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -resource -result-type -parse-xml + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {![dict exists $myargs -result-type]} { + dict set myargs -result-type "dict" + } + set restype [dict get $myargs -result-type] + if {$restype eq "REST" && [dict exists $myargs -parse-xml]} { + error "Do not use REST with -parse-xml" "" \ + [list S3 usage -parse-xml $args] + } + if {![dict exists $myargs -parse-xml]} { + # We need to fetch the results. + if {"" eq [dict get $myargs -bucket]} { + error "GetAcl requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -resource]} { + error "GetAcl requires -resource" "" [list S3 usage -resource $args] + } + # Clean up bucket, and take one leading slash (if any) off resource. + if {[string match "/*" [dict get $myargs -resource]]} { + dict set myargs -resource \ + [string range [dict get $myargs -resource] 1 end] + } + set req [dict create verb GET \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource] \ + rtype acl] + set dict [S3::maybebackground $req $myargs] + if {$restype eq "REST"} { + return $dict ; #we're done! + } + S3::throwhttp $dict ; #make sure it worked. + set xml [dict get $dict outbody] + } else { + set xml [dict get $myargs -parse-xml] + } + if {[dict get $myargs -result-type] == "xml"} { + return $xml + } + set pxml [xsxp::parse $xml] + if {[dict get $myargs -result-type] == "pxml"} { + return $pxml + } + if {[dict get $myargs -result-type] == "dict"} { + array set resdict {} + set owner [xsxp::fetch $pxml Owner/ID %PCDATA] + set grants [xsxp::fetch $pxml AccessControlList %CHILDREN] + foreach grant $grants { + set perm [xsxp::fetch $grant Permission %PCDATA] + set id "" + catch {set id [xsxp::fetch $grant Grantee/ID %PCDATA]} + if {$id == ""} { + set id [xsxp::fetch $grant Grantee/URI %PCDATA] + } + lappend resdict($perm) $id + } + return [dict create owner $owner acl [array get resdict]] + } + error "GetAcl requires -result-type to be REST, xml, pxml or dict" "" [list S3 usage -result-type $args] +} + +# Make one Grant thingie +proc S3::engrant {who what} { + if {$who == "AuthenticatedUsers" || $who == "AllUsers"} { + set who http://acs.amazonaws.com/groups/global/$who + } + if {-1 != [string first "//" $who]} { + set type Group ; set tag URI + } elseif {-1 != [string first "@" $who]} { + set type AmazonCustomerByEmail ; set tag EmailAddress + } else { + set type CanonicalUser ; set tag ID + } + set who [string map {< < > > & &} $who] + set what [string toupper $what] + set xml "<$tag>$who" + append xml "$what" + return $xml +} + +# Make the owner header +proc S3::enowner {owner} { + return "$owner" + return "\n$owner" +} + +proc S3::endacl {} { + return "\n" +} + +# Public. Set the ACL on an existing object. +proc S3::PutAcl {args} { + global errorCode + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -resource -acl -owner + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "PutAcl requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -resource]} { + error "PutAcl requires -resource" "" [list S3 usage -resource $args] + } + if {![dict exists $myargs -acl]} { + dict set myargs -acl [S3::Configure -default-acl] + } + dict set myargs -acl [string trim [dict get $myargs -acl]] + if {[dict get $myargs -acl] == ""} { + dict set myargs -acl [S3::Configure -default-acl] + } + if {[dict get $myargs -acl] == ""} { + error "PutAcl requires -acl" "" [list D3 usage -resource $args] + } + # Clean up bucket, and take one leading slash (if any) off resource. + if {[string match "/*" [dict get $myargs -resource]]} { + dict set myargs -resource \ + [string range [dict get $myargs -resource] 1 end] + } + # Now, figure out the XML to send. + set acl [dict get $myargs -acl] + set owner "" + if {"<" != [string index $acl 0] && ![dict exists $myargs -owner]} { + # Grab the owner off the resource + set req [dict create verb GET \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource] \ + rtype acl] + set dict [S3::maybebackground $req $myargs] + S3::throwhttp $dict ; #make sure it worked. + set xml [dict get $dict outbody] + set pxml [xsxp::parse $xml] + set owner [xsxp::fetch $pxml Owner/ID %PCDATA] + } + if {[dict exists $myargs -owner]} { + set owner [dict get $myargs -owner] + } + set xml [enowner $owner] + if {"" == $acl || "private" == $acl} { + append xml [engrant $owner FULL_CONTROL] + append xml [endacl] + } elseif {"public-read" == $acl} { + append xml [engrant $owner FULL_CONTROL] + append xml [engrant AllUsers READ] + append xml [endacl] + } elseif {"public-read-write" == $acl} { + append xml [engrant $owner FULL_CONTROL] + append xml [engrant AllUsers READ] + append xml [engrant AllUsers WRITE] + append xml [endacl] + } elseif {"authenticated-read" == $acl} { + append xml [engrant $owner FULL_CONTROL] + append xml [engrant AuthenticatedUsers READ] + append xml [endacl] + } elseif {"<" == [string index $acl 0]} { + set xml $acl + } elseif {[llength $acl] % 2 != 0} { + error "S3::PutAcl -acl must be xml, private, public-read, public-read-write, authenticated-read, or a dictionary" \ + "" [list S3 usage -acl $acl] + } else { + # ACL in permission/ID-list format. + if {[dict exists $acl owner] && [dict exists $acl acl]} { + set xml [S3::enowner [dict get $acl owner]] + set acl [dict get $acl acl] + } + foreach perm {FULL_CONTROL READ READ_ACP WRITE WRITE_ACP} { + if {[dict exists $acl $perm]} { + foreach id [dict get $acl $perm] { + append xml [engrant $id $perm] + } + } + } + append xml [endacl] + } + set req [dict create verb PUT \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource] \ + inbody $xml \ + rtype acl] + set res [S3::maybebackground $req $myargs] + S3::throwhttp $res ; #make sure it worked. + return $xml +} + +# Public. Delete a resource from a bucket. +proc S3::Delete {args} { + global errorCode + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -resource -status + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "Delete requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -resource]} { + error "Delete requires -resource" "" [list S3 usage -resource $args] + } + # Clean up bucket, and take one leading slash (if any) off resource. + if {[string match "/*" [dict get $myargs -resource]]} { + dict set myargs -resource \ + [string range [dict get $myargs -resource] 1 end] + } + set req [dict create verb DELETE \ + resource /[dict get $myargs -bucket]/[dict get $myargs -resource]] + set res [S3::maybebackground $req $myargs] + if {[dict exists $myargs -status]} { + set x [list [dict get $res httpstatus] [dict get $res httpmessage]] + uplevel 1 \ + [list set [dict get $myargs -status] $x] + } + return [string match "2??" [dict get $res httpstatus]] +} + +# Some helper routines for Push, Pull, and Sync + +# Internal. Filter for fileutil::find. +proc S3::findfilter {dirs name} { + # In particular, skip links, devices, etc. + if {$dirs} { + return [expr {[file isdirectory $name] || [file isfile $name]}] + } else { + return [file isfile $name] + } +} + +# Internal. Get list of local files, appropriately trimmed. +proc S3::getLocal {root dirs} { + # Thanks to Michael Cleverly for this first line... + set base [file normalize [file join [pwd] $root]] + if {![string match "*/" $base]} { + set base $base/ + } + set files {} ; set bl [string length $base] + foreach file [fileutil::find $base [list S3::findfilter $dirs]] { + if {[file isdirectory $file]} { + lappend files [string range $file $bl end]/ + } else { + lappend files [string range $file $bl end] + } + } + set files [lsort $files] + # At this point, $files is a sorted list of all the local files, + # with a trailing / on any directories included in the list. + return $files +} + +# Internal. Get list of remote resources, appropriately trimmed. +proc S3::getRemote {bucket prefix blocking} { + set prefix [string trim $prefix " /"] + if {0 != [string length $prefix]} {append prefix /} + set res [S3::GetBucket -bucket $bucket -prefix $prefix \ + -result-type names -blocking $blocking] + set names {} ; set pl [string length $prefix] + foreach name $res { + lappend names [string range $name $pl end] + } + return [lsort $names] +} + +# Internal. Create any directories we need to put the file in place. +proc S3::makeDirs {directory suffix} { + set sofar {} + set nodes [split $suffix /] + set nodes [lrange $nodes 0 end-1] + foreach node $nodes { + lappend sofar $node + set tocheck [file join $directory {*}$sofar] + if {![file exists $tocheck]} { + catch {file mkdir $tocheck} + } + } +} + +# Internal. Default progress monitor for push, pull, toss. +proc S3::ignore {args} {} ; # default progress monitor + +# Internal. For development and testing. Progress monitor. +proc S3::printargs {args} {puts $args} ; # For testing. + +# Public. Send a local directory tree to S3. +proc S3::Push {args} { + uplevel #0 package require fileutil + global errorCode errorInfo + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -prefix -directory + -compare -x-amz-meta-* -acl -delete -error -progress + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "Push requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -directory]} { + error "Push requires -directory" "" [list S3 usage -directory $args] + } + # Set default values. + set defaults " + -acl \"[S3::Configure -default-acl]\" + -compare [S3::Configure -default-compare] + -prefix {} -delete 0 -error continue -progress ::S3::ignore -blocking 1" + foreach {key val} $defaults { + if {![dict exists $myargs $key]} {dict set myargs $key $val} + } + # Pull out arguments for convenience + foreach i {progress prefix directory bucket blocking} { + set $i [dict get $myargs -$i] + } + set prefix [string trimright $prefix /] + set meta [dict filter $myargs key x-amz-meta-*] + # We're readdy to roll here. + uplevel 1 [list {*}$progress args $myargs] + if {[catch { + set local [S3::getLocal $directory 0] + } caught]} { + error "Push could not walk local directory - $caught" \ + $errorInfo $errorCode + } + uplevel 1 [list {*}$progress local $local] + if {[catch { + set remote [S3::getRemote $bucket $prefix $blocking] + } caught]} { + error "Push could not walk remote directory - $caught" \ + $errorInfo $errorCode + } + uplevel 1 [list {*}$progress remote $remote] + set result [dict create] + set result0 [dict create \ + filescopied 0 bytescopied 0 compareskipped 0 \ + errorskipped 0 filesdeleted 0 filesnotdeleted 0] + foreach suffix $local { + uplevel 1 [list {*}$progress copy $suffix start] + set err [catch { + S3::Put -bucket $bucket -blocking $blocking \ + -file [file join $directory $suffix] \ + -resource $prefix/$suffix \ + -acl [dict get $myargs -acl] \ + {*}$meta \ + -compare [dict get $myargs -compare]} caught] + if {$err} { + uplevel 1 [list {*}$progress copy $suffix $errorCode] + dict incr result0 errorskipped + dict set result $suffix $errorCode + if {[dict get $myargs -error] eq "throw"} { + error "Push failed to Put - $caught" $errorInfo $errorCode + } elseif {[dict get $myargs -error] eq "break"} { + break + } + } else { + if {$caught} { + uplevel 1 [list {*}$progress copy $suffix copied] + dict incr result0 filescopied + dict incr result0 bytescopied \ + [file size [file join $directory $suffix]] + dict set result $suffix copied + } else { + uplevel 1 [list {*}$progress copy $suffix skipped] + dict incr result0 compareskipped + dict set result $suffix skipped + } + } + } + # Now do deletes, if so desired + if {[dict get $myargs -delete]} { + foreach suffix $remote { + if {$suffix ni $local} { + set err [catch { + S3::Delete -bucket $bucket -blocking $blocking \ + -resource $prefix/$suffix } caught] + if {$err} { + uplevel 1 [list {*}$progress delete $suffix $errorCode] + dict incr result0 filesnotdeleted + dict set result $suffix notdeleted + } else { + uplevel 1 [list {*}$progress delete $suffix {}] + dict incr result0 filesdeleted + dict set result $suffix deleted + } + } + } + } + dict set result {} $result0 + uplevel 1 [list {*}$progress finished $result] + return $result +} + +# Public. Fetch a portion of a remote bucket into a local directory tree. +proc S3::Pull {args} { + # This is waaaay to similar to Push for comfort. + # Fold it up later. + uplevel #0 package require fileutil + global errorCode errorInfo + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -prefix -directory + -compare -timestamp -delete -error -progress + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "Pull requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -directory]} { + error "Pull requires -directory" "" [list S3 usage -directory $args] + } + # Set default values. + set defaults " + -timestamp now + -compare [S3::Configure -default-compare] + -prefix {} -delete 0 -error continue -progress ::S3::ignore -blocking 1" + foreach {key val} $defaults { + if {![dict exists $myargs $key]} {dict set myargs $key $val} + } + # Pull out arguments for convenience + foreach i {progress prefix directory bucket blocking} { + set $i [dict get $myargs -$i] + } + set prefix [string trimright $prefix /] + # We're readdy to roll here. + uplevel 1 [list {*}$progress args $myargs] + if {[catch { + set local [S3::getLocal $directory 1] + } caught]} { + error "Pull could not walk local directory - $caught" \ + $errorInfo $errorCode + } + uplevel 1 [list {*}$progress local $local] + if {[catch { + set remote [S3::getRemote $bucket $prefix $blocking] + } caught]} { + error "Pull could not walk remote directory - $caught" \ + $errorInfo $errorCode + } + uplevel 1 [list {*}$progress remote $remote] + set result [dict create] + set result0 [dict create \ + filescopied 0 bytescopied 0 compareskipped 0 \ + errorskipped 0 filesdeleted 0 filesnotdeleted 0] + foreach suffix $remote { + uplevel 1 [list {*}$progress copy $suffix start] + set err [catch { + S3::makeDirs $directory $suffix + S3::Get -bucket $bucket -blocking $blocking \ + -file [file join $directory $suffix] \ + -resource $prefix/$suffix \ + -timestamp [dict get $myargs -timestamp] \ + -compare [dict get $myargs -compare]} caught] + if {$err} { + uplevel 1 [list {*}$progress copy $suffix $errorCode] + dict incr result0 errorskipped + dict set result $suffix $errorCode + if {[dict get $myargs -error] eq "throw"} { + error "Pull failed to Get - $caught" $errorInfo $errorCode + } elseif {[dict get $myargs -error] eq "break"} { + break + } + } else { + if {$caught} { + uplevel 1 [list {*}$progress copy $suffix copied] + dict incr result0 filescopied + dict incr result0 bytescopied \ + [file size [file join $directory $suffix]] + dict set result $suffix copied + } else { + uplevel 1 [list {*}$progress copy $suffix skipped] + dict incr result0 compareskipped + dict set result $suffix skipped + } + } + } + # Now do deletes, if so desired + if {[dict get $myargs -delete]} { + foreach suffix [lsort -decreasing $local] { + # Note, decreasing because we delete empty dirs + if {[string match "*/" $suffix]} { + set f [file join $directory $suffix] + catch {file delete -- $f} + if {![file exists $f]} { + uplevel 1 [list {*}$progress delete $suffix {}] + dict set result $suffix deleted + dict incr result0 filesdeleted + } + } elseif {$suffix ni $remote} { + set err [catch { + file delete [file join $directory $suffix] + } caught] + if {$err} { + uplevel 1 [list {*}$progress delete $suffix $errorCode] + dict incr result0 filesnotdeleted + dict set result $suffix notdeleted + } else { + uplevel 1 [list {*}$progress delete $suffix {}] + dict incr result0 filesdeleted + dict set result $suffix deleted + } + } + } + } + dict set result {} $result0 + uplevel 1 [list {*}$progress finished $result] + return $result +} + +# Public. Delete a collection of resources with the same prefix. +proc S3::Toss {args} { + # This is waaaay to similar to Push for comfort. + # Fold it up later. + global errorCode errorInfo + checkinit + set myargs [S3::parseargs1 $args { + -bucket -blocking -prefix + -error -progress + }] + if {![dict exists $myargs -bucket]} { + dict set myargs -bucket [S3::Configure -default-bucket] + } + dict set myargs -bucket [string trim [dict get $myargs -bucket] "/ "] + if {"" eq [dict get $myargs -bucket]} { + error "Toss requires -bucket" "" [list S3 usage -bucket $args] + } + if {![dict exists $myargs -prefix]} { + error "Toss requires -prefix" "" [list S3 usage -directory $args] + } + # Set default values. + set defaults "-error continue -progress ::S3::ignore -blocking 1" + foreach {key val} $defaults { + if {![dict exists $myargs $key]} {dict set myargs $key $val} + } + # Pull out arguments for convenience + foreach i {progress prefix bucket blocking} { + set $i [dict get $myargs -$i] + } + set prefix [string trimright $prefix /] + # We're readdy to roll here. + uplevel 1 [list {*}$progress args $myargs] + if {[catch { + set remote [S3::getRemote $bucket $prefix $blocking] + } caught]} { + error "Toss could not walk remote bucket - $caught" \ + $errorInfo $errorCode + } + uplevel 1 [list {*}$progress remote $remote] + set result [dict create] + set result0 [dict create \ + filescopied 0 bytescopied 0 compareskipped 0 \ + errorskipped 0 filesdeleted 0 filesnotdeleted 0] + # Now do deletes + foreach suffix $remote { + set err [catch { + S3::Delete -bucket $bucket -blocking $blocking \ + -resource $prefix/$suffix } caught] + if {$err} { + uplevel 1 [list {*}$progress delete $suffix $errorCode] + dict incr result0 filesnotdeleted + dict set result $suffix notdeleted + } else { + uplevel 1 [list {*}$progress delete $suffix {}] + dict incr result0 filesdeleted + dict set result $suffix deleted + } + } + dict set result {} $result0 + uplevel 1 [list {*}$progress finished $result] + return $result +} diff --git a/src/vendorlib_tcl9/tcllib2.0/amazon-s3/pkgIndex.tcl b/src/vendorlib_tcl9/tcllib2.0/amazon-s3/pkgIndex.tcl new file mode 100644 index 00000000..0640c1aa --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/amazon-s3/pkgIndex.tcl @@ -0,0 +1,9 @@ +# pkgIndex.tcl -- +# Copyright (c) 2006 Darren New +# This is for the Amazon S3 web service packages. + +if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} + +package ifneeded xsxp 1.1 [list source [file join $dir xsxp.tcl]] +package ifneeded S3 1.0.5 [list source [file join $dir S3.tcl]] + diff --git a/src/vendorlib_tcl9/tcllib2.0/amazon-s3/xsxp.tcl b/src/vendorlib_tcl9/tcllib2.0/amazon-s3/xsxp.tcl new file mode 100644 index 00000000..fe0b0c3a --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/amazon-s3/xsxp.tcl @@ -0,0 +1,254 @@ +# xsxp.tcl -- +# +###Abstract +# Extremely Simple XML Parser +# +# This is pretty lame, but I needed something like this for S3, +# and at the time, TclDOM would not work with the new 8.5 Tcl +# due to version number problems. +# +# In addition, this is a pure-value implementation. There is no +# garbage to clean up in the event of a thrown error, for example. +# This simplifies the code for sufficiently small XML documents, +# which is what Amazon's S3 guarantees. +# +###Copyright +# Copyright (c) 2006 Darren New. +# All Rights Reserved. +# NO WARRANTIES OF ANY TYPE ARE PROVIDED. +# COPYING OR USE INDEMNIFIES THE AUTHOR IN ALL WAYS. +# See the license terms in LICENSE.txt +# +###Revision String +# SCCS: %Z% %M% %I% %E% %U% + +# xsxp::parse $xml +# Returns a parsed XML, or PXML. A pxml is a list. +# The first element is the name of the tag. +# The second element is a list of name/value pairs of the +# associated attribues, if any. +# The third thru final values are recursively PXML values. +# If the first element (element zero, that is) is "%PCDATA", +# then the attributes will be emtpy and the third element +# will be the text of the element. + +# xsxp::fetch $pxml $path ?$part? +# $pxml is a parsed XML, as returned from xsxp::parse. +# $path is a list of elements. Each element is the name of +# a child to look up, optionally followed by a hash ("#") +# and a string of digits. An emtpy list or an initial empty +# element selects $pxml. If no hash sign is present, the +# behavior is as if "#0" had been appended to that element. +# An element of $path scans the children at the indicated +# level for the n'th instance of a child whose tag matches +# the part of the element before the hash sign. If an element +# is simply "#" followed by digits, that indexed child is +# selected, regardless of the tags in the children. So +# an element of #3 will always select the fourth child +# of the node under consideration. +# $part defaults to %ALL. It can be one of the following: +# %ALL - returns the entire selected element. +# %TAGNAME - returns lindex 0 of the selected element. +# %ATTRIBUTES - returns lindex 1 of the selected element. +# %CHILDREN - returns lrange 2 through end of the selected element, +# resulting in a list of elements being returned. +# %PCDATA - returns a concatenation of all the bodies of +# direct children of this node whose tag is %PCDATA. +# Throws an error if no such children are found. That +# is, part=%PCDATA means return the textual content found +# in that node but not its children nodes. +# %PCDATA? - like %PCDATA, but returns an empty string if +# no PCDATA is found. + +# xsxp::fetchall $pxml_list $path ?$part? +# Iterates over each PXML in $pxml_list, selecting the indicated +# path from it, building a new list with the selected data, and +# returning that new list. For example, $pxml_list might be +# the %CHILDREN of a particular element, and the $path and $part +# might select from each child a sub-element in which we're interested. + +# xsxp::only $pxml $tagname +# Iterates over the direct children of $pxml and selects only +# those with $tagname as their tag. Returns a list of matching +# elements. + +# xsxp::prettyprint $pxml +# Outputs to stdout a nested-list notation of the parsed XML. + +package require xml +package provide xsxp 1.1 + +namespace eval xsxp { + + variable Stack + variable Cur + + proc Characterdatacommand {characterdata} { + variable Cur + # puts "characterdatacommand $characterdata" + set x [list %PCDATA {} $characterdata] + lappend Cur $x + } + + proc Elementstartcommand {name attlist args} { + # puts "elementstart $name {$attlist} $args" + variable Stack + variable Cur + lappend Stack $Cur + set Cur [list $name $attlist] + } + + proc Elementendcommand {args} { + # puts "elementend $args" + variable Stack + variable Cur + set x [lindex $Stack end] + lappend x $Cur + set Cur $x + set Stack [lrange $Stack 0 end-1] + } + + proc parse {xml} { + variable Cur + variable Stack + set Cur {} + set Stack {} + set parser [::xml::parser \ + -characterdatacommand [namespace code Characterdatacommand] \ + -elementstartcommand [namespace code Elementstartcommand] \ + -elementendcommand [namespace code Elementendcommand] \ + -ignorewhitespace 1 -final 1 + ] + $parser parse $xml + $parser free + # The following line is needed because the close of the last element + # appends the outermost element to the item on the top of the stack. + # Since there's nothing on the top of the stack at the close of the + # last element, we append the current element to an empty list. + # In essence, since we don't really have a terminating condition + # on the recursion, an empty stack is still treated like an element. + set Cur [lindex $Cur 0] + set Cur [Normalize $Cur] + return $Cur + } + + proc Normalize {pxml} { + # This iterates over pxml recursively, finding entries that + # start with multiple %PCDATA elements, and coalesces their + # content, so if an element contains only %PCDATA, it is + # guaranteed to have only one child. + # Not really necessary, given definition of part=%PCDATA + # However, it makes pretty-prints nicer (for AWS at least) + # and ends up with smaller lists. I have no idea why they + # would put quotes around an MD5 hash in hex, tho. + set dupl 1 + while {$dupl} { + set first [lindex $pxml 2] + set second [lindex $pxml 3] + if {[lindex $first 0] eq "%PCDATA" && [lindex $second 0] eq "%PCDATA"} { + set repl [list %PCDATA {} [lindex $first 2][lindex $second 2]] + set pxml [lreplace $pxml 2 3 $repl] + } else { + set dupl 0 + for {set i 2} {$i < [llength $pxml]} {incr i} { + set pxml [lreplace $pxml $i $i [Normalize [lindex $pxml $i]]] + } + } + } + return $pxml + } + + proc prettyprint {pxml {chan stdout} {indent 0}} { + puts -nonewline $chan [string repeat " " $indent] + if {[lindex $pxml 0] eq "%PCDATA"} { + puts $chan "%PCDATA: [lindex $pxml 2]" + return + } + puts -nonewline $chan "[lindex $pxml 0]" + foreach {name val} [lindex $pxml 1] { + puts -nonewline $chan " $name='$val'" + } + puts $chan "" + foreach node [lrange $pxml 2 end] { + prettyprint $node $chan [expr $indent+1] + } + } + + proc fetch {pxml path {part %ALL}} { + set path [string trim $path /] + if {-1 != [string first / $path]} { + set path [split $path /] + } + foreach element $path { + if {$pxml eq ""} {return ""} + foreach {tag count} [split $element #] { + if {$tag ne ""} { + if {$count eq ""} {set count 0} + set pxml [lrange $pxml 2 end] + while {0 <= $count && 0 != [llength $pxml]} { + if {$tag eq [lindex $pxml 0 0]} { + incr count -1 + if {$count < 0} { + # We're done. Go on to next element. + set pxml [lindex $pxml 0] + } else { + # Not done yet. Throw this away. + set pxml [lrange $pxml 1 end] + } + } else { + # Not what we want. + set pxml [lrange $pxml 1 end] + } + } + } else { # tag eq "" + if {$count eq ""} { + # Just select whole $pxml + } else { + set pxml [lindex $pxml [expr {2+$count}]] + } + } + break + } ; # done the foreach [split] loop + } ; # done all the elements. + if {$part eq "%ALL"} {return $pxml} + if {$part eq "%ATTRIBUTES"} {return [lindex $pxml 1]} + if {$part eq "%TAGNAME"} {return [lindex $pxml 0]} + if {$part eq "%CHILDREN"} {return [lrange $pxml 2 end]} + if {$part eq "%PCDATA" || $part eq "%PCDATA?"} { + set res "" ; set found 0 + foreach elem [lrange $pxml 2 end] { + if {"%PCDATA" eq [lindex $elem 0]} { + append res [lindex $elem 2] + set found 1 + } + } + if {$found || $part eq "%PCDATA?"} { + return $res + } else { + error "xsxp::fetch did not find requested PCDATA" + } + } + return $pxml ; # Don't know what he's after + } + + proc only {pxml tag} { + set res {} + foreach element [lrange $pxml 2 end] { + if {[lindex $element 0] eq $tag} { + lappend res $element + } + } + return $res + } + + proc fetchall {pxml_list path {part %ALL}} { + set res [list] + foreach pxml $pxml_list { + lappend res [fetch $pxml $path $part] + } + return $res + } +} + +namespace export xsxp parse prettyprint fetch + diff --git a/src/vendorlib_tcl9/tcllib2.0/asn/asn.tcl b/src/vendorlib_tcl9/tcllib2.0/asn/asn.tcl new file mode 100644 index 00000000..1271e429 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/asn/asn.tcl @@ -0,0 +1,1580 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 1999-2004 Jochen C. Loewer (loewerj@web.de) +# Copyright (C) 2004-2011 Michael Schlenker (mic42@users.sourceforge.net) +#----------------------------------------------------------------------------- +# +# A partial ASN decoder/encoder implementation in plain Tcl. +# +# See ASN.1 (X.680) and BER (X.690). +# See 'asn_ber_intro.txt' in this directory. +# +# This software is copyrighted by Jochen C. Loewer (loewerj@web.de). The +# following terms apply to all files associated with the software unless +# explicitly disclaimed in individual files. +# +# The authors hereby grant permission to use, copy, modify, distribute, +# and license this software and its documentation for any purpose, provided +# that existing copyright notices are retained in all copies and that this +# notice is included verbatim in any distributions. No written agreement, +# license, or royalty fee is required for any of the authorized uses. +# Modifications to this software may be copyrighted by their authors +# and need not follow the licensing terms described here, provided that +# the new terms are clearly indicated on the first page of each file where +# they apply. +# +# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +# MODIFICATIONS. +# +# written by Jochen Loewer +# 3 June, 1999 +# +# $Id: asn.tcl,v 1.20 2011/01/05 22:33:33 mic42 Exp $ +# +#----------------------------------------------------------------------------- + +# needed for using wide() +package require Tcl 8.5 9 + +namespace eval asn { + # Encoder commands + namespace export \ + asnSequence \ + asnSequenceFromList \ + asnSet \ + asnSetFromList \ + asnApplicationConstr \ + asnApplication \ + asnContext\ + asnContextConstr\ + asnChoice \ + asnChoiceConstr \ + asnInteger \ + asnEnumeration \ + asnBoolean \ + asnOctetString \ + asnNull \ + asnUTCTime \ + asnNumericString \ + asnPrintableString \ + asnIA5String\ + asnBMPString\ + asnUTF8String\ + asnBitString \ + asnObjectIdentifer + + # Decoder commands + namespace export \ + asnGetResponse \ + asnGetInteger \ + asnGetEnumeration \ + asnGetOctetString \ + asnGetSequence \ + asnGetSet \ + asnGetApplication \ + asnGetNumericString \ + asnGetPrintableString \ + asnGetIA5String \ + asnGetBMPString \ + asnGetUTF8String \ + asnGetObjectIdentifier \ + asnGetBoolean \ + asnGetUTCTime \ + asnGetBitString \ + asnGetContext + + # general BER utility commands + namespace export \ + asnPeekByte \ + asnGetLength \ + asnRetag \ + asnPeekTag \ + asnTag + +} + +#----------------------------------------------------------------------------- +# Implementation notes: +# +# See the 'asn_ber_intro.txt' in this directory for an introduction +# into BER/DER encoding of ASN.1 information. Bibliography information +# +# A Layman's Guide to a Subset of ASN.1, BER, and DER +# +# An RSA Laboratories Technical Note +# Burton S. Kaliski Jr. +# Revised November 1, 1993 +# +# Supersedes June 3, 1991 version, which was also published as +# NIST/OSI Implementors' Workshop document SEC-SIG-91-17. +# PKCS documents are available by electronic mail to +# . +# +# Copyright (C) 1991-1993 RSA Laboratories, a division of RSA +# Data Security, Inc. License to copy this document is granted +# provided that it is identified as "RSA Data Security, Inc. +# Public-Key Cryptography Standards (PKCS)" in all material +# mentioning or referencing this document. +# 003-903015-110-000-000 +# +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# asnLength : Encode some length data. Helper command. +#----------------------------------------------------------------------------- + +proc ::asn::asnLength {len} { + + if {$len < 0} { + return -code error "Negative length octet requested" + } + if {$len < 128} { + # short form: ISO X.690 8.1.3.4 + return [binary format c $len] + } + # long form: ISO X.690 8.1.3.5 + # try to use a minimal encoding, + # even if not required by BER, but it is required by DER + # take care for signed vs. unsigned issues + if {$len < 256 } { + return [binary format H2c 81 [expr {$len - 256}]] + } + if {$len < 32769} { + # two octet signed value + return [binary format H2S 82 $len] + } + if {$len < 65536} { + return [binary format H2S 82 [expr {$len - 65536}]] + } + if {$len < 8388608} { + # three octet signed value + return [binary format H2cS 83 [expr {$len >> 16}] [expr {($len & 0xFFFF) - 65536}]] + } + if {$len < 16777216} { + # three octet signed value + return [binary format H2cS 83 [expr {($len >> 16) -256}] [expr {($len & 0xFFFF) -65536}]] + } + if {$len < 2147483649} { + # four octet signed value + return [binary format H2I 84 $len] + } + if {$len < 4294967296} { + # four octet unsigned value + return [binary format H2I 84 [expr {$len - 4294967296}]] + } + if {$len < 1099511627776} { + # five octet unsigned value + return [binary format H2 85][string range [binary format W $len] 3 end] + } + if {$len < 281474976710656} { + # six octet unsigned value + return [binary format H2 86][string range [binary format W $len] 2 end] + } + if {$len < 72057594037927936} { + # seven octet value + return [binary format H2 87][string range [binary format W $len] 1 end] + } + + # must be a 64-bit wide signed value + return [binary format H2W 88 $len] +} + +#----------------------------------------------------------------------------- +# asnSequence : Assumes that the arguments are already ASN encoded. +#----------------------------------------------------------------------------- + +proc ::asn::asnSequence {args} { + asnSequenceFromList $args +} + +proc ::asn::asnSequenceFromList {lst} { + # The sequence tag is 0x30. The length is arbitrary and thus full + # length coding is required. The arguments have to be BER encoded + # already. Constructed value, definite-length encoding. + + set out "" + foreach part $lst { + append out $part + } + set len [string length $out] + return [binary format H2a*a$len 30 [asnLength $len] $out] +} + + +#----------------------------------------------------------------------------- +# asnSet : Assumes that the arguments are already ASN encoded. +#----------------------------------------------------------------------------- + +proc ::asn::asnSet {args} { + asnSetFromList $args +} + +proc ::asn::asnSetFromList {lst} { + # The set tag is 0x31. The length is arbitrary and thus full + # length coding is required. The arguments have to be BER encoded + # already. + + set out "" + foreach part $lst { + append out $part + } + set len [string length $out] + return [binary format H2a*a$len 31 [asnLength $len] $out] +} + + +#----------------------------------------------------------------------------- +# asnApplicationConstr +#----------------------------------------------------------------------------- + +proc ::asn::asnApplicationConstr {appNumber args} { + # Packs the arguments into a constructed value with application tag. + + set out "" + foreach part $args { + append out $part + } + set code [expr {0x060 + $appNumber}] + set len [string length $out] + return [binary format ca*a$len $code [asnLength $len] $out] +} + +#----------------------------------------------------------------------------- +# asnApplication +#----------------------------------------------------------------------------- + +proc ::asn::asnApplication {appNumber data} { + # Packs the arguments into a constructed value with application tag. + + set code [expr {0x040 + $appNumber}] + set len [string length $data] + return [binary format ca*a$len $code [asnLength $len] $data] +} + +#----------------------------------------------------------------------------- +# asnContextConstr +#----------------------------------------------------------------------------- + +proc ::asn::asnContextConstr {contextNumber args} { + # Packs the arguments into a constructed value with application tag. + + set out "" + foreach part $args { + append out $part + } + set code [expr {0x0A0 + $contextNumber}] + set len [string length $out] + return [binary format ca*a$len $code [asnLength $len] $out] +} + +#----------------------------------------------------------------------------- +# asnContext +#----------------------------------------------------------------------------- + +proc ::asn::asnContext {contextNumber data} { + # Packs the arguments into a constructed value with application tag. + set code [expr {0x080 + $contextNumber}] + set len [string length $data] + return [binary format ca*a$len $code [asnLength $len] $data] +} +#----------------------------------------------------------------------------- +# asnChoice +#----------------------------------------------------------------------------- + +proc ::asn::asnChoice {appNumber args} { + # Packs the arguments into a choice construction. + + set out "" + foreach part $args { + append out $part + } + set code [expr {0x080 + $appNumber}] + set len [string length $out] + return [binary format ca*a$len $code [asnLength $len] $out] +} + +#----------------------------------------------------------------------------- +# asnChoiceConstr +#----------------------------------------------------------------------------- + +proc ::asn::asnChoiceConstr {appNumber args} { + # Packs the arguments into a choice construction. + + set out "" + foreach part $args { + append out $part + } + set code [expr {0x0A0 + $appNumber}] + set len [string length $out] + return [binary format ca*a$len $code [asnLength $len] $out] +} + +#----------------------------------------------------------------------------- +# asnInteger : Encode integer value. +#----------------------------------------------------------------------------- + +proc ::asn::asnInteger {number} { + asnIntegerOrEnum 02 $number +} + +#----------------------------------------------------------------------------- +# asnEnumeration : Encode enumeration value. +#----------------------------------------------------------------------------- + +proc ::asn::asnEnumeration {number} { + asnIntegerOrEnum 0a $number +} + +#----------------------------------------------------------------------------- +# asnIntegerOrEnum : Common code for Integers and Enumerations +# No Bignum version, as we do not expect large Enums. +#----------------------------------------------------------------------------- + +proc ::asn::asnIntegerOrEnum {tag number} { + # The integer tag is 0x02 , the Enum Tag 0x0a otherwise identical. + # The length is 1, 2, 3, or 4, coded in a + # single byte. This can be done directly, no need to go through + # asnLength. The value itself is written in big-endian. + + # Known bug/issue: The command cannot handle very wide integers, i.e. + # anything above 8 bytes length. Use asnBignumInteger for those. + + # check if we really have an int + set num $number + incr num + + if {($number >= -128) && ($number < 128)} { + return [binary format H2H2c $tag 01 $number] + } + if {($number >= -32768) && ($number < 32768)} { + return [binary format H2H2S $tag 02 $number] + } + if {($number >= -8388608) && ($number < 8388608)} { + set numberb [expr {$number & 0xFFFF}] + set numbera [expr {($number >> 16) & 0xFF}] + return [binary format H2H2cS $tag 03 $numbera $numberb] + } + if {($number >= -2147483648) && ($number < 2147483648)} { + return [binary format H2H2I $tag 04 $number] + } + if {($number >= -549755813888) && ($number < 549755813888)} { + set numberb [expr {$number & 0xFFFFFFFF}] + set numbera [expr {($number >> 32) & 0xFF}] + return [binary format H2H2cI $tag 05 $numbera $numberb] + } + if {($number >= -140737488355328) && ($number < 140737488355328)} { + set numberb [expr {$number & 0xFFFFFFFF}] + set numbera [expr {($number >> 32) & 0xFFFF}] + return [binary format H2H2SI $tag 06 $numbera $numberb] + } + if {($number >= -36028797018963968) && ($number < 36028797018963968)} { + set numberc [expr {$number & 0xFFFFFFFF}] + set numberb [expr {($number >> 32) & 0xFFFF}] + set numbera [expr {($number >> 48) & 0xFF}] + return [binary format H2H2cSI $tag 07 $numbera $numberb $numberc] + } + if {($number >= -9223372036854775808) && ($number <= 9223372036854775807)} { + return [binary format H2H2W $tag 08 $number] + } + return -code error "Integer value to large to encode, use asnBigInteger" +} + +#----------------------------------------------------------------------------- +# asnBigInteger : Encode a long integer value using math::bignum +#----------------------------------------------------------------------------- + +proc ::asn::asnBigInteger {bignum} { + # require math::bignum only if it is used + package require math::bignum + + # this is a hack to check for bignum... + if {[llength $bignum] < 2 || ([lindex $bignum 0] ne "bignum")} { + return -code error "expected math::bignum value got \"$bignum\"" + } + if {[math::bignum::sign $bignum]} { + # generate two's complement form + set bits [math::bignum::bits $bignum] + set padding [expr {$bits % 8}] + set len [expr {int(ceil($bits / 8.0))}] + if {$padding == 0} { + # we need a complete extra byte for the sign + # unless this is a base 2 multiple + set test [math::bignum::fromstr 0] + math::bignum::setbit test [expr {$bits-1}] + if {[math::bignum::ne [math::bignum::abs $bignum] $test]} { + incr len + } + } + set exp [math::bignum::pow \ + [math::bignum::fromstr 256] \ + [math::bignum::fromstr $len]] + set bignum [math::bignum::add $bignum $exp] + set hex [math::bignum::tostr $bignum 16] + } else { + set bits [math::bignum::bits $bignum] + if {($bits % 8) == 0 && $bits > 0} { + set pad "00" + } else { + set pad "" + } + set hex $pad[math::bignum::tostr $bignum 16] + } + if {[string length $hex]%2} { + set hex "0$hex" + } + set octets [expr {(([string length $hex]+1)/2)}] + return [binary format H2a*H* 02 [asnLength $octets] $hex] +} + + +#----------------------------------------------------------------------------- +# asnBoolean : Encode a boolean value. +#----------------------------------------------------------------------------- + +proc ::asn::asnBoolean {bool} { + # The boolean tag is 0x01. The length is always 1, coded in + # a single byte. This can be done directly, no need to go through + # asnLength. The value itself is written in big-endian. + + return [binary format H2H2c 01 01 [expr {$bool ? 0x0FF : 0x0}]] +} + +#----------------------------------------------------------------------------- +# asnOctetString : Encode a string of arbitrary bytes +#----------------------------------------------------------------------------- + +proc ::asn::asnOctetString {string} { + # The octet tag is 0x04. The length is arbitrary, so we need + # 'asnLength' for full coding of the length. + + set len [string length $string] + return [binary format H2a*a$len 04 [asnLength $len] $string] +} + +#----------------------------------------------------------------------------- +# asnNull : Encode a null value +#----------------------------------------------------------------------------- + +proc ::asn::asnNull {} { + # Null has only one valid encoding + return \x05\x00 +} + +#----------------------------------------------------------------------------- +# asnBitstring : Encode a Bit String value +#----------------------------------------------------------------------------- + +proc ::asn::asnBitString {bitstring} { + # The bit string tag is 0x03. + # Bit strings can be either simple or constructed + # we always use simple encoding + + set bitlen [string length $bitstring] + set padding [expr {(8 - ($bitlen % 8)) % 8}] + set len [expr {($bitlen / 8) + 1}] + if {$padding != 0} { incr len } + + return [binary format H2a*cB* 03 [asnLength $len] $padding $bitstring] +} + +#----------------------------------------------------------------------------- +# asnUTCTime : Encode an UTC time string +#----------------------------------------------------------------------------- + +proc ::asn::asnUTCTime {UTCtimestring} { + # the utc time tag is 0x17. + # + # BUG: we do not check the string for well formedness + + set ascii [encoding convertto ascii $UTCtimestring] + set len [string length $ascii] + return [binary format H2a*a* 17 [asnLength $len] $ascii] +} + +#----------------------------------------------------------------------------- +# asnPrintableString : Encode a printable string +#----------------------------------------------------------------------------- +namespace eval asn { + variable nonPrintableChars {[^ A-Za-z0-9'()+,.:/?=-]} +} +proc ::asn::asnPrintableString {string} { + # the printable string tag is 0x13 + variable nonPrintableChars + # it is basically a restricted ascii string + if {[regexp $nonPrintableChars $string ]} { + return -code error "Illegal character in PrintableString." + } + + # check characters + set ascii [encoding convertto ascii $string] + return [asnEncodeString 13 $ascii] +} + +#----------------------------------------------------------------------------- +# asnIA5String : Encode an Ascii String +#----------------------------------------------------------------------------- +proc ::asn::asnIA5String {string} { + # the IA5 string tag is 0x16 + # check for extended charachers + if {[string length $string]!=[string length [encoding convertto utf-8 $string]]} { + return -code error "Illegal character in IA5String" + } + set ascii [encoding convertto ascii $string] + return [asnEncodeString 16 $ascii] +} + +#----------------------------------------------------------------------------- +# asnNumericString : Encode a Numeric String type +#----------------------------------------------------------------------------- +namespace eval asn { + variable nonNumericChars {[^0-9 ]} +} +proc ::asn::asnNumericString {string} { + # the Numeric String type has tag 0x12 + variable nonNumericChars + if {[regexp $nonNumericChars $string]} { + return -code error "Illegal character in Numeric String." + } + + return [asnEncodeString 12 $string] +} +#---------------------------------------------------------------------- +# asnBMPString: Encode a Tcl string as Basic Multinligval (UCS2) string +#----------------------------------------------------------------------- +proc asn::asnBMPString {string} { + if {$::tcl_platform(byteOrder) eq "littleEndian"} { + set bytes "" + foreach {lo hi} [split [encoding convertto unicode $string] ""] { + append bytes $hi $lo + } + } else { + set bytes [encoding convertto unicode $string] + } + return [asnEncodeString 1e $bytes] +} +#--------------------------------------------------------------------------- +# asnUTF8String: encode tcl string as UTF8 String +#---------------------------------------------------------------------------- +proc asn::asnUTF8String {string} { + return [asnEncodeString 0c [encoding convertto utf-8 $string]] +} +#----------------------------------------------------------------------------- +# asnEncodeString : Encode an RestrictedCharacter String +#----------------------------------------------------------------------------- +proc ::asn::asnEncodeString {tag string} { + set len [string length $string] + return [binary format H2a*a$len $tag [asnLength $len] $string] +} + +#----------------------------------------------------------------------------- +# asnObjectIdentifier : Encode an Object Identifier value +#----------------------------------------------------------------------------- +proc ::asn::asnObjectIdentifier {oid} { + # the object identifier tag is 0x06 + + if {[llength $oid] < 2} { + return -code error "OID must have at least two subidentifiers." + } + + # basic check that it is valid + foreach identifier $oid { + if {$identifier < 0} { + return -code error \ + "Malformed OID. Identifiers must be positive Integers." + } + } + + if {[lindex $oid 0] > 2} { + return -code error "First subidentifier must be 0,1 or 2" + } + if {[lindex $oid 1] > 39} { + return -code error \ + "Second subidentifier must be between 0 and 39" + } + + # handle the special cases directly + switch [llength $oid] { + 2 { return [binary format H2H2c 06 01 \ + [expr {[lindex $oid 0]*40+[lindex $oid 1]}]] } + default { + # This can probably be written much shorter. + # Just a first try that works... + # + set octets [binary format c \ + [expr {[lindex $oid 0]*40+[lindex $oid 1]}]] + foreach identifier [lrange $oid 2 end] { + set d 128 + if {$identifier < 128} { + set subidentifier [list $identifier] + } else { + set subidentifier [list] + # find the largest divisor + + while {($identifier / $d) >= 128} { + set d [expr {$d * 128}] + } + # and construct the subidentifiers + set remainder $identifier + while {$d >= 128} { + set coefficient [expr {($remainder / $d) | 0x80}] + set remainder [expr {$remainder % $d}] + set d [expr {$d / 128}] + lappend subidentifier $coefficient + } + lappend subidentifier $remainder + } + append octets [binary format c* $subidentifier] + } + return [binary format H2a*a* 06 \ + [asnLength [string length $octets]] $octets] + } + } + +} + +#----------------------------------------------------------------------------- +# asnGetResponse : Read a ASN response from a channel. +#----------------------------------------------------------------------------- + +proc ::asn::asnGetResponse {sock data_var} { + upvar 1 $data_var data + + # We expect a sequence here (tag 0x30). The code below is an + # inlined replica of 'asnGetSequence', modified for reading from a + # channel instead of a string. + + set tag [read $sock 1] + + if {$tag == "\x30"} { + # The following code is a replica of 'asnGetLength', modified + # for reading the bytes from the channel instead of a string. + + set len1 [read $sock 1] + binary scan $len1 c num + set length [expr {($num + 0x100) % 0x100}] + + if {$length >= 0x080} { + # The byte the read is not the length, but a prefix, and + # the lower nibble tells us how many bytes follow. + + set len_length [expr {$length & 0x7f}] + + # BUG: We should not perform the value extraction for an + # BUG: improper length. It wastes cycles, and here it can + # BUG: cause us trouble, reading more data than there is + # BUG: on the channel. Depending on the channel + # BUG: configuration an attacker can induce us to block, + # BUG: causing a denial of service. + set lengthBytes [read $sock $len_length] + + switch $len_length { + 1 { + binary scan $lengthBytes c length + set length [expr {($length + 0x100) % 0x100}] + } + 2 { binary scan $lengthBytes S length } + 3 { binary scan \x00$lengthBytes I length } + 4 { binary scan $lengthBytes I length } + default { + return -code error \ + "length information too long ($len_length)" + } + } + } + + # Now that the length is known we get the remainder, + # i.e. payload, and construct proper in-memory BER encoded + # sequence. + + set rest [read $sock $length] + set data [binary format aa*a$length $tag [asnLength $length] $rest] + } else { + # Generate an error message if the data is not a sequence as + # we expected. + + set tag_hex "" + binary scan $tag H2 tag_hex + return -code error "unknown start tag [string length $tag] $tag_hex" + } +} + +if {[package vsatisfies [package present Tcl] 8.5.0 9]} { +############################################################################## +# Code for 8.5 +############################################################################## +#----------------------------------------------------------------------------- +# asnGetByte (8.5 version) : Retrieve a single byte from the data (unsigned) +#----------------------------------------------------------------------------- + +proc ::asn::asnGetByte {data_var byte_var} { + upvar 1 $data_var data $byte_var byte + + binary scan [string index $data 0] cu byte + set data [string range $data 1 end] + + return +} + +#----------------------------------------------------------------------------- +# asnPeekByte (8.5 version) : Retrieve a single byte from the data (unsigned) +# without removing it. +#----------------------------------------------------------------------------- + +proc ::asn::asnPeekByte {data_var byte_var {offset 0}} { + upvar 1 $data_var data $byte_var byte + + binary scan [string index $data $offset] cu byte + + return +} + +#----------------------------------------------------------------------------- +# asnGetLength (8.5 version) : Decode an ASN length value (See notes) +#----------------------------------------------------------------------------- + +proc ::asn::asnGetLength {data_var length_var} { + upvar 1 $data_var data $length_var length + + asnGetByte data length + if {$length == 0x080} { + return -code error "Indefinite length BER encoding not yet supported" + } + if {$length > 0x080} { + # The retrieved byte is a prefix value, and the integer in the + # lower nibble tells us how many bytes were used to encode the + # length data following immediately after this prefix. + + set len_length [expr {$length & 0x7f}] + + if {[string length $data] < $len_length} { + return -code error \ + "length information invalid, not enough octets left" + } + + asnGetBytes data $len_length lengthBytes + + switch $len_length { + 1 { binary scan $lengthBytes cu length } + 2 { binary scan $lengthBytes Su length } + 3 { binary scan \x00$lengthBytes Iu length } + 4 { binary scan $lengthBytes Iu length } + default { + binary scan $lengthBytes H* hexstr + scan $hexstr %llx length + } + } + } + return +} + +} else { +############################################################################## +# Code for Tcl 8.4 +############################################################################## +#----------------------------------------------------------------------------- +# asnGetByte : Retrieve a single byte from the data (unsigned) +#----------------------------------------------------------------------------- + +proc ::asn::asnGetByte {data_var byte_var} { + upvar 1 $data_var data $byte_var byte + + binary scan [string index $data 0] c byte + set byte [expr {($byte + 0x100) % 0x100}] + set data [string range $data 1 end] + + return +} + +#----------------------------------------------------------------------------- +# asnPeekByte : Retrieve a single byte from the data (unsigned) +# without removing it. +#----------------------------------------------------------------------------- + +proc ::asn::asnPeekByte {data_var byte_var {offset 0}} { + upvar 1 $data_var data $byte_var byte + + binary scan [string index $data $offset] c byte + set byte [expr {($byte + 0x100) % 0x100}] + + return +} + +#----------------------------------------------------------------------------- +# asnGetLength : Decode an ASN length value (See notes) +#----------------------------------------------------------------------------- + +proc ::asn::asnGetLength {data_var length_var} { + upvar 1 $data_var data $length_var length + + asnGetByte data length + if {$length == 0x080} { + return -code error "Indefinite length BER encoding not yet supported" + } + if {$length > 0x080} { + # The retrieved byte is a prefix value, and the integer in the + # lower nibble tells us how many bytes were used to encode the + # length data following immediately after this prefix. + + set len_length [expr {$length & 0x7f}] + + if {[string length $data] < $len_length} { + return -code error \ + "length information invalid, not enough octets left" + } + + asnGetBytes data $len_length lengthBytes + + switch $len_length { + 1 { + # Efficiently coded data will not go through this + # path, as small length values can be coded directly, + # without a prefix. + + binary scan $lengthBytes c length + set length [expr {($length + 0x100) % 0x100}] + } + 2 { binary scan $lengthBytes S length + set length [expr {($length + 0x10000) % 0x10000}] + } + 3 { binary scan \x00$lengthBytes I length + set length [expr {($length + 0x1000000) % 0x1000000}] + } + 4 { binary scan $lengthBytes I length + set length [expr {(wide($length) + 0x100000000) % 0x100000000}] + } + default { + binary scan $lengthBytes H* hexstr + # skip leading zeros which are allowed by BER + set hexlen [string trimleft $hexstr 0] + # check if it fits into a 64-bit signed integer + if {[string length $hexlen] > 16} { + return -code error -errorcode {ARITH IOVERFLOW + {Length value too large for normal use, try asnGetBigLength}} \ + "Length value to large" + } elseif { [string length $hexlen] == 16 \ + && ([string index $hexlen 0] & 0x8)} { + # check most significant bit, if set we need bignum + return -code error -errorcode {ARITH IOVERFLOW + {Length value too large for normal use, try asnGetBigLength}} \ + "Length value to large" + } else { + scan $hexstr "%lx" length + } + } + } + } + return +} + +} + +#----------------------------------------------------------------------------- +# asnRetag: Remove an explicit tag with the real newTag +# +#----------------------------------------------------------------------------- +proc ::asn::asnRetag {data_var newTag} { + upvar 1 $data_var data + set tag "" + set type "" + set len [asnPeekTag data tag type dummy] + asnGetBytes data $len tagbytes + set data [binary format c* $newTag]$data +} + +#----------------------------------------------------------------------------- +# asnGetBytes : Retrieve a block of 'length' bytes from the data. +#----------------------------------------------------------------------------- + +proc ::asn::asnGetBytes {data_var length bytes_var} { + upvar 1 $data_var data $bytes_var bytes + + incr length -1 + set bytes [string range $data 0 $length] + incr length + set data [string range $data $length end] + + return +} + +#----------------------------------------------------------------------------- +# asnPeekTag : Decode the tag value +#----------------------------------------------------------------------------- + +proc ::asn::asnPeekTag {data_var tag_var tagtype_var constr_var} { + upvar 1 $data_var data $tag_var tag $tagtype_var tagtype $constr_var constr + + set type 0 + set offset 0 + asnPeekByte data type $offset + # check if we have a simple tag, < 31, which fits in one byte + + set tval [expr {$type & 0x1f}] + if {$tval == 0x1f} { + # long tag, max 64-bit with Tcl 8.4, unlimited with 8.5 bignum + asnPeekByte data tagbyte [incr offset] + set tval [expr {wide($tagbyte & 0x7f)}] + while {($tagbyte & 0x80)} { + asnPeekByte data tagbyte [incr offset] + set tval [expr {($tval << 7) + ($tagbyte & 0x7f)}] + } + } + + set tagtype [lindex {UNIVERSAL APPLICATION CONTEXT PRIVATE} \ + [expr {($type & 0xc0) >>6}]] + set tag $tval + set constr [expr {($type & 0x20) > 0}] + + return [incr offset] +} + +#----------------------------------------------------------------------------- +# asnTag : Build a tag value +#----------------------------------------------------------------------------- + +proc ::asn::asnTag {tagnumber {class UNIVERSAL} {tagstyle P}} { + set first 0 + if {$tagnumber < 31} { + # encode everything in one byte + set first $tagnumber + set bytes [list] + } else { + # multi-byte tag + set first 31 + set bytes [list [expr {$tagnumber & 0x7f}]] + set tagnumber [expr {$tagnumber >> 7}] + while {$tagnumber > 0} { + lappend bytes [expr {($tagnumber & 0x7f)+0x80}] + set tagnumber [expr {$tagnumber >>7}] + } + + } + + if {$tagstyle eq "C" || $tagstyle == 1 } {incr first 32} + switch -glob -- $class { + U* { ;# UNIVERSAL } + A* { incr first 64 ;# APPLICATION } + C* { incr first 128 ;# CONTEXT } + P* { incr first 192 ;# PRIVATE } + default { + return -code error "Unknown tag class \"$class\"" + } + } + if {[llength $bytes] > 0} { + # long tag + set rbytes [list] + for {set i [expr {[llength $bytes]-1}]} {$i >= 0} {incr i -1} { + lappend rbytes [lindex $bytes $i] + } + return [binary format cc* $first $rbytes ] + } + return [binary format c $first] +} + + + +#----------------------------------------------------------------------------- +# asnGetBigLength : Retrieve a length that can not be represented in 63-bit +#----------------------------------------------------------------------------- + +proc ::asn::asnGetBigLength {data_var biglength_var} { + + # Does any real world code really need this? + # If we encounter this, we are doomed to fail anyway, + # (there would be an Exabyte inside the data_var, ) + # + # So i implement it just for completeness. + # + package require math::bignum + + upvar 1 $data_var data $biglength_var length + + asnGetByte data length + if {$length == 0x080} { + return -code error "Indefinite length BER encoding not yet supported" + } + if {$length > 0x080} { + # The retrieved byte is a prefix value, and the integer in the + # lower nibble tells us how many bytes were used to encode the + # length data following immediately after this prefix. + + set len_length [expr {$length & 0x7f}] + + if {[string length $data] < $len_length} { + return -code error \ + "length information invalid, not enough octets left" + } + + asnGetBytes data $len_length lengthBytes + binary scan $lengthBytes H* hexlen + set length [math::bignum::fromstr $hexlen 16] + } + return +} + +#----------------------------------------------------------------------------- +# asnGetInteger : Retrieve integer. +#----------------------------------------------------------------------------- + +proc ::asn::asnGetInteger {data_var int_var} { + # Tag is 0x02. + + upvar 1 $data_var data $int_var int + + asnGetByte data tag + + if {$tag != 0x02} { + return -code error \ + [format "Expected Integer (0x02), but got %02x" $tag] + } + + asnGetLength data len + asnGetBytes data $len integerBytes + + set int ? + + switch $len { + 1 { binary scan $integerBytes c int } + 2 { binary scan $integerBytes S int } + 3 { + # check for negative int and pad + scan [string index $integerBytes 0] %c byte + if {$byte & 128} { + binary scan \xff$integerBytes I int + } else { + binary scan \x00$integerBytes I int + } + } + 4 { binary scan $integerBytes I int } + 5 - + 6 - + 7 - + 8 { + # check for negative int and pad + scan [string index $integerBytes 0] %c byte + if {$byte & 128} { + set pad [string repeat \xff [expr {8-$len}]] + } else { + set pad [string repeat \x00 [expr {8-$len}]] + } + binary scan $pad$integerBytes W int + } + default { + # Too long, or prefix coding was used. + return -code error "length information too long" + } + } + return +} + +#----------------------------------------------------------------------------- +# asnGetBigInteger : Retrieve a big integer. +#----------------------------------------------------------------------------- + +proc ::asn::asnGetBigInteger {data_var bignum_var} { + # require math::bignum only if it is used + package require math::bignum + + # Tag is 0x02. We expect that the length of the integer is coded with + # maximal efficiency, i.e. without a prefix 0x81 prefix. If a prefix + # is used this decoder will fail. + + upvar $data_var data $bignum_var bignum + + asnGetByte data tag + + if {$tag != 0x02} { + return -code error \ + [format "Expected Integer (0x02), but got %02x" $tag] + } + + asnGetLength data len + asnGetBytes data $len integerBytes + + binary scan [string index $integerBytes 0] H* hex_head + set head [expr 0x$hex_head] + set replacement_head [expr {$head & 0x7f}] + set integerBytes [string replace $integerBytes 0 0 [format %c $replacement_head]] + + binary scan $integerBytes H* hex + + set bignum [math::bignum::fromstr $hex 16] + + if {($head >> 7) && 1} { + set bigsub [math::bignum::pow [::math::bignum::fromstr 2] [::math::bignum::fromstr [expr {($len * 8) - 1}]]] + set bignum [math::bignum::sub $bignum $bigsub] + } + + return $bignum +} + + + + +#----------------------------------------------------------------------------- +# asnGetEnumeration : Retrieve an enumeration id +#----------------------------------------------------------------------------- + +proc ::asn::asnGetEnumeration {data_var enum_var} { + # This is like 'asnGetInteger', except for a different tag. + + upvar 1 $data_var data $enum_var enum + + asnGetByte data tag + + if {$tag != 0x0a} { + return -code error \ + [format "Expected Enumeration (0x0a), but got %02x" $tag] + } + + asnGetLength data len + asnGetBytes data $len integerBytes + set enum ? + + switch $len { + 1 { binary scan $integerBytes c enum } + 2 { binary scan $integerBytes S enum } + 3 { binary scan \x00$integerBytes I enum } + 4 { binary scan $integerBytes I enum } + default { + return -code error "length information too long" + } + } + return +} + +#----------------------------------------------------------------------------- +# asnGetOctetString : Retrieve arbitrary string. +#----------------------------------------------------------------------------- + +proc ::asn::asnGetOctetString {data_var string_var} { + # Here we need the full decoder for length data. + + upvar 1 $data_var data $string_var string + + asnGetByte data tag + if {$tag != 0x04} { + return -code error \ + [format "Expected Octet String (0x04), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length temp + set string $temp + return +} + +#----------------------------------------------------------------------------- +# asnGetSequence : Retrieve Sequence data for further decoding. +#----------------------------------------------------------------------------- + +proc ::asn::asnGetSequence {data_var sequence_var} { + # Here we need the full decoder for length data. + + upvar 1 $data_var data $sequence_var sequence + + asnGetByte data tag + if {$tag != 0x030} { + return -code error \ + [format "Expected Sequence (0x30), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length temp + set sequence $temp + return +} + +#----------------------------------------------------------------------------- +# asnGetSet : Retrieve Set data for further decoding. +#----------------------------------------------------------------------------- + +proc ::asn::asnGetSet {data_var set_var} { + # Here we need the full decoder for length data. + + upvar 1 $data_var data $set_var set + + asnGetByte data tag + if {$tag != 0x031} { + return -code error \ + [format "Expected Set (0x31), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length temp + set set $temp + return +} + +#----------------------------------------------------------------------------- +# asnGetApplication +#----------------------------------------------------------------------------- + +proc ::asn::asnGetApplication {data_var appNumber_var {content_var {}} {encodingType_var {}} } { + upvar 1 $data_var data $appNumber_var appNumber + + asnGetByte data tag + asnGetLength data length + + if {($tag & 0xC0) != 0x40} { + return -code error \ + [format "Expected Application, but got %02x" $tag] + } + if {$encodingType_var != {}} { + upvar 1 $encodingType_var encodingType + set encodingType [expr {($tag & 0x20) > 0}] + } + set appNumber [expr {$tag & 0x1F}] + if {[string length $content_var]} { + upvar 1 $content_var content + asnGetBytes data $length content + } + return +} + +#----------------------------------------------------------------------------- +# asnGetBoolean: decode a boolean value +#----------------------------------------------------------------------------- + +proc asn::asnGetBoolean {data_var bool_var} { + upvar 1 $data_var data $bool_var bool + + asnGetByte data tag + if {$tag != 0x01} { + return -code error \ + [format "Expected Boolean (0x01), but got %02x" $tag] + } + + asnGetLength data length + asnGetByte data byte + set bool [expr {$byte == 0 ? 0 : 1}] + return +} + +#----------------------------------------------------------------------------- +# asnGetUTCTime: Extract an UTC Time string from the data. Returns a string +# representing an UTC Time. +# +#----------------------------------------------------------------------------- + +proc asn::asnGetUTCTime {data_var utc_var} { + upvar 1 $data_var data $utc_var utc + + asnGetByte data tag + if {$tag != 0x17} { + return -code error \ + [format "Expected UTCTime (0x17), but got %02x" $tag] + } + + asnGetLength data length + asnGetBytes data $length bytes + + # this should be ascii, make it explicit + set bytes [encoding convertfrom ascii $bytes] + binary scan $bytes a* utc + + return +} + + +#----------------------------------------------------------------------------- +# asnGetBitString: Extract a Bit String value (a string of 0/1s) from the +# ASN.1 data. +# +#----------------------------------------------------------------------------- + +proc asn::asnGetBitString {data_var bitstring_var} { + upvar 1 $data_var data $bitstring_var bitstring + + asnGetByte data tag + if {$tag != 0x03} { + return -code error \ + [format "Expected Bit String (0x03), but got %02x" $tag] + } + + asnGetLength data length + # get the number of padding bits used at the end + asnGetByte data padding + incr length -1 + asnGetBytes data $length bytes + binary scan $bytes B* bits + + # cut off the padding bits + set bits [string range $bits 0 end-$padding] + set bitstring $bits +} + +#----------------------------------------------------------------------------- +# asnGetObjectIdentifier: Decode an ASN.1 Object Identifier (OID) into +# a Tcl list of integers. +#----------------------------------------------------------------------------- + +proc asn::asnGetObjectIdentifier {data_var oid_var} { + upvar 1 $data_var data $oid_var oid + + asnGetByte data tag + if {$tag != 0x06} { + return -code error \ + [format "Expected Object Identifier (0x06), but got %02x" $tag] + } + asnGetLength data length + + # the first byte encodes the OID parts in position 0 and 1 + asnGetByte data val + set oid [expr {$val / 40}] + lappend oid [expr {$val % 40}] + incr length -1 + + # the next bytes encode the remaining parts of the OID + set bytes [list] + set incomplete 0 + while {$length} { + asnGetByte data octet + incr length -1 + if {$octet < 128} { + set oidval $octet + set mult 128 + foreach byte $bytes { + if {$byte != {}} { + incr oidval [expr {$mult*$byte}] + set mult [expr {$mult*128}] + } + } + lappend oid $oidval + set bytes [list] + set incomplete 0 + } else { + set byte [expr {$octet-128}] + set bytes [concat [list $byte] $bytes] + set incomplete 1 + } + } + if {$incomplete} { + return -code error "OID Data is incomplete, not enough octets." + } + return +} + +#----------------------------------------------------------------------------- +# asnGetContext: Decode an explicit context tag +# +#----------------------------------------------------------------------------- + +proc ::asn::asnGetContext {data_var contextNumber_var {content_var {}} {encodingType_var {}}} { + upvar 1 $data_var data $contextNumber_var contextNumber + + asnGetByte data tag + asnGetLength data length + + if {($tag & 0xC0) != 0x80} { + return -code error \ + [format "Expected Context, but got %02x" $tag] + } + if {$encodingType_var != {}} { + upvar 1 $encodingType_var encodingType + set encodingType [expr {($tag & 0x20) > 0}] + } + set contextNumber [expr {$tag & 0x1F}] + if {[string length $content_var]} { + upvar 1 $content_var content + asnGetBytes data $length content + } + return +} + + +#----------------------------------------------------------------------------- +# asnGetNumericString: Decode a Numeric String from the data +#----------------------------------------------------------------------------- + +proc ::asn::asnGetNumericString {data_var print_var} { + upvar 1 $data_var data $print_var print + + asnGetByte data tag + if {$tag != 0x12} { + return -code error \ + [format "Expected Numeric String (0x12), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length string + set print [encoding convertfrom ascii $string] + return +} + +#----------------------------------------------------------------------------- +# asnGetPrintableString: Decode a Printable String from the data +#----------------------------------------------------------------------------- + +proc ::asn::asnGetPrintableString {data_var print_var} { + upvar 1 $data_var data $print_var print + + asnGetByte data tag + if {$tag != 0x13} { + return -code error \ + [format "Expected Printable String (0x13), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length string + set print [encoding convertfrom ascii $string] + return +} + +#----------------------------------------------------------------------------- +# asnGetIA5String: Decode a IA5(ASCII) String from the data +#----------------------------------------------------------------------------- + +proc ::asn::asnGetIA5String {data_var print_var} { + upvar 1 $data_var data $print_var print + + asnGetByte data tag + if {$tag != 0x16} { + return -code error \ + [format "Expected IA5 String (0x16), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length string + set print [encoding convertfrom ascii $string] + return +} +#------------------------------------------------------------------------ +# asnGetBMPString: Decode Basic Multiningval (UCS2 string) from data +#------------------------------------------------------------------------ +proc asn::asnGetBMPString {data_var print_var} { + upvar 1 $data_var data $print_var print + asnGetByte data tag + if {$tag != 0x1e} { + return -code error \ + [format "Expected BMP String (0x1e), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length string + if {$::tcl_platform(byteOrder) eq "littleEndian"} { + set str2 "" + foreach {hi lo} [split $string ""] { + append str2 $lo $hi + } + } else { + set str2 $string + } + set print [encoding convertfrom unicode $str2] + return +} +#------------------------------------------------------------------------ +# asnGetUTF8String: Decode UTF8 string from data +#------------------------------------------------------------------------ +proc asn::asnGetUTF8String {data_var print_var} { + upvar 1 $data_var data $print_var print + asnGetByte data tag + if {$tag != 0x0c} { + return -code error \ + [format "Expected UTF8 String (0x0c), but got %02x" $tag] + } + asnGetLength data length + asnGetBytes data $length string + #there should be some error checking to see if input is + #properly-formatted utf8 + set print [encoding convertfrom utf-8 $string] + + return +} +#----------------------------------------------------------------------------- +# asnGetNull: decode a NULL value +#----------------------------------------------------------------------------- + +proc ::asn::asnGetNull {data_var} { + upvar 1 $data_var data + + asnGetByte data tag + if {$tag != 0x05} { + return -code error \ + [format "Expected NULL (0x05), but got %02x" $tag] + } + + asnGetLength data length + asnGetBytes data $length bytes + + # we do not check the null data, all bytes must be 0x00 + + return +} + +#---------------------------------------------------------------------------- +# MultiType string routines +#---------------------------------------------------------------------------- + +namespace eval asn { + variable stringTypes + array set stringTypes { + 12 NumericString + 13 PrintableString + 16 IA5String + 1e BMPString + 0c UTF8String + 14 T61String + 15 VideotexString + 1a VisibleString + 1b GeneralString + 1c UniversalString + } + variable defaultStringType UTF8 +} +#--------------------------------------------------------------------------- +# asnGetString - get readable string automatically detecting its type +#--------------------------------------------------------------------------- +proc ::asn::asnGetString {data_var print_var {type_var {}}} { + variable stringTypes + upvar 1 $data_var data $print_var print + asnPeekByte data tag + set tag [format %02x $tag] + if {![info exists stringTypes($tag)]} { + return -code error "Expected one of string types, but got $tag" + } + asnGet$stringTypes($tag) data print + if {[string length $type_var]} { + upvar $type_var type + set type $stringTypes($tag) + } +} +#--------------------------------------------------------------------- +# defaultStringType - set or query default type for unrestricted strings +#--------------------------------------------------------------------- +proc ::asn::defaultStringType {{type {}}} { + variable defaultStringType + if {![string length $type]} { + return $defaultStringType + } + if {$type ne "BMP" && $type ne "UTF8"} { + return -code error "Invalid default string type. Should be one of BMP, UTF8" + } + set defaultStringType $type + return +} + +#--------------------------------------------------------------------------- +# asnString - encode readable string into most restricted type possible +#--------------------------------------------------------------------------- + +proc ::asn::asnString {string} { + variable nonPrintableChars + variable nonNumericChars + if {[string length $string]!=[string length [encoding convertto utf-8 $string]]} { + # There are non-ascii character + variable defaultStringType + return [asn${defaultStringType}String $string] + } elseif {![regexp $nonNumericChars $string]} { + return [asnNumericString $string] + } elseif {![regexp $nonPrintableChars $string]} { + return [asnPrintableString $string] + } else { + return [asnIA5String $string] + } +} + +#----------------------------------------------------------------------------- +package provide asn 0.8.5 + diff --git a/src/vendorlib_tcl9/tcllib2.0/asn/pkgIndex.tcl b/src/vendorlib_tcl9/tcllib2.0/asn/pkgIndex.tcl new file mode 100644 index 00000000..b9fdabb8 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/asn/pkgIndex.tcl @@ -0,0 +1,4 @@ +# Tcl package index file, version 1.1 + +if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} +package ifneeded asn 0.8.5 [list source [file join $dir asn.tcl]] diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/base32.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/base32.tcl new file mode 100644 index 00000000..563ea6c2 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/base32.tcl @@ -0,0 +1,180 @@ +# -*- tcl -*- +# This code is hereby put into the public domain. +# ### ### ### ######### ######### ######### +## Overview +# Base32 encoding and decoding of small strings. +# +# Management code for switching between Tcl and C accelerated +# implementations. + +# @mdgen EXCLUDE: base32_c.tcl + +package require Tcl 8.5 9 + +namespace eval ::base32 {} + +# ### ### ### ######### ######### ######### +## Management of base32 std implementations. + +# ::base32::LoadAccelerator -- +# +# Loads a named implementation, if possible. +# +# Arguments: +# key Name of the implementation to load. +# +# Results: +# A boolean flag. True if the implementation +# was successfully loaded; and False otherwise. + +proc ::base32::LoadAccelerator {key} { + variable accel + set isok 0 + switch -exact -- $key { + critcl { + # Critcl implementation of base32 requires Tcl 8.4. + if {![package vsatisfies [package provide Tcl] 8.4]} {return 0} + if {[catch {package require tcllibc}]} {return 0} + set isok [llength [info commands ::base32::critcl_encode]] + } + tcl { + variable selfdir + if {[catch {source [file join $selfdir base32_tcl.tcl]}]} {return 0} + set isok [llength [info commands ::base32::tcl_encode]] + } + default { + return -code error "invalid accelerator $key:\ + must be one of [join [KnownImplementations] {, }]" + } + } + set accel($key) $isok + return $isok +} + +# ::base32::SwitchTo -- +# +# Activates a loaded named implementation. +# +# Arguments: +# key Name of the implementation to activate. +# +# Results: +# None. + +proc ::base32::SwitchTo {key} { + variable accel + variable loaded + + if {[string equal $key $loaded]} { + # No change, nothing to do. + return + } elseif {![string equal $key ""]} { + # Validate the target implementation of the switch. + + if {![info exists accel($key)]} { + return -code error "Unable to activate unknown implementation \"$key\"" + } elseif {![info exists accel($key)] || !$accel($key)} { + return -code error "Unable to activate missing implementation \"$key\"" + } + } + + # Deactivate the previous implementation, if there was any. + + if {![string equal $loaded ""]} { + foreach c {encode decode} { + rename ::base32::$c ::base32::${loaded}_$c + } + } + + # Activate the new implementation, if there is any. + + if {![string equal $key ""]} { + foreach c {encode decode} { + rename ::base32::${key}_$c ::base32::$c + } + } + + # Remember the active implementation, for deactivation by future + # switches. + + set loaded $key + return +} + +# ::base32::Implementations -- +# +# Determines which implementations are +# present, i.e. loaded. +# +# Arguments: +# None. +# +# Results: +# A list of implementation keys. + +proc ::base32::Implementations {} { + variable accel + set res {} + foreach n [array names accel] { + if {!$accel($n)} continue + lappend res $n + } + return $res +} + +# ::base32::KnownImplementations -- +# +# Determines which implementations are known +# as possible implementations. +# +# Arguments: +# None. +# +# Results: +# A list of implementation keys. In the order +# of preference, most prefered first. + +proc ::base32::KnownImplementations {} { + return {critcl tcl} +} + +proc ::base32::Names {} { + return { + critcl {tcllibc based} + tcl {pure Tcl} + } +} + +# ### ### ### ######### ######### ######### +## Initialization: Data structures. + +namespace eval ::base32 { + variable selfdir [file dirname [info script]] + variable loaded {} + + variable accel + array set accel {tcl 0 critcl 0} +} + +# ### ### ### ######### ######### ######### +## Initialization: Choose an implementation, +## most prefered first. Loads only one of the +## possible implementations. And activates it. + +namespace eval ::base32 { + variable e + foreach e [KnownImplementations] { + if {[LoadAccelerator $e]} { + SwitchTo $e + break + } + } + unset e + + namespace export encode decode +} + +# ### ### ### ######### ######### ######### +## Ready + +package provide base32 0.2 diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/base32_c.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/base32_c.tcl new file mode 100644 index 00000000..3741c3fd --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/base32_c.tcl @@ -0,0 +1,254 @@ +# base32c.tcl -- +# +# Implementation of a base32 (std) de/encoder for Tcl. +# +# Public domain +# +# RCS: @(#) $Id: base32_c.tcl,v 1.3 2008/01/28 22:58:18 andreas_kupries Exp $ + +package require critcl +package require Tcl 8.5 9 + +namespace eval ::base32 { + # Supporting code for the main command. + catch { + #critcl::cheaders -g + #critcl::debug memory symbols + } + + # Main commands, encoder & decoder + + critcl::ccommand critcl_encode {dummy interp objc objv} { + /* Syntax -*- c -*- + * critcl_encode string + */ + + unsigned char* buf; + Tcl_Size nbuf; + + unsigned char* out; + unsigned char* at; + int nout; + + /* + * The array used for encoding + */ /* 123456789 123456789 123456789 12 */ + static const char map[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +#define USAGEE "bitstring" + + if (objc != 2) { + Tcl_WrongNumArgs (interp, 1, objv, USAGEE); /* OK tcl9 */ + return TCL_ERROR; + } + + buf = Tcl_GetBytesFromObj (interp, objv[1], &nbuf); /* OK tcl9 */ + if (buf == NULL) return TCL_ERROR; + nout = ((nbuf+4)/5)*8; + out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); + + for (at = out; nbuf >= 5; nbuf -= 5, buf += 5) { + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; + *(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; + *(at++) = map [ 0x1f & (buf[3]>>2) ]; + *(at++) = map [ 0x1f & ((buf[3]<<3) | (buf[4]>>5)) ]; + *(at++) = map [ 0x1f & (buf[4]) ]; + } + if (nbuf > 0) { + /* Process partials at end. */ + switch (nbuf) { + case 1: + /* |01234567| 2, padding 6 + * xxxxx + * xxx 00 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & (buf[0]<<2) ]; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + break; + case 2: /* x3/=4 */ + /* |01234567|01234567| 4, padding 4 + * xxxxx + * xxx xx + * xxxxx + * x 0000 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & (buf[1]<<4) ]; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + break; + case 3: + /* |01234567|01234567|01234567| 5, padding 3 + * xxxxx + * xxx xx + * xxxxx + * x xxxx + * xxxx 0 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; + *(at++) = map [ 0x1f & (buf[2]<<1) ]; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + break; + case 4: + /* |01234567|01234567|01234567|012334567| 7, padding 1 + * xxxxx + * xxx xx + * xxxxx + * x xxxx + * xxxx + * xxxxx + * xxxx 0 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; + *(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; + *(at++) = map [ 0x1f & (buf[3]>>2) ]; + *(at++) = map [ 0x1f & (buf[3]<<3) ]; + *(at++) = '='; + break; + } + } + + Tcl_SetObjResult (interp, Tcl_NewStringObj ((char*)out, nout)); /* OK tcl9 */ + Tcl_Free ((char*) out); + return TCL_OK; + } + + + critcl::ccommand critcl_decode {dummy interp objc objv} { + /* Syntax -*- c -*- + * critcl_decode estring + */ + + unsigned char* buf; + Tcl_Size nbuf; + + unsigned char* out; + unsigned char* at; + unsigned char x [8]; + int nout; + + int i, j, a, pad, nx; + + /* + * An array for translating single base-32 characters into a value. + * Disallowed input characters have a value of 64. Upper and lower + * case is the same. Only 128 chars, as everything above char(127) + * is 64. + */ + static const char map [] = { + /* \00 */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + /* DLE */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + /* SPC */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + /* '0' */ 64, 64, 26, 27, 28, 29, 30, 31, 64, 64, 64, 64, 64, 64, 64, 64, + /* '@' */ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 'P' */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + /* '`' */ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 'p' */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64 + }; + +#define USAGED "estring" + + if (objc != 2) { + Tcl_WrongNumArgs (interp, 1, objv, USAGED); /* OK tcl9 */ + return TCL_ERROR; + } + + buf = (unsigned char*) Tcl_GetStringFromObj (objv[1], &nbuf); /* OK tcl9 */ + + if (nbuf % 8) { + Tcl_SetObjResult (interp, Tcl_NewStringObj ("Length is not a multiple of 8", -1)); /* OK tcl9 */ + return TCL_ERROR; + } + + nout = (nbuf/8)*5 *TCL_UTF_MAX; + out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); + +#define HIGH(x) (((x) & 0x80) != 0) +#define BADC(x) ((x) == 64) +#define BADCHAR(a,j) (HIGH ((a)) || BADC (x [(j)] = map [(a)])) + + for (pad = 0, i=0, at = out; i < nbuf; i += 8, buf += 8){ + for (j=0; j < 8; j++){ + a = buf [j]; + + if (a == '=') { + x[j] = 0; + pad++; + continue; + } else if (pad) { + char msg [120]; + sprintf (msg, + "Invalid character at index %d: \"=\" (padding found in the middle of the input)", + j-1); + Tcl_Free ((char*) out); + Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ + return TCL_ERROR; + } + + if (BADCHAR (a,j)) { + char msg [100]; + sprintf (msg,"Invalid character at index %d: \"%c\"",j,a); + Tcl_Free ((char*) out); + Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ + return TCL_ERROR; + } + } + + *(at++) = (x[0]<<3) | (x[1]>>2) ; + *(at++) = (x[1]<<6) | (x[2]<<1) | (x[3]>>4); + *(at++) = (x[3]<<4) | (x[4]>>1) ; + *(at++) = (x[4]<<7) | (x[5]<<2) | (x[6]>>3); + *(at++) = (x[6]<<5) | x[7] ; + } + + if (pad) { + if (pad == 1) { + at -= 1; + } else if (pad == 3) { + at -= 2; + } else if (pad == 4) { + at -= 3; + } else if (pad == 6) { + at -= 4; + } else { + char msg [100]; + sprintf (msg,"Invalid padding of length %d",pad); + Tcl_Free ((char*) out); + Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ + return TCL_ERROR; + } + } + + Tcl_SetObjResult (interp, Tcl_NewByteArrayObj (out, at-out)); /* OK tcl9 */ + Tcl_Free ((char*) out); + return TCL_OK; + } +} + +# ### ### ### ######### ######### ######### +## Ready diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/base32_tcl.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/base32_tcl.tcl new file mode 100644 index 00000000..a8d50335 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/base32_tcl.tcl @@ -0,0 +1,73 @@ +# -*- tcl -*- +# This code is hereby put into the public domain. +# ### ### ### ######### ######### ######### +## Overview +# Base32 encoding and decoding of small strings. + +# ### ### ### ######### ######### ######### +## Notes + +# A binary string is split into groups of 5 bits (2^5 == 32), and each +# group is converted into a printable character as is specified in RFC +# 3548. + +# ### ### ### ######### ######### ######### +## Requisites + +package require base32::core +namespace eval ::base32 {} + +# ### ### ### ######### ######### ######### +## API & Implementation + +proc ::base32::tcl_encode {bitstring} { + variable forward + + binary scan $bitstring B* bits + set len [string length $bits] + set rem [expr {$len % 5}] + if {$rem} {append bits =/$rem} + #puts "($bitstring) => <$bits>" + + return [string map $forward $bits] +} + +proc ::base32::tcl_decode {estring} { + variable backward + variable invalid + + if {![core::valid $estring $invalid msg]} { + return -code error $msg + } + #puts "I<$estring>" + #puts "M<[string map $backward $estring]>" + + return [binary format B* [string map $backward [string toupper $estring]]] +} + +# ### ### ### ######### ######### ######### +## Data structures + +namespace eval ::base32 { + # Initialize the maps + variable forward + variable backward + variable invalid + + core::define { + 0 A 9 J 18 S 27 3 + 1 B 10 K 19 T 28 4 + 2 C 11 L 20 U 29 5 + 3 D 12 M 21 V 30 6 + 4 E 13 N 22 W 31 7 + 5 F 14 O 23 X + 6 G 15 P 24 Y + 7 H 16 Q 25 Z + 8 I 17 R 26 2 + } forward backward invalid ; # {} + # puts ///$forward/// + # puts ///$backward/// +} + +# ### ### ### ######### ######### ######### +## Ok diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/base32core.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/base32core.tcl new file mode 100644 index 00000000..dd18e5a2 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/base32core.tcl @@ -0,0 +1,134 @@ +# -*- tcl -*- +# This code is hereby put into the public domain. +# ### ### ### ######### ######### ######### +#= Overview + +# Fundamental handling of base32 conversion tables. Expansion of a +# basic mapping into a full mapping and its inverse mapping. + +# ### ### ### ######### ######### ######### +#= Requisites + +namespace eval ::base32::core {} + +# ### ### ### ######### ######### ######### +#= API & Implementation + +proc ::base32::core::define {map fv bv iv} { + variable bits + upvar 1 $fv forward $bv backward $iv invalid + + # bytes - bits - padding - tail | bits - padding - tail + # 0 - 0 - "" - "xxxxxxxx" | 0 - "" - "" + # 1 - 8 - "======" - "xx======" | 3 - "======" - "x======" + # 2 - 16 - "====" - "xxxx====" | 1 - "====" - "x====" + # 3 - 24 - "===" - "xxxxx===" | 4 - "===" - "x===" + # 4 - 32 - "=" - "xxxxxxx=" | 2 - "=" - "x=" + + array set _ $bits + + set invalid "\[^=" + set forward {} + set btmp {} + + foreach {code char} $map { + set b $_($code) + + append invalid [string tolower $char][string toupper $char] + + # 5 bit remainder + lappend forward $b $char + lappend btmp [list $char $b] + + # 4 bit remainder + if {$code%2} continue + set b [string range $b 0 end-1] + lappend forward ${b}=/4 ${char}=== + lappend btmp [list ${char}=== $b] + + # 3 bit remainder + if {$code%4} continue + set b [string range $b 0 end-1] + lappend forward ${b}=/3 ${char}====== + lappend btmp [list ${char}====== $b] + + # 2 bit remainder + if {$code%8} continue + set b [string range $b 0 end-1] + lappend forward ${b}=/2 ${char}= + lappend btmp [list ${char}= $b] + + # 1 bit remainder + if {$code%16} continue + set b [string range $b 0 end-1] + lappend forward ${b}=/1 ${char}==== + lappend btmp [list ${char}==== $b] + } + + set backward {} + foreach item [lsort -index 0 -decreasing $btmp] { + foreach {c b} $item break + lappend backward $c $b + } + + append invalid "\]" + return +} + +proc ::base32::core::valid {estring pattern mv} { + upvar 1 $mv message + + if {[string length $estring] % 8} { + set message "Length is not a multiple of 8" + return 0 + } elseif {[regexp -indices $pattern $estring where]} { + foreach {s e} $where break + set message "Invalid character at index $s: \"[string index $estring $s]\"" + return 0 + } elseif {[regexp {(=+)$} $estring -> pad]} { + set padlen [string length $pad] + if { + ($padlen != 6) && + ($padlen != 4) && + ($padlen != 3) && + ($padlen != 1) + } { + set message "Invalid padding of length $padlen" + return 0 + } + } + + # Remove the brackets and ^= from the pattern, to construct the + # class of valid characters which must not follow the padding. + + set badp "=\[[string range $pattern 3 end-1]\]" + if {[regexp -indices $badp $estring where]} { + foreach {s e} $where break + set message "Invalid character at index $s: \"[string index $estring $s]\" (padding found in the middle of the input)" + return 0 + } + return 1 +} + +# ### ### ### ######### ######### ######### +## Data structures + +namespace eval ::base32::core { + namespace export define valid + + variable bits { + 0 00000 1 00001 2 00010 3 00011 + 4 00100 5 00101 6 00110 7 00111 + 8 01000 9 01001 10 01010 11 01011 + 12 01100 13 01101 14 01110 15 01111 + 16 10000 17 10001 18 10010 19 10011 + 20 10100 21 10101 22 10110 23 10111 + 24 11000 25 11001 26 11010 27 11011 + 28 11100 29 11101 30 11110 31 11111 + } +} + +# ### ### ### ######### ######### ######### +#= Registration + +package provide base32::core 0.2 diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/base32hex.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/base32hex.tcl new file mode 100644 index 00000000..c270f6a0 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/base32hex.tcl @@ -0,0 +1,182 @@ +# -*- tcl -*- +# This code is hereby put into the public domain. +# ### ### ### ######### ######### ######### +## Overview +# Base32 encoding and decoding of small strings. +# +# Management code for switching between Tcl and C accelerated +# implementations. +# +# RCS: @(#) $Id: base32hex.tcl,v 1.3 2008/03/22 23:46:42 andreas_kupries Exp $ + +# @mdgen EXCLUDE: base32hex_c.tcl + +package require Tcl 8.5 9 + +namespace eval ::base32::hex {} + +# ### ### ### ######### ######### ######### +## Management of base32 std implementations. + +# ::base32::hex::LoadAccelerator -- +# +# Loads a named implementation, if possible. +# +# Arguments: +# key Name of the implementation to load. +# +# Results: +# A boolean flag. True if the implementation +# was successfully loaded; and False otherwise. + +proc ::base32::hex::LoadAccelerator {key} { + variable accel + set isok 0 + switch -exact -- $key { + critcl { + # Critcl implementation of base32 requires Tcl 8.4. + if {![package vsatisfies [package provide Tcl] 8.4]} {return 0} + if {[catch {package require tcllibc}]} {return 0} + set isok [llength [info commands ::base32::hex::critcl_encode]] + } + tcl { + variable selfdir + if {[catch {source [file join $selfdir base32hex_tcl.tcl]}]} {return 0} + set isok [llength [info commands ::base32::hex::tcl_encode]] + } + default { + return -code error "invalid accelerator $key:\ + must be one of [join [KnownImplementations] {, }]" + } + } + set accel($key) $isok + return $isok +} + +# ::base32::hex::SwitchTo -- +# +# Activates a loaded named implementation. +# +# Arguments: +# key Name of the implementation to activate. +# +# Results: +# None. + +proc ::base32::hex::SwitchTo {key} { + variable accel + variable loaded + + if {[string equal $key $loaded]} { + # No change, nothing to do. + return + } elseif {![string equal $key ""]} { + # Validate the target implementation of the switch. + + if {![info exists accel($key)]} { + return -code error "Unable to activate unknown implementation \"$key\"" + } elseif {![info exists accel($key)] || !$accel($key)} { + return -code error "Unable to activate missing implementation \"$key\"" + } + } + + # Deactivate the previous implementation, if there was any. + + if {![string equal $loaded ""]} { + foreach c {encode decode} { + rename ::base32::hex::$c ::base32::hex::${loaded}_$c + } + } + + # Activate the new implementation, if there is any. + + if {![string equal $key ""]} { + foreach c {encode decode} { + rename ::base32::hex::${key}_$c ::base32::hex::$c + } + } + + # Remember the active implementation, for deactivation by future + # switches. + + set loaded $key + return +} + +# ::base32::hex::Implementations -- +# +# Determines which implementations are +# present, i.e. loaded. +# +# Arguments: +# None. +# +# Results: +# A list of implementation keys. + +proc ::base32::hex::Implementations {} { + variable accel + set res {} + foreach n [array names accel] { + if {!$accel($n)} continue + lappend res $n + } + return $res +} + +# ::base32::hex::KnownImplementations -- +# +# Determines which implementations are known +# as possible implementations. +# +# Arguments: +# None. +# +# Results: +# A list of implementation keys. In the order +# of preference, most prefered first. + +proc ::base32::hex::KnownImplementations {} { + return {critcl tcl} +} + +proc ::base32::hex::Names {} { + return { + critcl {tcllibc based} + tcl {pure Tcl} + } +} + +# ### ### ### ######### ######### ######### +## Initialization: Data structures. + +namespace eval ::base32::hex { + variable selfdir [file dirname [info script]] + variable loaded {} + + variable accel + array set accel {tcl 0 critcl 0} +} + +# ### ### ### ######### ######### ######### +## Initialization: Choose an implementation, +## most prefered first. Loads only one of the +## possible implementations. And activates it. + +namespace eval ::base32::hex { + variable e + foreach e [KnownImplementations] { + if {[LoadAccelerator $e]} { + SwitchTo $e + break + } + } + unset e + + namespace export encode decode +} + +# ### ### ### ######### ######### ######### +## Ready + +package provide base32::hex 0.2 diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/base32hex_c.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/base32hex_c.tcl new file mode 100644 index 00000000..20e50021 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/base32hex_c.tcl @@ -0,0 +1,254 @@ +# base32hexc.tcl -- +# +# Implementation of a base32 (extended hex) de/encoder for Tcl. +# +# Public domain +# +# RCS: @(#) $Id: base32hex_c.tcl,v 1.3 2008/01/28 22:58:18 andreas_kupries Exp $ + +package require critcl +package require Tcl 8.5 9 + +namespace eval ::base32::hex { + # Supporting code for the main command. + catch { + #critcl::cheaders -g + #critcl::debug memory symbols + } + + # Main commands, encoder & decoder + + critcl::ccommand critcl_encode {dummy interp objc objv} { + /* Syntax -*- c -*- + * critcl_encode string + */ + + unsigned char* buf; + Tcl_Size nbuf; + + unsigned char* out; + unsigned char* at; + int nout; + + /* + * The array used for encoding + */ /* 123456789 123456789 123456789 12 */ + static const char map[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + +#define USAGEE "bitstring" + + if (objc != 2) { + Tcl_WrongNumArgs (interp, 1, objv, USAGEE); /* OK tcl9 */ + return TCL_ERROR; + } + + buf = Tcl_GetBytesFromObj (interp, objv[1], &nbuf); /* OK tcl9 */ + if (buf == NULL) return TCL_ERROR; + nout = ((nbuf+4)/5)*8; + out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); + + for (at = out; nbuf >= 5; nbuf -= 5, buf += 5) { + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; + *(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; + *(at++) = map [ 0x1f & (buf[3]>>2) ]; + *(at++) = map [ 0x1f & ((buf[3]<<3) | (buf[4]>>5)) ]; + *(at++) = map [ 0x1f & (buf[4]) ]; + } + if (nbuf > 0) { + /* Process partials at end. */ + switch (nbuf) { + case 1: + /* |01234567| 2, padding 6 + * xxxxx + * xxx 00 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & (buf[0]<<2) ]; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + break; + case 2: /* x3/=4 */ + /* |01234567|01234567| 4, padding 4 + * xxxxx + * xxx xx + * xxxxx + * x 0000 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & (buf[1]<<4) ]; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + break; + case 3: + /* |01234567|01234567|01234567| 5, padding 3 + * xxxxx + * xxx xx + * xxxxx + * x xxxx + * xxxx 0 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; + *(at++) = map [ 0x1f & (buf[2]<<1) ]; + *(at++) = '='; + *(at++) = '='; + *(at++) = '='; + break; + case 4: + /* |01234567|01234567|01234567|012334567| 7, padding 1 + * xxxxx + * xxx xx + * xxxxx + * x xxxx + * xxxx + * xxxxx + * xxxx 0 + */ + + *(at++) = map [ (buf[0]>>3) ]; + *(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; + *(at++) = map [ 0x1f & (buf[1]>>1) ]; + *(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; + *(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; + *(at++) = map [ 0x1f & (buf[3]>>2) ]; + *(at++) = map [ 0x1f & (buf[3]<<3) ]; + *(at++) = '='; + break; + } + } + + Tcl_SetObjResult (interp, Tcl_NewStringObj ((char*)out, nout)); /* OK tcl9 */ + Tcl_Free ((char*) out); + return TCL_OK; + } + + + critcl::ccommand critcl_decode {dummy interp objc objv} { + /* Syntax -*- c -*- + * critcl_decode estring + */ + + unsigned char* buf; + Tcl_Size nbuf; + + unsigned char* out; + unsigned char* at; + unsigned char x [8]; + int nout; + + int i, j, a, pad, nx; + + /* + * An array for translating single base-32 characters into a value. + * Disallowed input characters have a value of 64. Upper and lower + * case is the same. Only 128 chars, as everything above char(127) + * is 64. + */ + static const char map [] = { + /* \00 */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + /* DLE */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + /* SPC */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + /* '0' */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 64, 64, 64, 64, 64, 64, + /* '@' */ 64, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + /* 'P' */ 25, 26, 27, 28, 29, 30, 31, 64, 64, 64, 64, 64, 64, 64, 64, 64, + /* '`' */ 64, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + /* 'p' */ 25, 26, 27, 28, 29, 30, 31, 64, 64, 64, 64, 64, 64, 64, 64, 64 + }; + +#define USAGED "estring" + + if (objc != 2) { + Tcl_WrongNumArgs (interp, 1, objv, USAGED); /* OK tcl9 */ + return TCL_ERROR; + } + + buf = (unsigned char*) Tcl_GetStringFromObj (objv[1], &nbuf); /* OK tcl9 */ + + if (nbuf % 8) { + Tcl_SetObjResult (interp, Tcl_NewStringObj ("Length is not a multiple of 8", -1)); /* OK tcl9 */ + return TCL_ERROR; + } + + nout = (nbuf/8)*5 *TCL_UTF_MAX; + out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); + +#define HIGH(x) (((x) & 0x80) != 0) +#define BADC(x) ((x) == 64) +#define BADCHAR(a,j) (HIGH ((a)) || BADC (x [(j)] = map [(a)])) + + for (pad = 0, i=0, at = out; i < nbuf; i += 8, buf += 8){ + for (j=0; j < 8; j++){ + a = buf [j]; + + if (a == '=') { + x[j] = 0; + pad++; + continue; + } else if (pad) { + char msg [120]; + sprintf (msg, + "Invalid character at index %d: \"=\" (padding found in the middle of the input)", + j-1); + Tcl_Free ((char*) out); + Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ + return TCL_ERROR; + } + + if (BADCHAR (a,j)) { + char msg [100]; + sprintf (msg,"Invalid character at index %d: \"%c\"",j,a); + Tcl_Free ((char*) out); + Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ + return TCL_ERROR; + } + } + + *(at++) = (x[0]<<3) | (x[1]>>2) ; + *(at++) = (x[1]<<6) | (x[2]<<1) | (x[3]>>4); + *(at++) = (x[3]<<4) | (x[4]>>1) ; + *(at++) = (x[4]<<7) | (x[5]<<2) | (x[6]>>3); + *(at++) = (x[6]<<5) | x[7] ; + } + + if (pad) { + if (pad == 1) { + at -= 1; + } else if (pad == 3) { + at -= 2; + } else if (pad == 4) { + at -= 3; + } else if (pad == 6) { + at -= 4; + } else { + char msg [100]; + sprintf (msg,"Invalid padding of length %d",pad); + Tcl_Free ((char*) out); + Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ + return TCL_ERROR; + } + } + + Tcl_SetObjResult (interp, Tcl_NewByteArrayObj (out, at-out)); /* OK tcl9 */ + Tcl_Free ((char*) out); + return TCL_OK; + } +} + +# ### ### ### ######### ######### ######### +## Ready diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/base32hex_tcl.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/base32hex_tcl.tcl new file mode 100644 index 00000000..f406bc6d --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/base32hex_tcl.tcl @@ -0,0 +1,79 @@ +# -*- tcl -*- +# This code is hereby put into the public domain. +# ### ### ### ######### ######### ######### +## Overview +# Base32 encoding and decoding of small strings. + +# ### ### ### ######### ######### ######### +## Notes + +# A binary string is split into groups of 5 bits (2^5 == 32), and each +# group is converted into a printable character as is specified in RFC +# 3548 for the extended hex encoding. + +# ### ### ### ######### ######### ######### +## Requisites + +package require base32::core +namespace eval ::base32::hex {} + +# ### ### ### ######### ######### ######### +## API & Implementation + +proc ::base32::hex::tcl_encode {bitstring} { + variable forward + + binary scan $bitstring B* bits + set len [string length $bits] + set rem [expr {$len % 5}] + if {$rem} {append bits =/$rem} + #puts "($bitstring) => <$bits>" + + return [string map $forward $bits] +} + +proc ::base32::hex::tcl_decode {estring} { + variable backward + variable invalid + + if {![core::valid $estring $invalid msg]} { + return -code error $msg + } + #puts "I<$estring>" + #puts "M<[string map $backward $estring]>" + + return [binary format B* [string map $backward [string toupper $estring]]] +} + +# ### ### ### ######### ######### ######### +## Data structures + +namespace eval ::base32::hex { + namespace eval core { + namespace import ::base32::core::define + namespace import ::base32::core::valid + } + + namespace export encode decode + # Initialize the maps + variable forward + variable backward + variable invalid + + core::define { + 0 0 9 9 18 I 27 R + 1 1 10 A 19 J 28 S + 2 2 11 B 20 K 29 T + 3 3 12 C 21 L 30 U + 4 4 13 D 22 M 31 V + 5 5 14 E 23 N + 6 6 15 F 24 O + 7 7 16 G 25 P + 8 8 17 H 26 Q + } forward backward invalid ; # {} + # puts ///$forward/// + # puts ///$backward/// +} + +# ### ### ### ######### ######### ######### +## Ok diff --git a/src/vendorlib_tcl9/tcllib2.0/base32/pkgIndex.tcl b/src/vendorlib_tcl9/tcllib2.0/base32/pkgIndex.tcl new file mode 100644 index 00000000..ea0c6d97 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base32/pkgIndex.tcl @@ -0,0 +1,4 @@ +if {![package vsatisfies [package provide Tcl] 8.5 9]} return +package ifneeded base32 0.2 [list source [file join $dir base32.tcl]] +package ifneeded base32::hex 0.2 [list source [file join $dir base32hex.tcl]] +package ifneeded base32::core 0.2 [list source [file join $dir base32core.tcl]] diff --git a/src/vendorlib_tcl9/tcllib2.0/base64/ascii85.tcl b/src/vendorlib_tcl9/tcllib2.0/base64/ascii85.tcl new file mode 100644 index 00000000..4fa17c06 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base64/ascii85.tcl @@ -0,0 +1,270 @@ +# ascii85.tcl -- +# +# Encode/Decode ascii85 for a string +# +# Copyright (c) Emiliano Gavilan +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +package require Tcl 8.5 9 + +namespace eval ascii85 { + namespace export encode encodefile decode + # default values for encode options + variable options + array set options [list -wrapchar \n -maxlen 76] +} + +# ::ascii85::encode -- +# +# Ascii85 encode a given string. +# +# Arguments: +# args ?-maxlen maxlen? ?-wrapchar wrapchar? string +# +# If maxlen is 0, the output is not wrapped. +# +# Results: +# A Ascii85 encoded version of $string, wrapped at $maxlen characters +# by $wrapchar. + +proc ascii85::encode {args} { + variable options + + set alen [llength $args] + if {$alen != 1 && $alen != 3 && $alen != 5} { + return -code error "wrong # args:\ + should be \"[lindex [info level 0] 0]\ + ?-maxlen maxlen?\ + ?-wrapchar wrapchar? string\"" + } + + set data [lindex $args end] + array set opts [array get options] + array set opts [lrange $args 0 end-1] + foreach key [array names opts] { + if {[lsearch -exact [array names options] $key] == -1} { + return -code error "unknown option \"$key\":\ + must be -maxlen or -wrapchar" + } + } + ##nagelfar ignore + if {![string is integer -strict $opts(-maxlen)] + || $opts(-maxlen) < 0} { + return -code error "expected positive integer but got\ + \"$opts(-maxlen)\"" + } + + # perform this check early + if {[string length $data] == 0} { + return "" + } + + # shorten the names, and normalize numeric values. + set ml [format %d $opts(-maxlen)] + set wc $opts(-wrapchar) + + # if maxlen is zero, don't wrap the output + if {$ml == 0} { + set wc "" + } + + set encoded {} + + binary scan $data c* X + set len [llength $X] + set rest [expr {$len % 4}] + set lastidx [expr {$len - $rest - 1}] + + foreach {b1 b2 b3 b4} [lrange $X 0 $lastidx] { + # calculate the 32 bit value + # this is an inlined version of the [encode4bytes] proc + # included here for performance reasons + set val [expr { + ( (($b1 & 0xff) << 24) + |(($b2 & 0xff) << 16) + |(($b3 & 0xff) << 8) + | ($b4 & 0xff) + ) & 0xffffffff }] + + if {$val == 0} { + # four \0 bytes encodes as "z" instead of "!!!!!" + append current "z" + } else { + # no magic numbers here. + # 52200625 -> 85 ** 4 + # 614125 -> 85 ** 3 + # 7225 -> 85 ** 2 + append current [binary format ccccc \ + [expr { ( $val / 52200625) + 33 }] \ + [expr { (($val % 52200625) / 614125) + 33 }] \ + [expr { (($val % 614125) / 7225) + 33 }] \ + [expr { (($val % 7225) / 85) + 33 }] \ + [expr { ( $val % 85) + 33 }]] + } + + if {[string length $current] >= $ml} { + append encoded [string range $current 0 [expr {$ml - 1}]] $wc + set current [string range $current $ml end] + } + } + + if { $rest } { + # there are remaining bytes. + # pad with \0 and encode not using the "z" convention. + # finally, add ($rest + 1) chars. + set val 0 + foreach {b1 b2 b3 b4} [pad [lrange $X [incr lastidx] end] 4 0] break + append current [string range [encode4bytes $b1 $b2 $b3 $b4] 0 $rest] + } + append encoded [regsub -all -- ".{$ml}" $current "&$wc"] + + return $encoded +} + +proc ascii85::encode4bytes {b1 b2 b3 b4} { + set val [expr { + ( (($b1 & 0xff) << 24) + |(($b2 & 0xff) << 16) + |(($b3 & 0xff) << 8) + | ($b4 & 0xff) + ) & 0xffffffff }] + return [binary format ccccc \ + [expr { ( $val / 52200625) + 33 }] \ + [expr { (($val % 52200625) / 614125) + 33 }] \ + [expr { (($val % 614125) / 7225) + 33 }] \ + [expr { (($val % 7225) / 85) + 33 }] \ + [expr { ( $val % 85) + 33 }]] +} + +# ::ascii85::encodefile -- +# +# Ascii85 encode the contents of a file using default values +# for maxlen and wrapchar parameters. +# +# Arguments: +# fname The name of the file to encode. +# +# Results: +# An Ascii85 encoded version of the contents of the file. +# This is a convenience command + +proc ascii85::encodefile {fname} { + set fd [open $fname rb] + return [encode [read $fd]][close $fd] +} + +# ::ascii85::decode -- +# +# Ascii85 decode a given string. +# +# Arguments: +# string The string to decode. +# Leading spaces and tabs are removed, along with trailing newlines +# +# Results: +# The decoded value. + +proc ascii85::decode {data} { + # get rid of leading spaces/tabs and trailing newlines + set data [string map [list \n {} \t {} { } {}] $data] + set len [string length $data] + + # perform this ckeck early + if {! $len} { + return "" + } + + set decoded {} + set count 0 + set group [list] + binary scan $data c* X + + foreach char $X { + # we must check that every char is in the allowed range + if {$char < 33 || $char > 117 } { + # "z" is an exception + if {$char == 122} { + if {$count == 0} { + # if a "z" char appears at the beggining of a group, + # it decodes as four null bytes + append decoded \x00\x00\x00\x00 + continue + } else { + # if not, is an error + return -code error \ + "error decoding data: \"z\" char misplaced" + } + } + # char is not in range and not a "z" at the beggining of a group + return -code error \ + "error decoding data: chars outside the allowed range" + } + + lappend group $char + incr count + if {$count == 5} { + # this is an inlined version of the [decode5chars] proc + # included here for performance reasons + set val [expr { + ([lindex $group 0] - 33) * wide(52200625) + + ([lindex $group 1] - 33) * 614125 + + ([lindex $group 2] - 33) * 7225 + + ([lindex $group 3] - 33) * 85 + + ([lindex $group 4] - 33) }] + if {$val > 0xffffffff} { + return -code error "error decoding data: decoded group overflow" + } else { + append decoded [binary format I $val] + incr count -5 + set group [list] + } + } + } + + set len [llength $group] + switch -- $len { + 0 { + # all input has been consumed + # do nothing + } + 1 { + # a single char is a condition error, there should be at least 2 + return -code error \ + "error decoding data: trailing char" + } + default { + # pad with "u"s, decode and add ($len - 1) bytes + append decoded [string range \ + [decode5chars [pad $group 5 122]] \ + 0 \ + [expr {$len - 2}]] + } + } + + return $decoded +} + +proc ascii85::decode5chars {group} { + set val [expr { + ([lindex $group 0] - 33) * wide(52200625) + + ([lindex $group 1] - 33) * 614125 + + ([lindex $group 2] - 33) * 7225 + + ([lindex $group 3] - 33) * 85 + + ([lindex $group 4] - 33) }] + if {$val > 0xffffffff} { + return -code error "error decoding data: decoded group overflow" + } + + return [binary format I $val] +} + +proc ascii85::pad {chars len padchar} { + while {[llength $chars] < $len} { + lappend chars $padchar + } + + return $chars +} + +package provide ascii85 1.1.1 diff --git a/src/vendorlib_tcl9/tcllib2.0/base64/base64.tcl b/src/vendorlib_tcl9/tcllib2.0/base64/base64.tcl new file mode 100644 index 00000000..14a6fbb5 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base64/base64.tcl @@ -0,0 +1,411 @@ +# base64.tcl -- +# +# Encode/Decode base64 for a string +# Stephen Uhler / Brent Welch (c) 1997 Sun Microsystems +# The decoder was done for exmh by Chris Garrigues +# +# Copyright (c) 1998-2000 by Ajuba Solutions. +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +# Version 1.0 implemented Base64_Encode, Base64_Decode +# Version 2.0 uses the base64 namespace +# Version 2.1 fixes various decode bugs and adds options to encode +# Version 2.2 is much faster, Tcl8.0 compatible +# Version 2.2.1 bugfixes +# Version 2.2.2 bugfixes +# Version 2.3 bugfixes and extended to support Trf +# Version 2.4.x bugfixes + +# @mdgen EXCLUDE: base64c.tcl + +package require Tcl 8.5 9 +namespace eval ::base64 { + namespace export encode decode +} + +package provide base64 2.6.1 + +if {[package vsatisfies [package require Tcl] 8.6 9]} { + proc ::base64::encode {args} { + binary encode base64 -maxlen 76 {*}$args + } + + proc ::base64::decode {string} { + # Tcllib is strict with respect to end of input, yet lax for + # invalid characters outside of that. + regsub -all -- {[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/]} $string {} string + binary decode base64 -strict $string + } + + return +} + +if {![catch {package require Trf 2.0}]} { + # Trf is available, so implement the functionality provided here + # in terms of calls to Trf for speed. + + # ::base64::encode -- + # + # Base64 encode a given string. + # + # Arguments: + # args ?-maxlen maxlen? ?-wrapchar wrapchar? string + # + # If maxlen is 0, the output is not wrapped. + # + # Results: + # A Base64 encoded version of $string, wrapped at $maxlen characters + # by $wrapchar. + + proc ::base64::encode {args} { + # Set the default wrapchar and maximum line length to match + # the settings for MIME encoding (RFC 3548, RFC 2045). These + # are the settings used by Trf as well. Various RFCs allow for + # different wrapping characters and wraplengths, so these may + # be overridden by command line options. + set wrapchar "\n" + set maxlen 76 + + if { [llength $args] == 0 } { + error "wrong # args: should be \"[lindex [info level 0] 0]\ + ?-maxlen maxlen? ?-wrapchar wrapchar? string\"" + } + + set optionStrings [list "-maxlen" "-wrapchar"] + for {set i 0} {$i < [llength $args] - 1} {incr i} { + set arg [lindex $args $i] + set index [lsearch -glob $optionStrings "${arg}*"] + if { $index == -1 } { + error "unknown option \"$arg\": must be -maxlen or -wrapchar" + } + incr i + if { $i >= [llength $args] - 1 } { + error "value for \"$arg\" missing" + } + set val [lindex $args $i] + + # The name of the variable to assign the value to is extracted + # from the list of known options, all of which have an + # associated variable of the same name as the option without + # a leading "-". The [string range] command is used to strip + # of the leading "-" from the name of the option. + # + # FRINK: nocheck + set [string range [lindex $optionStrings $index] 1 end] $val + } + + # [string is] requires Tcl8.2; this works with 8.0 too + if {[catch {expr {$maxlen % 2}}]} { + return -code error "expected integer but got \"$maxlen\"" + } elseif {$maxlen < 0} { + return -code error "expected positive integer but got \"$maxlen\"" + } + + set string [lindex $args end] + set result [::base64 -mode encode -- $string] + + # Trf's encoder implicitly uses the settings -maxlen 76, + # -wrapchar \n for its output. We may have to reflow this for + # the settings chosen by the user. A second difference is that + # Trf closes the output with the wrap char sequence, + # always. The code here doesn't. Therefore 'trimright' is + # needed in the fast cases. + + if {($maxlen == 76) && [string equal $wrapchar \n]} { + # Both maxlen and wrapchar are identical to Trf's + # settings. This is the super-fast case, because nearly + # nothing has to be done. Only thing to do is strip a + # terminating wrapchar. + set result [string trimright $result] + } elseif {$maxlen == 76} { + # wrapchar has to be different here, length is the + # same. We can use 'string map' to transform the wrap + # information. + set result [string map [list \n $wrapchar] \ + [string trimright $result]] + } elseif {$maxlen == 0} { + # Have to reflow the output to no wrapping. Another fast + # case using only 'string map'. 'trimright' is not needed + # here. + + set result [string map [list \n ""] $result] + } else { + # Have to reflow the output from 76 to the chosen maxlen, + # and possibly change the wrap sequence as well. + + # Note: After getting rid of the old wrap sequence we + # extract the relevant segments from the string without + # modifying the string. Modification, i.e. removal of the + # processed part, means 'shifting down characters in + # memory', making the algorithm O(n^2). By avoiding the + # modification we stay in O(n). + + set result [string map [list \n ""] $result] + set l [expr {[string length $result]-$maxlen}] + for {set off 0} {$off < $l} {incr off $maxlen} { + append res [string range $result $off [expr {$off+$maxlen-1}]] $wrapchar + } + append res [string range $result $off end] + set result $res + } + + return $result + } + + # ::base64::decode -- + # + # Base64 decode a given string. + # + # Arguments: + # string The string to decode. Characters not in the base64 + # alphabet are ignored (e.g., newlines) + # + # Results: + # The decoded value. + + proc ::base64::decode {string} { + regsub -all {\s} $string {} string + ::base64 -mode decode -- $string + } + +} else { + # Without Trf use a pure tcl implementation + + namespace eval base64 { + variable base64 {} + variable base64_en {} + + # We create the auxiliary array base64_tmp, it will be unset later. + variable base64_tmp + variable i + + variable i 0 + variable char + foreach char {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ + a b c d e f g h i j k l m n o p q r s t u v w x y z \ + 0 1 2 3 4 5 6 7 8 9 + /} { + set base64_tmp($char) $i + lappend base64_en $char + incr i + } + + # + # Create base64 as list: to code for instance C<->3, specify + # that [lindex $base64 67] be 3 (C is 67 in ascii); non-coded + # ascii chars get a {}. we later use the fact that lindex on a + # non-existing index returns {}, and that [expr {} < 0] is true + # + + # the last ascii char is 'z' + variable char + variable len + variable val + + scan z %c len + for {set i 0} {$i <= $len} {incr i} { + set char [format %c $i] + set val {} + if {[info exists base64_tmp($char)]} { + set val $base64_tmp($char) + } else { + set val {} + } + lappend base64 $val + } + + # code the character "=" as -1; used to signal end of message + scan = %c i + set base64 [lreplace $base64 $i $i -1] + + # remove unneeded variables + unset base64_tmp i char len val + + namespace export encode decode + } + + # ::base64::encode -- + # + # Base64 encode a given string. + # + # Arguments: + # args ?-maxlen maxlen? ?-wrapchar wrapchar? string + # + # If maxlen is 0, the output is not wrapped. + # + # Results: + # A Base64 encoded version of $string, wrapped at $maxlen characters + # by $wrapchar. + + proc ::base64::encode {args} { + set base64_en $::base64::base64_en + + # Set the default wrapchar and maximum line length to match + # the settings for MIME encoding (RFC 3548, RFC 2045). These + # are the settings used by Trf as well. Various RFCs allow for + # different wrapping characters and wraplengths, so these may + # be overridden by command line options. + set wrapchar "\n" + set maxlen 76 + + if { [llength $args] == 0 } { + error "wrong # args: should be \"[lindex [info level 0] 0]\ + ?-maxlen maxlen? ?-wrapchar wrapchar? string\"" + } + + set optionStrings [list "-maxlen" "-wrapchar"] + for {set i 0} {$i < [llength $args] - 1} {incr i} { + set arg [lindex $args $i] + set index [lsearch -glob $optionStrings "${arg}*"] + if { $index == -1 } { + error "unknown option \"$arg\": must be -maxlen or -wrapchar" + } + incr i + if { $i >= [llength $args] - 1 } { + error "value for \"$arg\" missing" + } + set val [lindex $args $i] + + # The name of the variable to assign the value to is extracted + # from the list of known options, all of which have an + # associated variable of the same name as the option without + # a leading "-". The [string range] command is used to strip + # of the leading "-" from the name of the option. + # + # FRINK: nocheck + set [string range [lindex $optionStrings $index] 1 end] $val + } + + # [string is] requires Tcl8.2; this works with 8.0 too + if {[catch {expr {$maxlen % 2}}]} { + return -code error "expected integer but got \"$maxlen\"" + } elseif {$maxlen < 0} { + return -code error "expected positive integer but got \"$maxlen\"" + } + + set string [lindex $args end] + + set result {} + set state 0 + set length 0 + + + # Process the input bytes 3-by-3 + + binary scan $string c* X + + foreach {x y z} $X { + ADD [lindex $base64_en [expr {($x >>2) & 0x3F}]] + if {$y != {}} { + ADD [lindex $base64_en [expr {(($x << 4) & 0x30) | (($y >> 4) & 0xF)}]] + if {$z != {}} { + ADD [lindex $base64_en [expr {(($y << 2) & 0x3C) | (($z >> 6) & 0x3)}]] + ADD [lindex $base64_en [expr {($z & 0x3F)}]] + } else { + set state 2 + break + } + } else { + set state 1 + break + } + } + if {$state == 1} { + ADD [lindex $base64_en [expr {(($x << 4) & 0x30)}]] + ADD = + ADD = + } elseif {$state == 2} { + ADD [lindex $base64_en [expr {(($y << 2) & 0x3C)}]] + ADD = + } + return $result + } + + proc ::base64::ADD {x} { + # The line length check is always done before appending so + # that we don't get an extra newline if the output is a + # multiple of $maxlen chars long. + + upvar 1 maxlen maxlen length length result result wrapchar wrapchar + if {$maxlen && $length >= $maxlen} { + append result $wrapchar + set length 0 + } + append result $x + incr length + return + } + + # ::base64::decode -- + # + # Base64 decode a given string. + # + # Arguments: + # string The string to decode. Characters not in the base64 + # alphabet are ignored (e.g., newlines) + # + # Results: + # The decoded value. + + proc ::base64::decode {string} { + if {[string length $string] == 0} {return ""} + + set base64 $::base64::base64 + set output "" ; # Fix for [Bug 821126] + set nums {} + + binary scan $string c* X + lappend X 61 ;# force a terminator + foreach x $X { + set bits [lindex $base64 $x] + if {$bits >= 0} { + if {[llength [lappend nums $bits]] == 4} { + foreach {v w z y} $nums break + set a [expr {($v << 2) | ($w >> 4)}] + set b [expr {(($w & 0xF) << 4) | ($z >> 2)}] + set c [expr {(($z & 0x3) << 6) | $y}] + append output [binary format ccc $a $b $c] + set nums {} + } + } elseif {$bits == -1} { + # = indicates end of data. Output whatever chars are + # left, if any. + if {![llength $nums]} break + # The encoding algorithm dictates that we can only + # have 1 or 2 padding characters. If x=={}, we must + # (*) have 12 bits of input (enough for 1 8-bit + # output). If x!={}, we have 18 bits of input (enough + # for 2 8-bit outputs). + # + # (*) If we don't then the input is broken (bug 2976290). + + foreach {v w z} $nums break + + # Bug 2976290 + if {$w == {}} { + return -code error "Not enough data to process padding" + } + + set a [expr {($v << 2) | (($w & 0x30) >> 4)}] + if {$z == {}} { + append output [binary format c $a ] + } else { + set b [expr {(($w & 0xF) << 4) | (($z & 0x3C) >> 2)}] + append output [binary format cc $a $b] + } + break + } else { + # RFC 2045 says that line breaks and other characters not part + # of the Base64 alphabet must be ignored, and that the decoder + # can optionally emit a warning or reject the message. We opt + # not to do so, but to just ignore the character. + continue + } + } + return $output + } +} + +# # ## ### ##### ######## ############# ##################### +return + diff --git a/src/vendorlib_tcl9/tcllib2.0/base64/base64c.tcl b/src/vendorlib_tcl9/tcllib2.0/base64/base64c.tcl new file mode 100644 index 00000000..49a88711 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base64/base64c.tcl @@ -0,0 +1,19 @@ +# base64c - Copyright (C) 2003 Pat Thoyts +# +# This package is a place-holder for the critcl enhanced code present in +# the tcllib base64 module. +# +# Normally this code will become part of the tcllibc library. +# + +# @sak notprovided base64c +package require critcl +package provide base64c 0.1.1 + +namespace eval ::base64c { + variable base64c_rcsid {$Id: base64c.tcl,v 1.5 2008/03/25 07:15:35 andreas_kupries Exp $} + + critcl::ccode { + /* no code required in this file */ + } +} diff --git a/src/vendorlib_tcl9/tcllib2.0/base64/pkgIndex.tcl b/src/vendorlib_tcl9/tcllib2.0/base64/pkgIndex.tcl new file mode 100644 index 00000000..83a05a04 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base64/pkgIndex.tcl @@ -0,0 +1,5 @@ +if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} +package ifneeded base64 2.6.1 [list source [file join $dir base64.tcl]] +package ifneeded uuencode 1.1.6 [list source [file join $dir uuencode.tcl]] +package ifneeded yencode 1.1.4 [list source [file join $dir yencode.tcl]] +package ifneeded ascii85 1.1.1 [list source [file join $dir ascii85.tcl]] diff --git a/src/vendorlib_tcl9/tcllib2.0/base64/uuencode.tcl b/src/vendorlib_tcl9/tcllib2.0/base64/uuencode.tcl new file mode 100644 index 00000000..2b2a9ee3 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base64/uuencode.tcl @@ -0,0 +1,337 @@ +# uuencode - Copyright (C) 2002 Pat Thoyts +# +# Provide a Tcl only implementation of uuencode and uudecode. +# +# ------------------------------------------------------------------------- +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# ------------------------------------------------------------------------- + +package require Tcl 8.5 9; # tcl minimum version + +# Try and get some compiled helper package. +if {[catch {package require tcllibc}]} { + catch {package require Trf} +} + +namespace eval ::uuencode { + namespace export encode decode uuencode uudecode +} + +proc ::uuencode::Enc {c} { + return [format %c [expr {($c != 0) ? (($c & 0x3f) + 0x20) : 0x60}]] +} + +proc ::uuencode::Encode {s} { + set r {} + binary scan $s c* d + foreach {c1 c2 c3} $d { + if {$c1 == {}} {set c1 0} + if {$c2 == {}} {set c2 0} + if {$c3 == {}} {set c3 0} + append r [Enc [expr {$c1 >> 2}]] + append r [Enc [expr {(($c1 << 4) & 0o060) | (($c2 >> 4) & 0o017)}]] + append r [Enc [expr {(($c2 << 2) & 0o074) | (($c3 >> 6) & 0o003)}]] + append r [Enc [expr {($c3 & 0o077)}]] + } + return $r +} + + +proc ::uuencode::Decode {s} { + if {[string length $s] == 0} {return ""} + set r {} + binary scan [pad $s] c* d + + foreach {c0 c1 c2 c3} $d { + append r [format %c [expr {((($c0-0x20)&0x3F) << 2) & 0xFF + | ((($c1-0x20)&0x3F) >> 4) & 0xFF}]] + append r [format %c [expr {((($c1-0x20)&0x3F) << 4) & 0xFF + | ((($c2-0x20)&0x3F) >> 2) & 0xFF}]] + append r [format %c [expr {((($c2-0x20)&0x3F) << 6) & 0xFF + | (($c3-0x20)&0x3F) & 0xFF}]] + } + return $r +} + +# ------------------------------------------------------------------------- +# C coded version of the Encode/Decode functions for base64c package. +# ------------------------------------------------------------------------- +if {[package provide critcl] != {}} { + namespace eval ::uuencode { + critcl::ccode { + #include + static unsigned char Enc(unsigned char c) { + return (c != 0) ? ((c & 0x3f) + 0x20) : 0x60; + } + } + critcl::ccommand CEncode {dummy interp objc objv} { + Tcl_Obj *inputPtr, *resultPtr; + Tcl_Size len, rlen, xtra; + unsigned char *input, *p, *r; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ + return TCL_ERROR; + } + + inputPtr = objv[1]; + input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ + if (input == NULL) return TCL_ERROR; + if ((xtra = (3 - (len % 3))) != 3) { + if (Tcl_IsShared(inputPtr)) + inputPtr = Tcl_DuplicateObj(inputPtr); + input = Tcl_SetByteArrayLength(inputPtr, len + xtra); /* OK tcl9 */ + memset(input + len, 0, xtra); + len += xtra; + } + + rlen = (len / 3) * 4; + resultPtr = Tcl_NewObj(); + r = Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ + memset(r, 0, rlen); + + for (p = input; p < input + len; p += 3) { + char a, b, c; + a = *p; b = *(p+1), c = *(p+2); + *r++ = Enc(a >> 2); + *r++ = Enc(((a << 4) & 060) | ((b >> 4) & 017)); + *r++ = Enc(((b << 2) & 074) | ((c >> 6) & 003)); + *r++ = Enc(c & 077); + } + Tcl_SetObjResult(interp, resultPtr); + return TCL_OK; + } + + critcl::ccommand CDecode {dummy interp objc objv} { + Tcl_Obj *inputPtr, *resultPtr; + Tcl_Size len, rlen, xtra; + unsigned char *input, *p, *r; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ + return TCL_ERROR; + } + + /* if input is not mod 4, extend it with nuls */ + inputPtr = objv[1]; + input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ + if (input == NULL) return TCL_ERROR; + if ((xtra = (4 - (len % 4))) != 4) { + if (Tcl_IsShared(inputPtr)) + inputPtr = Tcl_DuplicateObj(inputPtr); + input = Tcl_SetByteArrayLength(inputPtr, len + xtra); /* OK tcl9 */ + memset(input + len, 0, xtra); + len += xtra; + } + + /* output will be 1/3 smaller than input and a multiple of 3 */ + rlen = (len / 4) * 3; + resultPtr = Tcl_NewObj(); + r = Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ + memset(r, 0, rlen); + + for (p = input; p < input + len; p += 4) { + char a, b, c, d; + a = *p; b = *(p+1), c = *(p+2), d = *(p+3); + *r++ = (((a - 0x20) & 0x3f) << 2) | (((b - 0x20) & 0x3f) >> 4); + *r++ = (((b - 0x20) & 0x3f) << 4) | (((c - 0x20) & 0x3f) >> 2); + *r++ = (((c - 0x20) & 0x3f) << 6) | (((d - 0x20) & 0x3f) ); + } + Tcl_SetObjResult(interp, resultPtr); + return TCL_OK; + } + } +} + +# ------------------------------------------------------------------------- + +# Description: +# Permit more tolerant decoding of invalid input strings by padding to +# a multiple of 4 bytes with nulls. +# Result: +# Returns the input string - possibly padded with uuencoded null chars. +# +proc ::uuencode::pad {s} { + if {[set mod [expr {[string length $s] % 4}]] != 0} { + append s [string repeat "`" [expr {4 - $mod}]] + } + return $s +} + +# ------------------------------------------------------------------------- + +# If the Trf package is available then we shall use this by default but the +# Tcllib implementations are always visible if needed (ie: for testing) +if {[info commands ::uuencode::CDecode] != {}} { + # tcllib critcl package + interp alias {} ::uuencode::encode {} ::uuencode::CEncode + interp alias {} ::uuencode::decode {} ::uuencode::CDecode +} elseif {[package provide Trf] != {}} { + proc ::uuencode::encode {s} { + return [::uuencode -mode encode -- $s] + } + proc ::uuencode::decode {s} { + return [::uuencode -mode decode -- [pad $s]] + } +} else { + # pure-tcl then + interp alias {} ::uuencode::encode {} ::uuencode::Encode + interp alias {} ::uuencode::decode {} ::uuencode::Decode +} + +# ------------------------------------------------------------------------- + +proc ::uuencode::uuencode {args} { + array set opts {mode 0o0644 filename {} name {}} + set wrongargs "wrong \# args: should be\ + \"uuencode ?-name string? ?-mode octal?\ + (-file filename | ?--? string)\"" + while {[string match -* [lindex $args 0]]} { + switch -glob -- [lindex $args 0] { + -f* { + if {[llength $args] < 2} { + return -code error $wrongargs + } + set opts(filename) [lindex $args 1] + set args [lreplace $args 0 0] + } + -m* { + if {[llength $args] < 2} { + return -code error $wrongargs + } + set opts(mode) [lindex $args 1] + set args [lreplace $args 0 0] + } + -n* { + if {[llength $args] < 2} { + return -code error $wrongargs + } + set opts(name) [lindex $args 1] + set args [lreplace $args 0 0] + } + -- { + set args [lreplace $args 0 0] + break + } + default { + return -code error "bad option [lindex $args 0]:\ + must be -file, -mode, or -name" + } + } + set args [lreplace $args 0 0] + } + + if {$opts(name) == {}} { + set opts(name) $opts(filename) + } + if {$opts(name) == {}} { + set opts(name) "data.dat" + } + + if {$opts(filename) != {}} { + set f [open $opts(filename) r] + fconfigure $f -translation binary + set data [read $f] + close $f + } else { + if {[llength $args] != 1} { + return -code error $wrongargs + } + set data [lindex $args 0] + } + + set r {} + append r [format "begin %o %s" $opts(mode) $opts(name)] "\n" + for {set n 0} {$n < [string length $data]} {incr n 45} { + set s [string range $data $n [expr {$n + 44}]] + append r [Enc [string length $s]] + append r [encode $s] "\n" + } + append r "`\nend" + return $r +} + +# ------------------------------------------------------------------------- +# Description: +# Perform uudecoding of a file or data. A file may contain more than one +# encoded data section so the result is a list where each element is a +# three element list of the provided filename, the suggested mode and the +# data itself. +# +proc ::uuencode::uudecode {args} { + array set opts {mode 0o0644 filename {}} + set wrongargs "wrong \# args: should be \"uudecode (-file filename | ?--? string)\"" + while {[string match -* [lindex $args 0]]} { + switch -glob -- [lindex $args 0] { + -f* { + if {[llength $args] < 2} { + return -code error $wrongargs + } + set opts(filename) [lindex $args 1] + set args [lreplace $args 0 0] + } + -- { + set args [lreplace $args 0 0] + break + } + default { + return -code error "bad option [lindex $args 0]:\ + must be -file" + } + } + set args [lreplace $args 0 0] + } + + if {$opts(filename) != {}} { + set f [open $opts(filename) r] + set data [read $f] + close $f + } else { + if {[llength $args] != 1} { + return -code error $wrongargs + } + set data [lindex $args 0] + } + + set state false + set result {} + + foreach {line} [split $data "\n"] { + switch -exact -- $state { + false { + if {[regexp {^begin ([0-7]+) ([^\s]*)} $line \ + -> opts(mode) opts(name)]} { + set state true + set r {} + } + } + + true { + if {[string match "end" $line]} { + set state false + lappend result [list $opts(name) $opts(mode) $r] + } else { + scan $line %c c + set n [expr {($c - 0x21)}] + append r [string range \ + [decode [string range $line 1 end]] 0 $n] + } + } + } + } + + return $result +} + +# ------------------------------------------------------------------------- + +package provide uuencode 1.1.6 + +# ------------------------------------------------------------------------- +# +# Local variables: +# mode: tcl +# indent-tabs-mode: nil +# End: + diff --git a/src/vendorlib_tcl9/tcllib2.0/base64/yencode.tcl b/src/vendorlib_tcl9/tcllib2.0/base64/yencode.tcl new file mode 100644 index 00000000..017085db --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/base64/yencode.tcl @@ -0,0 +1,309 @@ +# yencode.tcl - Copyright (C) 2002 Pat Thoyts +# +# Provide a Tcl only implementation of yEnc encoding algorithm +# +# ------------------------------------------------------------------------- +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# ------------------------------------------------------------------------- + +# FUTURE: Rework to allow switching between the tcl/critcl implementations. + +package require Tcl 8.5 9; # tcl minimum version +catch {package require crc32}; # tcllib 1.1 +catch {package require tcllibc}; # critcl enhancements for tcllib + +namespace eval ::yencode { + namespace export encode decode yencode ydecode +} + +# ------------------------------------------------------------------------- + +proc ::yencode::Encode {s} { + set r {} + binary scan $s c* d + foreach {c} $d { + set v [expr {($c + 42) % 256}] + if {$v == 0x00 || $v == 0x09 || $v == 0x0A + || $v == 0x0D || $v == 0x3D} { + append r "=" + set v [expr {($v + 64) % 256}] + } + append r [format %c $v] + } + return $r +} + +proc ::yencode::Decode {s} { + if {[string length $s] == 0} {return ""} + set r {} + set esc 0 + binary scan $s c* d + foreach c $d { + if {$c == 61 && $esc == 0} { + set esc 1 + continue + } + set v [expr {($c - 42) % 256}] + if {$esc} { + set v [expr {($v - 64) % 256}] + set esc 0 + } + append r [format %c $v] + } + return $r +} + +# ------------------------------------------------------------------------- +# C coded versions for critcl built base64c package +# ------------------------------------------------------------------------- + +if {[package provide critcl] != {}} { + namespace eval ::yencode { + critcl::ccode { + #include + } + critcl::ccommand CEncode {dummy interp objc objv} { + Tcl_Obj *inputPtr, *resultPtr; + Tcl_Size len, rlen, xtra; + unsigned char *input, *p, *r, v; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ + return TCL_ERROR; + } + + /* fetch the input data */ + inputPtr = objv[1]; + input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ + if (input == NULL) return TCL_ERROR; + + /* calculate the length of the encoded result */ + rlen = len; + for (p = input; p < input + len; p++) { + v = (*p + 42) % 256; + if (v == 0 || v == 9 || v == 0x0A || v == 0x0D || v == 0x3D) + rlen++; + } + + /* allocate the output buffer */ + resultPtr = Tcl_NewObj(); + r = Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ + + /* encode the input */ + for (p = input; p < input + len; p++) { + v = (*p + 42) % 256; + if (v == 0 || v == 9 || v == 0x0A || v == 0x0D || v == 0x3D) { + *r++ = '='; + v = (v + 64) % 256; + } + *r++ = v; + } + Tcl_SetObjResult(interp, resultPtr); + return TCL_OK; + } + + critcl::ccommand CDecode {dummy interp objc objv} { + Tcl_Obj *inputPtr, *resultPtr; + Tcl_Size len, rlen, esc; + unsigned char *input, *p, *r, v; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ + return TCL_ERROR; + } + + /* fetch the input data */ + inputPtr = objv[1]; + input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ + if (input == NULL) return TCL_ERROR; + + /* allocate the output buffer */ + resultPtr = Tcl_NewObj(); + r = Tcl_SetByteArrayLength(resultPtr, len); /* OK tcl9 */ + + /* encode the input */ + for (p = input, esc = 0, rlen = 0; p < input + len; p++) { + if (*p == 61 && esc == 0) { + esc = 1; + continue; + } + v = (*p - 42) % 256; + if (esc) { + v = (v - 64) % 256; + esc = 0; + } + *r++ = v; + rlen++; + } + Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ + Tcl_SetObjResult(interp, resultPtr); + return TCL_OK; + } + } +} + +if {[info commands ::yencode::CEncode] != {}} { + interp alias {} ::yencode::encode {} ::yencode::CEncode + interp alias {} ::yencode::decode {} ::yencode::CDecode +} else { + interp alias {} ::yencode::encode {} ::yencode::Encode + interp alias {} ::yencode::decode {} ::yencode::Decode +} + +# ------------------------------------------------------------------------- +# Description: +# Pop the nth element off a list. Used in options processing. +# +proc ::yencode::Pop {varname {nth 0}} { + upvar $varname args + set r [lindex $args $nth] + set args [lreplace $args $nth $nth] + return $r +} + +# ------------------------------------------------------------------------- + +proc ::yencode::yencode {args} { + array set opts {mode 0644 filename {} name {} line 128 crc32 1} + while {[string match -* [lindex $args 0]]} { + switch -glob -- [lindex $args 0] { + -f* { set opts(filename) [Pop args 1] } + -m* { set opts(mode) [Pop args 1] } + -n* { set opts(name) [Pop args 1] } + -l* { set opts(line) [Pop args 1] } + -c* { set opts(crc32) [Pop args 1] } + -- { Pop args ; break } + default { + set options [join [lsort [array names opts]] ", -"] + return -code error "bad option [lindex $args 0]:\ + must be -$options" + } + } + Pop args + } + + if {$opts(name) == {}} { + set opts(name) $opts(filename) + } + if {$opts(name) == {}} { + set opts(name) "data.dat" + } + if {! [string is boolean $opts(crc32)]} { + return -code error "bad option -crc32: argument must be true or false" + } + + if {$opts(filename) != {}} { + set f [open $opts(filename) rb] + fconfigure $f -translation binary + set data [read $f] + close $f + } else { + if {[llength $args] != 1} { + return -code error "wrong \# args: should be\ + \"yencode ?options? -file name | data\"" + } + set data [lindex $args 0] + } + + set opts(size) [string length $data] + + set r {} + append r [format "=ybegin line=%d size=%d name=%s" \ + $opts(line) $opts(size) $opts(name)] "\n" + + set ndx 0 + while {$ndx < $opts(size)} { + set pln [string range $data $ndx [expr {$ndx + $opts(line) - 1}]] + set enc [encode $pln] + incr ndx [string length $pln] + append r $enc "\r\n" + } + + append r [format "=yend size=%d" $ndx] + if {$opts(crc32)} { + append r " crc32=" [crc::crc32 -format %x $data] + } + return $r +} + +# ------------------------------------------------------------------------- +# Description: +# Perform ydecoding of a file or data. A file may contain more than one +# encoded data section so the result is a list where each element is a +# three element list of the provided filename, the file size and the +# data itself. +# +proc ::yencode::ydecode {args} { + array set opts {mode 0644 filename {} name default.bin} + while {[string match -* [lindex $args 0]]} { + switch -glob -- [lindex $args 0] { + -f* { set opts(filename) [Pop args 1] } + -- { Pop args ; break; } + default { + set options [join [lsort [array names opts]] ", -"] + return -code error "bad option [lindex $args 0]:\ + must be -$opts" + } + } + Pop args + } + + if {$opts(filename) != {}} { + set f [open $opts(filename) r] + set data [read $f] + close $f + } else { + if {[llength $args] != 1} { + return -code error "wrong \# args: should be\ + \"ydecode ?options? -file name | data\"" + } + set data [lindex $args 0] + } + + set state false + set result {} + + foreach {line} [split $data "\n"] { + set line [string trimright $line "\r\n"] + switch -exact -- $state { + false { + if {[string match "=ybegin*" $line]} { + regexp {line=(\d+)} $line -> opts(line) + regexp {size=(\d+)} $line -> opts(size) + regexp {name=(\d+)} $line -> opts(name) + + if {$opts(name) == {}} { + set opts(name) default.bin + } + + set state true + set r {} + } + } + + true { + if {[string match "=yend*" $line]} { + set state false + lappend result [list $opts(name) $opts(size) $r] + } else { + append r [decode $line] + } + } + } + } + + return $result +} + +# ------------------------------------------------------------------------- + +package provide yencode 1.1.4 + +# ------------------------------------------------------------------------- +# +# Local variables: +# mode: tcl +# indent-tabs-mode: nil +# End: + diff --git a/src/vendorlib_tcl9/tcllib2.0/bee/bee.tcl b/src/vendorlib_tcl9/tcllib2.0/bee/bee.tcl new file mode 100644 index 00000000..1e741d9a --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/bee/bee.tcl @@ -0,0 +1,999 @@ +# bee.tcl -- +# +# BitTorrent Bee de- and encoder. +# +# Copyright (c) 2004 by Andreas Kupries +# See the file license.terms. + +package require Tcl 8.5 9 + +namespace eval ::bee { + # Encoder commands + namespace export \ + encodeString encodeNumber \ + encodeListArgs encodeList \ + encodeDictArgs encodeDict + + # Decoder commands. + namespace export \ + decode \ + decodeChannel \ + decodeCancel \ + decodePush + + # Channel decoders, reference to state information, keyed by + # channel handle. + + variable bee + array set bee {} + + # Counter for generation of names for the state variables. + + variable count 0 + + # State information for the channel decoders. + + # stateN, with N an integer number counting from 0 on up. + # ...(chan) Handle of channel the decoder is for. + # ...(cmd) Command prefix, completion callback + # ...(exact) Boolean flag, set for exact processing. + # ...(read) Buffer for new characters to process. + # ...(type) Type of current value (integer, string, list, dict) + # ...(value) Buffer for assembling the current value. + # ...(pend) Stack of pending 'value' buffers, for nested + # containers. + # ...(state) Current state of the decoding state machine. + + # States of the finite automaton ... + # intro - One char, type of value, or 'e' as stop of container. + # signum - sign or digit, for integer. + # idigit - digit, for integer, or 'e' as stop + # ldigit - digit, for length of string, or : + # data - string data, 'get' characters. + # Containers via 'pend'. + + #Debugging help, nesting level + #variable X 0 +} + + +# ::bee::encodeString -- +# +# Encode a string to bee-format. +# +# Arguments: +# string The string to encode. +# +# Results: +# The bee-encoded form of the string. + +proc ::bee::encodeString {string} { + return "[string length $string]:$string" +} + + +# ::bee::encodeNumber -- +# +# Encode an integer number to bee-format. +# +# Arguments: +# num The integer number to encode. +# +# Results: +# The bee-encoded form of the integer number. + +proc ::bee::encodeNumber {num} { + ##nagelfar ignore + if {![string is integer -strict $num]} { + return -code error "Expected integer number, got \"$num\"" + } + + # The reformatting deals with hex, octal and other tcl + # representation of the value. In other words we normalize the + # string representation of the input value. + + set num [format %d $num] + return "i${num}e" +} + + +# ::bee::encodeList -- +# +# Encode a list of bee-coded values to bee-format. +# +# Arguments: +# list The list to encode. +# +# Results: +# The bee-encoded form of the list. + +proc ::bee::encodeList {list} { + return "l[join $list ""]e" +} + + +# ::bee::encodeListArgs -- +# +# Encode a variable list of bee-coded values to bee-format. +# +# Arguments: +# args The values to encode. +# +# Results: +# The bee-encoded form of the list of values. + +proc ::bee::encodeListArgs {args} { + return [encodeList $args] +} + + +# ::bee::encodeDict -- +# +# Encode a dictionary of keys and bee-coded values to bee-format. +# +# Arguments: +# dict The dictionary to encode. +# +# Results: +# The bee-encoded form of the dictionary. + +proc ::bee::encodeDict {dict} { + if {([llength $dict] % 2) == 1} { + return -code error "Expected even number of elements, got \"[llength $dict]\"" + } + set temp [list] + foreach {k v} $dict { + lappend temp [list $k $v] + } + set res "d" + foreach item [lsort -index 0 $temp] { + foreach {k v} $item break + append res [encodeString $k]$v + } + append res "e" + return $res +} + + +# ::bee::encodeDictArgs -- +# +# Encode a variable dictionary of keys and bee-coded values to bee-format. +# +# Arguments: +# args The keys and values to encode. +# +# Results: +# The bee-encoded form of the dictionary. + +proc ::bee::encodeDictArgs {args} { + return [encodeDict $args] +} + + +# ::bee::decode -- +# +# Decode a bee-encoded value and returns the embedded tcl +# value. For containers this recurses into the contained value. +# +# Arguments: +# value The string containing the bee-encoded value to decode. +# evar Optional. If set the name of the variable to store the +# index of the first character after the decoded value to. +# start Optional. If set the index of the first character of the +# value to decode. Defaults to 0, i.e. the beginning of the +# string. +# +# Results: +# The tcl value embedded in the encoded string. + +proc ::bee::decode {value {evar {}} {start 0}} { + #variable X + #puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout + + if {$evar ne ""} {upvar 1 $evar end} else {set end _} + + if {[string length $value] < ($start+2)} { + # This checked that the 'start' index is still in the string, + # and the end of the value most likely as well. Note that each + # encoded value consists of at least two characters (the + # bracketing characters for integer, list, and dict, and for + # string at least one digit length and the colon). + + #puts \t[string length $value]\ <\ ($start+2) + return -code error "String not large enough for value" + } + + set type [string index $value $start] + + #puts -nonewline " $type=" ; flush stdout + + if {$type eq "i"} { + # Extract integer + #puts -nonewline integer ; flush stdout + + incr start ; # Skip over intro 'i'. + set end [string first e $value $start] + if {$end < 0} { + return -code error "End of integer number not found" + } + incr end -1 ; # Get last character before closing 'e'. + set num [string range $value $start $end] + ##nagelfar ignore + if { + [regexp {^-0+$} $num] || + ![string is integer -strict $num] || + (([string length $num] > 1) && [string match 0* $num]) + } { + return -code error "Expected integer number, got \"$num\"" + } + incr end 2 ; # Step after closing 'e' to the beginning of + # ........ ; # the next bee-value behind the current one. + + #puts " ($num) @$end" + return [format %d $num] + + } elseif {($type eq "l") || ($type eq "d")} { + #puts -nonewline $type\n ; flush stdout + + # Extract list or dictionary, recursively each contained + # element. From the perspective of the decoder this is the + # same, the tcl representation of both is a list, and for a + # dictionary keys and values are also already in the correct + # order. + + set result [list] + incr start ; # Step over intro 'e' to beginning of the first + # ........ ; # contained value, or behind the container (if + # ........ ; # empty). + + set end $start + #incr X + while {[string index $value $start] ne "e"} { + lappend result [decode $value end $start] + set start $end + } + #incr X -1 + incr end + + #puts "[string repeat " " $X]($result) @$end" + + if {$type eq "d" && ([llength $result] % 2 == 1)} { + return -code error "Dictionary has to be of even length" + } + return $result + + } elseif {[string match {[0-9]} $type]} { + #puts -nonewline string ; flush stdout + + # Extract string. First the length, bounded by a colon, then + # the appropriate number of characters. + + set end [string first : $value $start] + if {$end < 0} { + return -code error "End of string length not found" + } + incr end -1 + set length [string range $value $start $end] + incr end 2 ;# Skip to beginning of the string after the colon + + ##nagelfar ignore + if {![string is integer -strict $length]} { + return -code error "Expected integer number for string length, got \"$length\"" + } elseif {$length < 0} { + # This cannot happen. To happen "-" has to be first character, + # and this is caught as unknown bee-type. + return -code error "Illegal negative string length" + } elseif {($end + $length) > [string length $value]} { + return -code error "String not large enough for value" + } + + #puts -nonewline \[$length\] ; flush stdout + set length [format %d $length] + if {$length > 0} { + set start $end + incr end $length + incr end -1 + set result [string range $value $start $end] + incr end + } else { + set result "" + } + + #puts " ($result) @$end" + return $result + + } else { + return -code error "Unknown bee-type \"$type\"" + } +} + +# ::bee::decodeIndices -- +# +# Similar to 'decode', but does not return the decoded tcl values, +# but a structure containing the start- and end-indices for all +# values in the structure. +# +# Arguments: +# value The string containing the bee-encoded value to decode. +# evar Optional. If set the name of the variable to store the +# index of the first character after the decoded value to. +# start Optional. If set the index of the first character of the +# value to decode. Defaults to 0, i.e. the beginning of the +# string. +# +# Results: +# The structure of the value, with indices and types for all +# contained elements. + +proc ::bee::decodeIndices {value {evar {}} {start 0}} { + #variable X + #puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout + + if {$evar ne ""} {upvar 1 $evar end} else {set end _} + + if {[string length $value] < ($start+2)} { + # This checked that the 'start' index is still in the string, + # and the end of the value most likely as well. Note that each + # encoded value consists of at least two characters (the + # bracketing characters for integer, list, and dict, and for + # string at least one digit length and the colon). + + #puts \t[string length $value]\ <\ ($start+2) + return -code error "String not large enough for value" + } + + set type [string index $value $start] + + #puts -nonewline " $type=" ; flush stdout + + if {$type eq "i"} { + # Extract integer + #puts -nonewline integer ; flush stdout + + set begin $start + + incr start ; # Skip over intro 'i'. + set end [string first e $value $start] + if {$end < 0} { + return -code error "End of integer number not found" + } + incr end -1 ; # Get last character before closing 'e'. + set num [string range $value $start $end] + ##nagelfar ignore + if { + [regexp {^-0+$} $num] || + ![string is integer -strict $num] || + (([string length $num] > 1) && [string match 0* $num]) + } { + return -code error "Expected integer number, got \"$num\"" + } + incr end + set stop $end + incr end 1 ; # Step after closing 'e' to the beginning of + # ........ ; # the next bee-value behind the current one. + + #puts " ($num) @$end" + return [list integer $begin $stop] + + } elseif {$type eq "l"} { + #puts -nonewline $type\n ; flush stdout + + # Extract list, recursively each contained element. + + set result [list] + + lappend result list $start @ + + incr start ; # Step over intro 'e' to beginning of the first + # ........ ; # contained value, or behind the container (if + # ........ ; # empty). + + set end $start + #incr X + + set contained [list] + while {[string index $value $start] ne "e"} { + lappend contained [decodeIndices $value end $start] + set start $end + } + lappend result $contained + #incr X -1 + set stop $end + incr end + + #puts "[string repeat " " $X]($result) @$end" + + return [lreplace $result 2 2 $stop] + + } elseif {($type eq "l") || ($type eq "d")} { + #puts -nonewline $type\n ; flush stdout + + # Extract dictionary, recursively each contained element. + + set result [list] + + lappend result dict $start @ + + incr start ; # Step over intro 'e' to beginning of the first + # ........ ; # contained value, or behind the container (if + # ........ ; # empty). + + set end $start + set atkey 1 + #incr X + + set contained [list] + set val [list] + while {[string index $value $start] ne "e"} { + if {$atkey} { + lappend contained [decode $value {} $start] + lappend val [decodeIndices $value end $start] + set atkey 0 + } else { + lappend val [decodeIndices $value end $start] + lappend contained $val + set val [list] + set atkey 1 + } + set start $end + } + lappend result $contained + #incr X -1 + set stop $end + incr end + + #puts "[string repeat " " $X]($result) @$end" + + if {[llength $result] % 2 == 1} { + return -code error "Dictionary has to be of even length" + } + return [lreplace $result 2 2 $stop] + + } elseif {[string match {[0-9]} $type]} { + #puts -nonewline string ; flush stdout + + # Extract string. First the length, bounded by a colon, then + # the appropriate number of characters. + + set end [string first : $value $start] + if {$end < 0} { + return -code error "End of string length not found" + } + incr end -1 + set length [string range $value $start $end] + incr end 2 ;# Skip to beginning of the string after the colon + + ##nagelfar ignore + if {![string is integer -strict $length]} { + return -code error "Expected integer number for string length, got \"$length\"" + } elseif {$length < 0} { + # This cannot happen. To happen "-" has to be first character, + # and this is caught as unknown bee-type. + return -code error "Illegal negative string length" + } elseif {($end + $length) > [string length $value]} { + return -code error "String not large enough for value" + } + + set length [format %d $length] + #puts -nonewline \[$length\] ; flush stdout + incr end -1 + if {$length > 0} { + incr end $length + set stop $end + } else { + set stop $end + } + incr end + + #puts " ($result) @$end" + return [list string $start $stop] + + } else { + return -code error "Unknown bee-type \"$type\"" + } +} + + +# ::bee::decodeChannel -- +# +# Attach decoder for a bee-value to a channel. See the +# documentation for details. +# +# Arguments: +# chan Channel to attach to. +# -command cmdprefix Completion callback. Required. +# -exact Keep running after completion. +# -prefix data Seed for decode buffer. +# +# Results: +# A token to use when referring to the decoder. +# For example when canceling it. + +proc ::bee::decodeChannel {chan args} { + variable bee + if {[info exists bee($chan)]} { + return -code error "bee-Decoder already active for channel" + } + + # Create state and token. + + variable count + variable [set st state$count] + array set $st {} + set bee($chan) $st + upvar 0 $st state + incr count + + # Initialize the decoder state, process the options. When + # encountering errors here destroy the half-baked state before + # throwing the message. + + set state(chan) $chan + array set state { + exact 0 + type ? + read {} + value {} + pend {} + state intro + get 1 + } + + while {[llength $args]} { + set option [lindex $args 0] + set args [lrange $args 1 end] + if {$option eq "-command"} { + if {![llength $args]} { + unset bee($chan) + unset state + return -code error "Missing value for option -command." + } + set state(cmd) [lindex $args 0] + set args [lrange $args 1 end] + + } elseif {$option eq "-prefix"} { + if {![llength $args]} { + unset bee($chan) + unset state + return -code error "Missing value for option -prefix." + } + set state(read) [lindex $args 0] + set args [lrange $args 1 end] + + } elseif {$option eq "-exact"} { + set state(exact) 1 + } else { + unset bee($chan) + unset state + return -code error "Illegal option \"$option\",\ + expected \"-command\", \"-prefix\", or \"-keep\"" + } + } + + if {![info exists state(cmd)]} { + unset bee($chan) + unset state + return -code error "Missing required completion callback." + } + + # Set up the processing of incoming data. + + fileevent $chan readable [list ::bee::Process $chan $bee($chan)] + + # Return the name of the state array as token. + return $bee($chan) +} + +# ::bee::Parse -- +# +# Internal helper. Fileevent handler for a decoder. +# Parses input and handles both error and eof conditions. +# +# Arguments: +# token The decoder to run on its input. +# +# Results: +# None. + +proc ::bee::Process {chan token} { + if {[catch {Parse $token} msg]} { + # Something failed. Destroy and report. + Command $token error $msg + return + } + + if {[eof $chan]} { + # Having data waiting, either in the input queue, or in the + # output stack (of nested containers) is a failure. Report + # this instead of the eof. + + variable $token + upvar 0 $token state + + if { + [string length $state(read)] || + [llength $state(pend)] || + [string length $state(value)] || + ($state(state) ne "intro") + } { + Command $token error "Incomplete value at end of channel" + } else { + Command $token eof + } + } + return +} + +# ::bee::Parse -- +# +# Internal helper. Reading from the channel and parsing the input. +# Uses a hardwired state machine. +# +# Arguments: +# token The decoder to run on its input. +# +# Results: +# None. + +proc ::bee::Parse {token} { + variable $token + upvar 0 $token state + upvar 0 state(state) current + upvar 0 state(read) input + upvar 0 state(type) type + upvar 0 state(value) value + upvar 0 state(pend) pend + upvar 0 state(exact) exact + upvar 0 state(get) get + set chan $state(chan) + + #puts Parse/$current + + if {!$exact} { + # Add all waiting characters to the buffer so that we can process as + # much as is possible in one go. + append input [read $chan] + } else { + # Exact reading. Usually one character, but when in the data + # section for a string value we know for how many characters + # we are looking for. + + append input [read $chan $get] + } + + # We got nothing, do nothing. + if {![string length $input]} return + + + if {$current eq "data"} { + # String data, this can be done faster, as we read longer + # sequences of characters for this. + set l [string length $input] + if {$l < $get} { + # Not enough, wait for more. + append value $input + incr get -$l + return + } elseif {$l == $get} { + # Got all, exactly. Prepare state machine for next value. + + if {[Complete $token $value$input]} return + + set current intro + set get 1 + set value "" + set input "" + + return + } else { + # Got more than required (only for !exact). + + incr get -1 + if {[Complete $token $value[string range $input 0 $get]]} {return} + + incr get + set input [string range $input $get end] + set get 1 + set value "" + set current intro + # This now falls into the loop below. + } + } + + set where 0 + set n [string length $input] + + #puts Parse/$n + + while {$where < $n} { + # Hardwired state machine. Get current character. + set ch [string index $input $where] + + #puts Parse/@$where/$current/$ch/ + if {$current eq "intro"} { + # First character of a value. + + if {$ch eq "i"} { + # Begin reading integer. + set type integer + set current signum + } elseif {$ch eq "l"} { + # Begin a list. + set type list + lappend pend list {} + #set current intro + + } elseif {$ch eq "d"} { + # Begin a dictionary. + set type dict + lappend pend dict {} + #set current intro + + } elseif {$ch eq "e"} { + # Close a container. Throw an error if there is no + # container to close. + + if {![llength $pend]} { + return -code error "End of container outside of container." + } + + set v [lindex $pend end] + set t [lindex $pend end-1] + set pend [lrange $pend 0 end-2] + + if {$t eq "dict" && ([llength $v] % 2 == 1)} { + return -code error "Dictionary has to be of even length" + } + + if {[Complete $token $v]} {return} + set current intro + + } elseif {[string match {[0-9]} $ch]} { + # Begin reading a string, length section first. + set type string + set current ldigit + set value $ch + + } else { + # Unknown type. Throw error. + return -code error "Unknown bee-type \"$ch\"" + } + + # To next character. + incr where + } elseif {$current eq "signum"} { + # Integer number, a minus sign, or a digit. + if {[string match {[-0-9]} $ch]} { + append value $ch + set current idigit + } else { + return -code error "Syntax error in integer,\ + expected sign or digit, got \"$ch\"" + } + incr where + + } elseif {$current eq "idigit"} { + # Integer number, digit or closing 'e'. + + if {[string match {[-0-9]} $ch]} { + append value $ch + } elseif {$ch eq "e"} { + # Integer closes. Validate and report. + #puts validate + ##nagelfar ignore + if { + [regexp {^-0+$} $value] || + ![string is integer -strict $value] || + (([string length $value] > 1) && [string match 0* $value]) + } { + return -code error "Expected integer number, got \"$value\"" + } + set value [format %d $value] + if {[Complete $token $value]} {return} + set value "" + set current intro + } else { + return -code error "Syntax error in integer,\ + expected digit, or 'e', got \"$ch\"" + } + incr where + + } elseif {$current eq "ldigit"} { + # String, length section, digit, or : + + if {[string match {[-0-9]} $ch]} { + append value $ch + + } elseif {$ch eq ":"} { + # Length section closes, validate, + # then perform data processing. + + set num $value + ##nagelfar ignore + if { + [regexp {^-0+$} $num] || + ![string is integer -strict $num] || + (([string length $num] > 1) && [string match 0* $num]) + } { + return -code error "Expected integer number as string length, got \"$num\"" + } + set num [format %d $num] + set value "" + + # We may have already part of the data in + # memory. Process that piece before looking for more. + + incr where + set have [expr {$n - $where}] + if {$num < $have} { + # More than enough in the buffer. + + set end $where + incr end $num + incr end -1 + + if {[Complete $token [string range $input $where $end]]} {return} + + set where $end ;# Further processing behind the string. + set current intro + + } elseif {$num == $have} { + # Just enough. + + if {[Complete $token [string range $input $where end]]} {return} + + set where $n + set current intro + } else { + # Not enough. Initialize value with the data we + # have (after the colon) and stop processing for + # now. + + set value [string range $input $where end] + set current data + set get $num + set input "" + return + } + } else { + return -code error "Syntax error in string length,\ + expected digit, or ':', got \"$ch\"" + } + incr where + } else { + # unknown state = internal error + return -code error "Unknown decoder state \"$current\", internal error" + } + } + + set input "" + return +} + +# ::bee::Command -- +# +# Internal helper. Runs the decoder command callback. +# +# Arguments: +# token The decoder invoking its callback +# how Which method to invoke (value, error, eof) +# args Arguments for the method. +# +# Results: +# A boolean flag. Set if further processing has to stop. + +proc ::bee::Command {token how args} { + variable $token + upvar 0 $token state + + #puts Report/$token/$how/$args/ + + set cmd $state(cmd) + set chan $state(chan) + + # We catch the fileevents because they will fail when this is + # called from the 'Close'. The channel will already be gone in + # that case. + + set stop 0 + if {($how eq "error") || ($how eq "eof")} { + variable bee + + set stop 1 + fileevent $chan readable {} + unset bee($chan) + unset state + + if {$how eq "eof"} { + #puts \tclosing/$chan + close $chan + } + } + + lappend cmd $how $token + foreach a $args {lappend cmd $a} + uplevel #0 $cmd + + if {![info exists state]} { + # The decoder token was killed by the callback, stop + # processing. + set stop 1 + } + + #puts /$stop/[file channels] + return $stop +} + +# ::bee::Complete -- +# +# Internal helper. Reports a completed value. +# +# Arguments: +# token The decoder reporting the value. +# value The value to report. +# +# Results: +# A boolean flag. Set if further processing has to stop. + +proc ::bee::Complete {token value} { + variable $token + upvar 0 $token state + upvar 0 state(pend) pend + + if {[llength $pend]} { + # The value is part of a container. Add the value to its end + # and keep processing. + + set pend [lreplace $pend end end \ + [linsert [lindex $pend end] end \ + $value]] + + # Don't stop. + return 0 + } + + # The value is at the top, report it. The callback determines if + # we keep processing. + + return [Command $token value $value] +} + +# ::bee::decodeCancel -- +# +# Destroys the decoder referenced by the token. +# +# Arguments: +# token The decoder to destroy. +# +# Results: +# None. + +proc ::bee::decodeCancel {token} { + variable bee + variable $token + upvar 0 $token state + unset bee($state(chan)) + unset state + return +} + +# ::bee::decodePush -- +# +# Push data into the decoder input buffer. +# +# Arguments: +# token The decoder to extend. +# string The characters to add. +# +# Results: +# None. + +proc ::bee::decodePush {token string} { + variable $token + upvar 0 $token state + append state(read) $string + return +} + + +package provide bee 0.3 diff --git a/src/vendorlib_tcl9/tcllib2.0/bee/pkgIndex.tcl b/src/vendorlib_tcl9/tcllib2.0/bee/pkgIndex.tcl new file mode 100644 index 00000000..58a35f35 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/bee/pkgIndex.tcl @@ -0,0 +1,4 @@ +# Tcl package index file, version 1.1 + +if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} +package ifneeded bee 0.3 [list source [file join $dir bee.tcl]] diff --git a/src/vendorlib_tcl9/tcllib2.0/bench/bench.tcl b/src/vendorlib_tcl9/tcllib2.0/bench/bench.tcl new file mode 100644 index 00000000..724583c0 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/bench/bench.tcl @@ -0,0 +1,556 @@ +# bench.tcl -- +# +# Management of benchmarks. +# +# Copyright (c) 2005-2008 by Andreas Kupries +# library derived from runbench.tcl application (C) Jeff Hobbs. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +# ### ### ### ######### ######### ######### ########################### +## Requisites - Packages and namespace for the commands and data. + +package require Tcl 8.5 9 +package require logger +package require csv +package require struct::matrix +package require report + +namespace eval ::bench {} +namespace eval ::bench::out {} + +# @mdgen OWNER: libbench.tcl + +# ### ### ### ######### ######### ######### ########################### +## Public API - Benchmark execution + +# ::bench::run -- +# +# Run a series of benchmarks. +# +# Arguments: +# ... +# +# Results: +# Dictionary. + +proc ::bench::run {args} { + log::debug [linsert $args 0 ::bench::run] + + # -errors 0|1 default 1, propagate errors in benchmarks + # -threads default 0, no threads, #threads to use + # -match only run tests matching this pattern + # -rmatch only run tests matching this pattern + # -iters default 1000, max#iterations for any benchmark + # -pkgdir Defaults to nothing, regular bench invokation. + + # interps - dict (path -> version) + # files - list (of files) + + # Process arguments ...................................... + # Defaults first, then overides by the user + + set errors 1 ; # Propagate errors + set threads 0 ; # Do not use threads + set match {} ; # Do not exclude benchmarks based on glob pattern + set rmatch {} ; # Do not exclude benchmarks based on regex pattern + set iters 1000 ; # Limit #iterations for any benchmark + set pkgdirs {} ; # List of dirs to put in front of auto_path in the + # bench interpreters. Default: nothing. + + while {[string match "-*" [set opt [lindex $args 0]]]} { + set val [lindex $args 1] + switch -exact -- $opt { + -errors { + if {![string is boolean -strict $val]} { + return -code error "Expected boolean, got \"$val\"" + } + set errors $val + } + -threads { + ##nagelfar ignore + if {![string is integer -strict $val] || ($val < 0)} { + return -code error "Expected int >= 0, got \"$val\"" + } + set threads [format %d $val] + } + -match { + set match [lindex $args 1] + } + -rmatch { + set rmatch [lindex $args 1] + } + -iters { + ##nagelfar ignore + if {![string is integer -strict $val] || ($val <= 0)} { + return -code error "Expected int > 0, got \"$val\"" + } + set iters [format %d $val] + } + -pkgdir { + CheckPkgDirArg $val + lappend pkgdirs $val + } + default { + return -code error "Unknown option \"$opt\", should -errors, -threads, -match, -rmatch, or -iters" + } + } + set args [lrange $args 2 end] + } + if {[llength $args] != 2} { + return -code error "wrong\#args, should be: ?options? interp files" + } + foreach {interps files} $args break + + # Run the benchmarks ..................................... + + array set DATA {} + + if {![llength $pkgdirs]} { + # No user specified package directories => Simple run. + foreach {ip ver} $interps { + Invoke $ip $ver {} ;# DATA etc passed via upvar. + } + } else { + # User specified package directories. + foreach {ip ver} $interps { + foreach pkgdir $pkgdirs { + Invoke $ip $ver $pkgdir ;# DATA etc passed via upvar. + } + } + } + + # Benchmark data ... Structure, dict (key -> value) + # + # Key || Value + # ============ ++ ========================================= + # interp IP -> Version. Shell IP was used to run benchmarks. IP is + # the path to the shell. + # + # desc DESC -> "". DESC is description of an executed benchmark. + # + # usec DESC IP -> Result. Result of benchmark DESC when run by the + # shell IP. Usually time in microseconds, but can be + # a special code as well (ERR, BAD_RES). + # ============ ++ ========================================= + + return [array get DATA] +} + +# ::bench::locate -- +# +# Locate interpreters on the pathlist, based on a pattern. +# +# Arguments: +# ... +# +# Results: +# List of paths. + +proc ::bench::locate {pattern paths} { + # Cache of executables already found. + array set var {} + set res {} + + foreach path $paths { + foreach ip [glob -nocomplain [file join $path $pattern]] { + set ip [file normalize $ip] + + # Follow soft-links to the actual executable. + while {[string equal link [file type $ip]]} { + set link [file readlink $ip] + if {[string match relative [file pathtype $link]]} { + set ip [file join [file dirname $ip] $link] + } else { + set ip $link + } + } + + if { + [file executable $ip] && ![info exists var($ip)] + } { + if {[catch {exec $ip << "exit"} dummy]} { + log::debug "$ip: $dummy" + continue + } + set var($ip) . + lappend res $ip + } + } + } + + return $res +} + +# ::bench::versions -- +# +# Take list of interpreters, find their versions. +# Removes all interps for which it cannot do so. +# +# Arguments: +# List of interpreters (paths) +# +# Results: +# dictionary: interpreter -> version. + +proc ::bench::versions {interps} { + set res {} + foreach ip $interps { + if {[catch { + exec $ip << {puts [info patchlevel] ; exit} + } patchlevel]} { + log::debug "$ip: $patchlevel" + continue + } + + lappend res [list $patchlevel $ip] + } + + # -uniq 8.4-ism, replaced with use of array. + array set tmp {} + set resx {} + foreach item [lsort -dictionary -decreasing -index 0 $res] { + foreach {p ip} $item break + if {[info exists tmp($p)]} continue + set tmp($p) . + lappend resx $ip $p + } + + return $resx +} + +# ::bench::merge -- +# +# Take the data of several benchmark runs and merge them into +# one data set. +# +# Arguments: +# One or more data sets to merge +# +# Results: +# The merged data set. + +proc ::bench::merge {args} { + if {[llength $args] == 1} { + return [lindex $args 0] + } + + array set DATA {} + foreach data $args { + array set DATA $data + } + return [array get DATA] +} + +# ::bench::norm -- +# +# Normalize the time data in the dataset, using one of the +# columns as reference. +# +# Arguments: +# Data to normalize +# Index of reference column +# +# Results: +# The normalized data set. + +proc ::bench::norm {data col} { + + ##nagelfar ignore + if {![string is integer -strict $col]} { + return -code error "Ref.column: Expected integer, but got \"$col\"" + } + set col [format %d $col] + if {$col < 1} { + return -code error "Ref.column out of bounds" + } + + array set DATA $data + set ipkeys [array names DATA interp*] + + if {$col > [llength $ipkeys]} { + return -code error "Ref.column out of bounds" + } + incr col -1 + set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1] + + foreach key [array names DATA] { + if {[string match "desc*" $key]} continue + if {[string match "interp*" $key]} continue + + foreach {_ desc ip} $key break + if {[string equal $ip $refip]} continue + + set v $DATA($key) + if {![string is double -strict $v]} continue + + if {![info exists DATA([list usec $desc $refip])]} { + # We cannot normalize, we do not keep the time value. + # The row will be shown, empty. + set DATA($key) "" + continue + } + set vref $DATA([list usec $desc $refip]) + + if {![string is double -strict $vref]} continue + + set DATA($key) [expr {$v/double($vref)}] + } + + foreach key [array names DATA [list * $refip]] { + if {![string is double -strict $DATA($key)]} continue + set DATA($key) 1 + } + + return [array get DATA] +} + +# ::bench::edit -- +# +# Change the 'path' of an interp to a user-defined value. +# +# Arguments: +# Data to edit +# Index of column to change +# The value replacing the current path +# +# Results: +# The changed data set. + +proc ::bench::edit {data col new} { + + ##nagelfar ignore + if {![string is integer -strict $col]} { + return -code error "Ref.column: Expected integer, but got \"$col\"" + } + set col [format %d $col] + if {$col < 1} { + return -code error "Ref.column out of bounds" + } + + array set DATA $data + set ipkeys [array names DATA interp*] + + if {$col > [llength $ipkeys]} { + return -code error "Ref.column out of bounds" + } + incr col -1 + set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1] + + if {[string equal $new $refip]} { + # No change, quick return + return $data + } + + set refkey [list interp $refip] + set DATA([list interp $new]) $DATA($refkey) + unset DATA($refkey) + + foreach key [array names DATA [list * $refip]] { + if {![string equal [lindex $key 0] "usec"]} continue + foreach {__ desc ip} $key break + set DATA([list usec $desc $new]) $DATA($key) + unset DATA($key) + } + + return [array get DATA] +} + +# ::bench::del -- +# +# Remove the data for an interp. +# +# Arguments: +# Data to edit +# Index of column to remove +# +# Results: +# The changed data set. + +proc ::bench::del {data col} { + ##nagelfar ignore + if {![string is integer -strict $col]} { + return -code error "Ref.column: Expected integer, but got \"$col\"" + } + set col [format %d $col] + if {$col < 1} { + return -code error "Ref.column out of bounds" + } + + array set DATA $data + set ipkeys [array names DATA interp*] + + if {$col > [llength $ipkeys]} { + return -code error "Ref.column out of bounds" + } + incr col -1 + set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1] + + unset DATA([list interp $refip]) + + # Do not use 'array unset'. Keep 8.2 clean. + foreach key [array names DATA [list * $refip]] { + if {![string equal [lindex $key 0] "usec"]} continue + unset DATA($key) + } + + return [array get DATA] +} + +# ### ### ### ######### ######### ######### ########################### +## Public API - Result formatting. + +# ::bench::out::raw -- +# +# Format the result of a benchmark run. +# Style: Raw data. +# +# Arguments: +# DATA dict +# +# Results: +# String containing the formatted DATA. + +proc ::bench::out::raw {data} { + return $data +} + +# ### ### ### ######### ######### ######### ########################### +## Internal commands + +proc ::bench::CheckPkgDirArg {path {expected {}}} { + # Allow empty string, special. + if {![string length $path]} return + + if {![file isdirectory $path]} { + return -code error \ + "The path \"$path\" is not a directory." + } + if {![file readable $path]} { + return -code error \ + "The path \"$path\" is not readable." + } +} + +proc ::bench::Invoke {ip ver pkgdir} { + variable self + # Import remainder of the current configuration/settings. + + upvar 1 DATA DATA match match rmatch rmatch \ + iters iters errors errors threads threads \ + files files + + if {[string length $pkgdir]} { + log::info "Benchmark $ver ($pkgdir) $ip" + set idstr "$ip ($pkgdir)" + } else { + log::info "Benchmark $ver $ip" + set idstr $ip + } + + set DATA([list interp $idstr]) $ver + + set cmd [list $ip [file join $self libbench.tcl] \ + -match $match \ + -rmatch $rmatch \ + -iters $iters \ + -interp $ip \ + -errors $errors \ + -threads $threads \ + -pkgdir $pkgdir \ + ] + + # Determine elapsed time per file, logged. + set start [clock seconds] + + array set tmp {} + + if {$threads} { + foreach f $files { lappend cmd $f } + if {[catch { + close [Process [open |$cmd r+]] + } output]} { + if {$errors} { + error $::errorInfo + } + } + } else { + foreach file $files { + log::info [file tail $file] + if {[catch { + close [Process [open |[linsert $cmd end $file] r+]] + } output]} { + if {$errors} { + error $::errorInfo + } else { + continue + } + } + } + } + + foreach desc [array names tmp] { + set DATA([list desc $desc]) {} + set DATA([list usec $desc $idstr]) $tmp($desc) + } + + unset tmp + set elapsed [expr {[clock seconds] - $start}] + + set hour [expr {$elapsed / 3600}] + set min [expr {$elapsed / 60}] + set sec [expr {$elapsed % 60}] + log::info " [format %.2d:%.2d:%.2d $hour $min $sec] elapsed" + return +} + + +proc ::bench::Process {pipe} { + while {1} { + if {[eof $pipe]} break + if {[gets $pipe line] < 0} break + # AK: FUTURE: Log all lines?! + #puts |$line| + set line [string trim $line] + if {[string equal $line ""]} continue + + Result + Feedback + # Unknown lines are printed. Future: Callback?! + log::info $line + } + return $pipe +} + +proc ::bench::Result {} { + upvar 1 line line + if {[lindex $line 0] ne "RESULT"} return + upvar 2 tmp tmp + foreach {_ desc result} $line break + set tmp($desc) $result + return -code continue +} + +proc ::bench::Feedback {} { + upvar 1 line line + if {[lindex $line 0] ne "LOG"} return + # AK: Future - Run through callback?! + log::info [lindex $line 1] + return -code continue +} + +# ### ### ### ######### ######### ######### ########################### +## Initialize internal data structures. + +namespace eval ::bench { + variable self [file join [pwd] [file dirname [info script]]] + + logger::init bench + logger::import -force -all -namespace log bench +} + +# ### ### ### ######### ######### ######### ########################### +## Ready to run + +package provide bench 0.6 diff --git a/src/vendorlib_tcl9/tcllib2.0/bench/bench_read.tcl b/src/vendorlib_tcl9/tcllib2.0/bench/bench_read.tcl new file mode 100644 index 00000000..5098b95f --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/bench/bench_read.tcl @@ -0,0 +1,162 @@ +# bench_read.tcl -- +# +# Management of benchmarks, reading results in various formats. +# +# Copyright (c) 2005 by Andreas Kupries +# library derived from runbench.tcl application (C) Jeff Hobbs. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: bench_read.tcl,v 1.3 2006/06/13 23:20:30 andreas_kupries Exp $ + +# ### ### ### ######### ######### ######### ########################### +## Requisites - Packages and namespace for the commands and data. + +package require Tcl 8.5 9 +package require csv + +namespace eval ::bench::in {} + +# ### ### ### ######### ######### ######### ########################### +## Public API - Result reading + +# ::bench::in::read -- +# +# Read a bench result in any of the raw/csv/text formats +# +# Arguments: +# path to file to read +# +# Results: +# DATA dictionary, internal representation of the bench results. + +proc ::bench::in::read {file} { + + set f [open $file r] + set head [gets $f] + + if {![string match "# -\\*- tcl -\\*- bench/*" $head]} { + return -code error "Bad file format, not a benchmark file" + } else { + regexp {bench/(.*)$} $head -> format + + switch -exact -- $format { + raw - csv - text { + set res [RD$format $f] + } + default { + return -code error "Bad format \"$val\", expected text, csv, or raw" + } + } + } + close $f + return $res +} + +# ### ### ### ######### ######### ######### ########################### +## Internal commands + +proc ::bench::in::RDraw {chan} { + return [string trimright [::read $chan]] +} + +proc ::bench::in::RDcsv {chan} { + # Lines Format + # First line is number of interpreters #n. int + # Next to 1+n is interpreter data. id,ver,path + # Beyond is benchmark results. id,desc,res1,...,res#n + + array set DATA {} + + # #Interp ... + + set nip [lindex [csv::split [gets $chan]] 0] + + # Interp data ... + + set iplist {} + for {set i 0} {$i < $nip} {incr i} { + foreach {__ ver ip} [csv::split [gets $chan]] break + + set DATA([list interp $ip]) $ver + lappend iplist $ip + } + + # Benchmark data ... + + while {[gets $chan line] >= 0} { + set line [string trim $line] + if {$line == {}} break + set line [csv::split $line] + set desc [lindex $line 1] + + set DATA([list desc $desc]) {} + foreach val [lrange $line 2 end] ip $iplist { + if {$val == {}} continue + set DATA([list usec $desc $ip]) $val + } + } + + return [array get DATA] +} + +proc ::bench::in::RDtext {chan} { + array set DATA {} + + # Interp data ... + + # Empty line - ignore + # "id: ver path" - interp data. + # Empty line - separator before benchmark data. + + set n 0 + set iplist {} + while {[gets $chan line] >= 0} { + set line [string trim $line] + if {$line == {}} { + incr n + if {$n == 2} break + continue + } + + regexp {[^:]+: ([^ ]+) (.*)$} $line -> ver ip + set DATA([list interp $ip]) $ver + lappend iplist $ip + } + + # Benchmark data ... + + # '---' -> Ignore. + # '|' column separators. Remove spaces around it. Then treat line + # as CSV data with a particular separator. + # Ignore the INTERP line. + + while {[gets $chan line] >= 0} { + set line [string trim $line] + if {$line == {}} continue + if {[string match "+---*" $line]} continue + if {[string match "*INTERP*" $line]} continue + + regsub -all "\\| +" $line {|} line + regsub -all " +\\|" $line {|} line + set line [csv::split [string trim $line |] |] + set desc [lindex $line 1] + + set DATA([list desc $desc]) {} + foreach val [lrange $line 2 end] ip $iplist { + if {$val == {}} continue + set DATA([list usec $desc $ip]) $val + } + } + + return [array get DATA] +} + +# ### ### ### ######### ######### ######### ########################### +## Initialize internal data structures. + +# ### ### ### ######### ######### ######### ########################### +## Ready to run + +package provide bench::in 0.2 diff --git a/src/vendorlib_tcl9/tcllib2.0/bench/bench_wcsv.tcl b/src/vendorlib_tcl9/tcllib2.0/bench/bench_wcsv.tcl new file mode 100644 index 00000000..321997f6 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/bench/bench_wcsv.tcl @@ -0,0 +1,101 @@ +# bench_wtext.tcl -- +# +# Management of benchmarks, formatted text. +# +# Copyright (c) 2005 by Andreas Kupries +# library derived from runbench.tcl application (C) Jeff Hobbs. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: bench_wcsv.tcl,v 1.4 2007/01/21 23:29:06 andreas_kupries Exp $ + +# ### ### ### ######### ######### ######### ########################### +## Requisites - Packages and namespace for the commands and data. + +package require Tcl 8.5 9 +package require csv + +namespace eval ::bench::out {} + +# ### ### ### ######### ######### ######### ########################### +## Public API - Benchmark execution + +# ### ### ### ######### ######### ######### ########################### +## Public API - Result formatting. + +# ::bench::out::csv -- +# +# Format the result of a benchmark run. +# Style: CSV +# +# Arguments: +# DATA dict +# +# Results: +# String containing the formatted DATA. + +proc ::bench::out::csv {data} { + array set DATA $data + set CSV {} + + # 1st record: #shells + # 2nd record to #shells+1: Interpreter data (id, version, path) + # #shells+2 to end: Benchmark data (id,desc,result1,...,result#shells) + + # --- --- ---- + # #interpreters used + + set ipkeys [array names DATA interp*] + lappend CSV [csv::join [list [llength $ipkeys]]] + + # --- --- ---- + # Table 1: Interpreter information. + + set n 1 + set iplist {} + foreach key [lsort -dict $ipkeys] { + set ip [lindex $key 1] + lappend CSV [csv::join [list $n $DATA($key) $ip]] + set DATA($key) $n + incr n + lappend iplist $ip + } + + # --- --- ---- + # Table 2: Benchmark information + + set dlist {} + foreach key [lsort -dict -index 1 [array names DATA desc*]] { + lappend dlist [lindex $key 1] + } + + set n 1 + foreach desc $dlist { + set record {} + lappend record $n + lappend record $desc + foreach ip $iplist { + if {[catch { + lappend record $DATA([list usec $desc $ip]) + }]} { + lappend record {} + } + } + lappend CSV [csv::join $record] + incr n + } + + return [join $CSV \n] +} + +# ### ### ### ######### ######### ######### ########################### +## Internal commands + +# ### ### ### ######### ######### ######### ########################### +## Initialize internal data structures. + +# ### ### ### ######### ######### ######### ########################### +## Ready to run + +package provide bench::out::csv 0.1.3 diff --git a/src/vendorlib_tcl9/tcllib2.0/bench/bench_wtext.tcl b/src/vendorlib_tcl9/tcllib2.0/bench/bench_wtext.tcl new file mode 100644 index 00000000..8c16b21a --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/bench/bench_wtext.tcl @@ -0,0 +1,165 @@ +# bench_wtext.tcl -- +# +# Management of benchmarks, formatted text. +# +# Copyright (c) 2005 by Andreas Kupries +# library derived from runbench.tcl application (C) Jeff Hobbs. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: bench_wtext.tcl,v 1.4 2007/01/21 23:29:06 andreas_kupries Exp $ + +# ### ### ### ######### ######### ######### ########################### +## Requisites - Packages and namespace for the commands and data. + +package require Tcl 8.5 9 +package require struct::matrix +package require report + +namespace eval ::bench::out {} + +# ### ### ### ######### ######### ######### ########################### +## Public API - Result formatting. + +# ::bench::out::text -- +# +# Format the result of a benchmark run. +# Style: TEXT +# +# General structure like CSV, but nicely formatted and aligned +# columns. +# +# Arguments: +# DATA dict +# +# Results: +# String containing the formatted DATA. + +proc ::bench::out::text {data} { + array set DATA $data + set LINES {} + + # 1st line to #shells: Interpreter data (id, version, path) + # #shells+1 to end: Benchmark data (id,desc,result1,...,result#shells) + + lappend LINES {} + + # --- --- ---- + # Table 1: Interpreter information. + + set ipkeys [array names DATA interp*] + set n 1 + set iplist {} + set vlen 0 + foreach key [lsort -dict $ipkeys] { + lappend iplist [lindex $key 1] + incr n + set l [string length $DATA($key)] + if {$l > $vlen} {set vlen $l} + } + set idlen [string length $n] + + set dlist {} + set n 1 + foreach key [lsort -dict -index 1 [array names DATA desc*]] { + lappend dlist [lindex $key 1] + incr n + } + set didlen [string length $n] + + set n 1 + set record [list "" INTERP] + foreach ip $iplist { + set v $DATA([list interp $ip]) + lappend LINES " [PADL $idlen $n]: [PADR $vlen $v] $ip" + lappend record $n + incr n + } + + lappend LINES {} + + # --- --- ---- + # Table 2: Benchmark information + + set m [struct::matrix m] + $m add columns [expr {2 + [llength $iplist]}] + $m add row $record + + set n 1 + foreach desc $dlist { + set record [list $n] + lappend record $desc + + foreach ip $iplist { + if {[catch { + set val $DATA([list usec $desc $ip]) + }]} { + set val {} + } + if {[string is double -strict $val]} { + lappend record [format %.2f $val] + } else { + lappend record [format %s $val] + } + } + $m add row $record + incr n + } + + ::report::defstyle simpletable {} { + data set [split "[string repeat "| " [columns]]|"] + top set [split "[string repeat "+ - " [columns]]+"] + bottom set [top get] + top enable + bottom enable + + set c [columns] + justify 0 right + pad 0 both + + if {$c > 1} { + justify 1 left + pad 1 both + } + for {set i 2} {$i < $c} {incr i} { + justify $i right + pad $i both + } + } + ::report::defstyle captionedtable {{n 1}} { + simpletable + topdata set [data get] + topcapsep set [top get] + topcapsep enable + tcaption $n + } + + set r [report::report r [$m columns] style captionedtable] + lappend LINES [$m format 2string $r] + $m destroy + $r destroy + + return [join $LINES \n] +} + +# ### ### ### ######### ######### ######### ########################### +## Internal commands + +proc ::bench::out::PADL {max str} { + format "%${max}s" $str + #return "[PAD $max $str]$str" +} + +proc ::bench::out::PADR {max str} { + format "%-${max}s" $str + #return "$str[PAD $max $str]" +} + +# ### ### ### ######### ######### ######### ########################### +## Initialize internal data structures. + +# ### ### ### ######### ######### ######### ########################### +## Ready to run + +package provide bench::out::text 0.1.3 diff --git a/src/vendorlib_tcl9/tcllib2.0/bench/libbench.tcl b/src/vendorlib_tcl9/tcllib2.0/bench/libbench.tcl new file mode 100644 index 00000000..ebf9f716 --- /dev/null +++ b/src/vendorlib_tcl9/tcllib2.0/bench/libbench.tcl @@ -0,0 +1,561 @@ +# -*- tcl -*- +# libbench.tcl ?(
  2. F7{6K3%S4Ooupg&9ya#^}o#@6R|Ojs^9D z>KfgqnJ;4J4!epntuoVf%b@dZKlBAD^B1Iry|MlXL|~a)z4GT@M>wF3$T>I zVnzO5tbdHJs|}ok#|mO^Rf76tAEJAy66|DQ3lv)!%Wp?5_gJA;>-&IxHPX`^#$-6X zme7ra8k=FOT!8WOZ+$y~QMeTbRVY*1bP{d5T^S^c9$7<2{g-adRwENWX@nW`!?3Ej z0M0|kUZHX1%g8SWH-kw-m9sk6oqsTw(VM;wegEU^WV+Xe3^c6GN3qeN z6j_^VX+LZ3&k8GJ0A^4^ zam9P3XO468=1lTNJy+ZCcul-LzlJ*wyO!OoHM5W>O`3J}U8{X-J4v>!h1IJ}j z?&9ukdrjrD*9>C=aS`SrSyp@5A!4N-tM^{Y9oZoWC8 zq+cfQz9DduT^pZ~o-5cjUyDWBOLP}1wl67EfXrKDJi6o7%-asVxWm@rbi&TtH3$3i zHmd}@d3P{xZ{jVD_^oIxFUIe%Tszd_GE^>*cH1dCm#hFe>fA3+S-G@1m*~BwEF21k zUqbe2>$R22*Th|CChk3?I6@irQq2FE$%=Fsnkwn`MMzEP%bp7md*z2Wd>qSgA$UvMt-#fYJ1Sv-cU#F&92gx65PT3{^UQ zvo>OdzKa=Ip}iIbvkYz|u7n`twx1R9T*B6R?{jEKJJ>D*7kU*JZ0a+3yx_#3q-lm> zciPq(pmR$EQLkQgwp%|NKQj^R!QAoUV)vm3u{j#rzZu2a^VRH(TL7T*)K^oGFAq0} zeNZNR4ZQ$*+pgI%{PT>lF4WnR&E7AKRIHOYdNfMKY>z@Ylv^fzG<`;%zT<|QkA@ow zBr~#dgxi=8x?tC6$c|`eZeY-@^e4{r7r(cWJSk_)`#toM5X>w@F9Ie+6cD=jh?pq4 zb8WTh*%j25jYY|5xx>?W5YT|oT9t7Miu1m4yeB7H=9C=e$<&sKs zNEH8Xl^T_RfxX!t>6HAzUxwhEZKUp1SVi4*O>GOm45H{81F(!ApTRhYJvM_BkzK+{ z5#n~@uyarc!s^ipT_+2zH#^m@_T2xD(0H?V9E40HMk%nU0Mn#PeW63OjjJBR@-9~L?)n>;ue(31(rs0Ob6jr`G`(s+Lzi9!es|``=GwS5l zzLq@?gbd+6_lu0Nd#apQIA@bvyE8X(r6M0E5jj0Gn)R6T8deG>BH@j`uyF3Uz~3JE z4n2HRCI6et_A28{vN6h7Jp6@Pl@GZzLVk5;Y2ob0WA5YW?-6y$UPZ8sU~U?<{$PQe zbFzE+YeU;UXgX%E0=ECq6d+3){NLvku=9n(|D9RSc?BHzJt4wr_-+hbP)foPUayWc zB{{9<6H94M{M)?5cb7Wx8eZI||8tq2C30GQ>{pd}Pby@~bci+dBWN=B91A1RJCyyn zAa6U*Jim=RFY-Q)-@HEk^p|J;_v_c!|K0m1fA4_3z6<>CP(W+!e1JdJq$tiF z`pVwq(eC-gC%PTiGZ+^i@xjI5zuP>$&fOk$)>IWj)!nJ}5w|_ExZ9inTlymSxHKS9avGLv9$a$b(8b+t?3<3%T z;*L)J!~_&NOLm5UAfPRP_!j}6M4aLQ&Uc>t*cnpSH8bX1vpMyHAi)#Ff2{bN$TNe) zzf*1veb}Bty6_3+{CFKr-XG6(fiPT6{s5@>?qX-j0$xPPCjSjemQd95CaR!$-v}2> ze}L(Q4SvW37dmYSoT%;q?^mw|QNQq`mKb%rN1}7JFrL=7_m0_)+hl>Z$_-i7Crsd8x(QT&r>Dk8~t2TBIaF&jwoY+d_ zjIA4kVU>l}l0TUAYX+>9G3dOPeWrc*_uYl`VGZs$jf+DwK;NR3bZ?h81P6|GaE9B4pT1ALeY-L0GItA^ znpLdw5#PDLuFGr(u?p&ldgb$D>%4;a>&{qKlhV8|`sXc+zN_~`^-liYvS^x${bQ6c z$MDpq^uN+(XAN0_#(6ne4a&`jgVyW3!uYn_%+n@NCH4+~Z?8h%r_hV{Din1jPLI31 z3%3R>hJ`eEI}B47+Xr9sVMb>0a)4h|>_7mg-%}qWG{rks)9|&(Q!~hl=7rtKu!ftx zi=0$4BVRK$WI9@ZVob3{BCBz{h5xBx@iAlAKJ{xJ93OKEFMXQ^rk0ScbNxbPZppY+z(a5GU_ZylDnFa!;5ji5bNTqX zzOl-u@DDq3b8O45Si=_8^`5viE}{a@QaU)iC0*I&j9u?Cb+>spW@GOmR@paKp?Kvi zO8(|p<*qqHv@>iuIS;j7=CN4imf4>$l(&78C5J9QCN>gFA$YVNRJz2L;g~NF9azI=&lvBqm(+XigS4hFCaf$?Ze|n;iE!rmti1 zq5V-!vZesphST;N5CH$T>`VGJJMl5=ctJ9A1EJ}D&J9bAxIMJ3oVq8A4ntnv<4?BB znm=zK&J2p^h6EIcq;%%ZSu?g^b!k(7dp@jqb(PVU;gcDb)rqc44kg1g236w~tn%eK z{nr+eBt4@hzkB2>@!f-Fk8*wKo68YwCK44GrxeVumKWy)y2R_g2 z?D|^!5imG;X>GeIY&|cT0mqrU=UZzaLTjfnS0TCEwd*~2M_+hncz;fJu)EEk?7(vb zw;2r2t6y1=cd`G>;kS+F7M|bn{Eg=so`3q!2mB8HDd)L)=KQQ6?>qc9^W4ev4o~Xm z_FG{`5g$C$cyHvn*nb@0PU5+T=j7W9@_x$m^;`e%XT@y=d3r*mxy%36`#t2Tr-t-5 z^9&&FYdmNA&uRP~oAVun2fu<39@QhL&)&ZQ4}-t{tn=@c?3-qR4h%_XTfn-M%k4y> z4x*oIXS8b(-9q1|z`+pOUhCGg3rlL3z-qIu7DEhz*VY1pQfp{cB)+h-39I&FI-B<~ z*phlm{DUr=j{L2VRMl?Lvv74RBkN|yU$2tcVm(!eB+vRBg0!u)sla_Kz0b_HCnN5< zh!b6p4YJwfOjkWQL^Z|yMz>+XB-0*EOQY#wf#=jB*}~(!7+O2Xr2|WMYl_*raUM<6W+$w zP1!6Z8#JNQ@fe_E;Le&0M$Ax)YSE%LN~9Je)ZH0%FKVjG|CfXwksZ;-KDCL9RzM+$ zqX!o)z}3Z1d8dKV`44wM%?JT=h!IPB%`w#g`qE6M4A#6QQ>jfKO<%JEu%?`Zu457DBw1xCehD(YLNsub?vc4pS7krkc>~c|4xlzjn5O%rXJSI7%&qt78dNooxZH9B*DoCeA)B!Lh3?i96 zAIpDQ^sX5CviJJ15M~Y$VAe^+px8*Z)=%4YqQ>3dde1ors9-C_NwpF$jE5yGl5RIk zJa;HfzIUcv1We*PxZRLGt#P072I4RWgj>xFc}HsJ%H2-qiP)JI+wm%0EDc$PAnywE_KbT#=e z3b7K=Wts3@q;$6{Edqi$v}mQgpkXPX=|^+?_Aw(hMSv5<3Z`Fkwpd2yL~P4f61mC@ zF0Q{D)nZ1|+H#ip=}W!-#%68NDqF1d{*9b^F7ZwSSEkvAF%rn)=`8i2mNa=s^Fo}Y(r6;)?pkXIbLB>7 z{6;$Y^a60c>Z^FRX4p}9w%AI!B}_z@(gmB2qzgKtH;^sewUOGIbHU&Wu?Q)iHePgM z@?0d-0;*G*2}efcvu|C{d>lwwWvEhJqt{fg3U!&3f;DMP0?uz`!bPOfj$xQO#91@3 zpEjgVIQxUM&HeE>a0}0!JZpG5cwXXphiB)=%}x9Agz%HoG$_{5p|+;;jVr)Vv1%{; zkj#g5A`!iVHhp#k2fWYd>klbj6&|#PU7^>Bfc^d6ZGoRI&~hifC73vFXakW}ddCTx z=~VHWb6~7>XT$f|uJmg@DL&?MUi#vR9r?d<3NDqP)daN?Bywl6v>6$H+Shgxd9f|| zcPx}&V!S5=d2OR>()U%nS5v~%-pzN>pGg@*MsM+!>ch6~V1Z*75=cLyce>+_hq@id zO}0Ab!nysS>S~Nq8ak`dtM@u3 z(LJUUIi?2>0*M*@ow|QSD77<<__O&#o}KQvS)-n_oW@0m1)N4? z$LR%(U^LToZOjeT5zd@^btQ^Iok;Wd)9&Omu-Qk^rzr zM*{FzUyL8b(h>U^epkJFInBj{v_ksO8;ADrT;>MWQJK+K&vLFyQY@o_e>3s)H_;q%u-d z?mo{2OH$sBNI@Ywo~CdnQ~?Lu5jvXxMwT&;tx3yO$L2}jWY$NvVp-=o#3)T6fHihWo>ta9OR^mhho83tynxD`tLV zEaBX65n%+tOGQ^@1G|TdMot0`qpSVEhDn3?x|il`CDQL#S59oXcA5Jxvw`j;)OJz& zEcEA`FpTKTb=E)6hT-^0fSumcoU6jTmBlgI<-Lj{VX6b0D`qQnMHU=E$kD=}>4ct_ z8#+>z-8WXs++lYgR9j5 zdF>E1{Adt-^r6}%M0?&*RGSwzv6EK;wpL(vHVVsBYS~(85@xoIFK8M8&Y{I5vpq`}d|RnN{?KLFP=(aX{9M6js|>OXszO}3+3HmikyXd-P@0OF zmGjPa;@W=5LZpN$Rl7Ta`RZOcr6&K?Sp4e3Jg4p9df&XF=0b{yZB?Fr;wzO^`xyECDIZsm^dyglHj_-?ln~ zvZbcBk%$U@cCg9k_sYkX(rFUpkx^?dhu($C$E|lGz`W8GO)z zz6cYRlVI!{ZW}1={QC5jy(DyENp1W>KmJTInPrL-cPS3hZBNS){aLn}pZA1HwnbS=yrRCQe6(Y&q%KYGK(|Y&cqh^EVtolz#=p z_j*B8d<8^R79tbsg3n6~+r)o)$K8Qvq~NB_o*S-wQtQ@P`=8g%mP6Q-;~`7?4JP^J za`luo!93|1pQN{rXt79hgxE@Vm-OGWrZYPC9i(q==IVs7OSE$bvv2gUZwQPzxSCXK zsNG>91;i`+T!SBwP>V@~tfSjx+NFI1yE@Qou#Y9)u*>0CWFCDsumMhqQfriQ0YAlI zkpUc;3kbc2s(9yaU`jQJc!{EL{>Q+WImtl-GugcSr;%1Q+^|wu2bdbQ?Ld`jz#4A2 z&5xKxL`QDKy?#U^5r6E7AdmS%k}864A=n7Q1BBU5Q{!VC7*UAOk4)>sZ@@}M42i3C zLvQmzf2t?2gj;mI@<3M6kB`uy0@dlh8nCef z5)rE$)&_vv-SnN9yB~sh=*&Q~1!n$+KQ0mY)0rQM3rLfHXd59$>>i#-vL5+UsDlWR zywEpHHN(g8V-a319=^n1tTZ|M`~|h_78A#O6IOf$T3-TqFf~FKsHm*HMJy5en@Nl% z*H{FVc}M~-jDOgN{a&VT(>FRoeA(8|Z|sB4vYT~_rFCqCwGV){Ba zwYRm4AeB9^>bgF8#w(>0-2zkT-B+y%zP%x|E$!oh~@efLxMn=|_0jf4m7Xurf zYd0VJ1yUKrW7EBtFNQP>hFM4!or{*#yX14bz*aGGU*Iz$)-aJ<%X(I5E75T$4wW?L zHWF*@8Ae1Yo+d@kYK;7e8n?})|HX`#M?in~h19L87KEK?R8#16TqF~fZZ++fL`be2 zjE@n<yVP^OyBt!fA+fC@D2(nQvtb(!1ONzbrtq! z#vXm~dm@~`UhsH!>gr6tF`tC2mQw`LpVcJKlZ9X8f%*CtZ(wB9ni$5&uk;eay*2Z5 zt@O5-qyxPz4PW5$P^QoIk+to#1#ZTh_D)~vWU9z4v;zH*Opv!JE*bh0yF-0;^GOvh zC!69XM-`r8KKtYqr#V=AfpC#LGvPdRne2vYU;~;@M#=27UM(^ZuE=MmcTquex&y4M z<|DN4ej42_)MuK)EDM{xE4a=+jha;?@{@fPZDxVYR%I!_%>)_0MeP~}WK1g#%C^GdQ+z{tWa?BQPA{wB z`vLt>r{a4d&I9z7ED*~c)P6=}s`4~lGJ(5Rj86E52z#q5KWCs>cL_3`+2zf9yRW~P zM^WNUNDh4>a>>Sp#qPsF|T;!51`st`@ zT!5OhHh+CB((3%@BOQ4*WuI(H-V6*1&YHx~C&=T8j=m;~~H(>|Es-)UzPv=(LV$JNBDjI zNBDvNBmAKM5x(eug6GK1SHXX&|0B$Rcz2bvggfN$tO;-ZU={ShFeS;4w10HkWHAoA zF&!ITr2bkxAwtYMga&Wq8y?|V`Bg3TPQl+8>*{nVA3tFOaDwrm(xcyD(Mz84Ea}OA z&?fKuPvG(*QE*!32_>rhX!dI|tDnML&mU}5h}k935{vhXf+et4*8fsY*QXs3Olab> z-l-qK^I$MlSK{4tvX~wy10uhGq_D~uf0^akvrgGF6Pu}#Xm^z%eUQO8C3)AReF~U8 z@O1uDP(X9Qr+EOZ@?<`Bm^WO&i?*&YrY6NE|?TT$blTd1xDUDzD?R|Vc^4oCVrI`5i8IXqOR zDoR%``-lm&TQlY|R(b=9Bg5To?>C)>MIHKe?FK3WSBSvZxI_ElRybMEPRWjGQ*eA^ zgqVd6gdiL@nho0?+P;q+>Ut_xZ0gi*t}k@jc3Fe4%>W?WfX~ds6NGdip<4VJQ2)?h z{j{KojZ&^f3hU(!`#ITioJ42Y6vBa=e^DfbGV%RLb7l&^2gE)b+15Q#0OiN{J zyiS6Z!}i*k?@Nt43sqw^*}^(=x(Y|boi<%ylYLpO{_|`KHtL0oGac~z&1Wn1teHwZ z)48VIFo-M7asJQ`fHxy9HWf2W+<0F~a^6y{N`{{d-i@mY z>%Xw+B5WXH(ECXI^OD&gki~e_7eCLOo9fN+!jKYOW=hC$hEi87vUTXQnhU=D!Qu^S zL9TBUd0WVDXVYp=(XAw-*ZNUTFMjw|LZ2j5WNV661WARaLnSy1{U;M{Yz)paC+*5D ze;mpD$kH~a?Q%o3LQDM|N@MO>B>)B9IdLd$01%h#c&ty;*D34*RUuh4d6DGpG6F}; zYAjMn=H2rz#(Ee@B*RFK{hV{%B8s7-NW{)5Su`!1EZu?h5V^5dbZ&@vE8_k`^aG6@ z;u&qJK;&}g`tJxql4GvJHJ{yY$f?!!)dp>#HflCfAv*c&u0jfWB&4ydq9;X?{{_-g zlQNxc9zqgqOyb=~J4^2*Jce)4rCul4lD|3g`WeZB6VVaYR&JkDR9$hHZ!z47#c*Y3 zbEwY!Jl%ifW0{S$4c!!S9))l@F^;LMS+KjOW+bc!m#~_13^gMW$AH_N1|xgood8%bwPzxZbb&EbfPDarlqF%B5i%FcaTVeP>7nq*a9x!JGW zzNmj~^7K9CZrPAqz#5XA*jHKt$8Qm55mJZ{Sz9{&b|{3+m|xZg-fUU@{zF+4lBY0k z=j<^#SH85UIJV_oYVZAXGL@FMI-PeQd+!+qtT6$5IG%E;DfOAi=)v}&$ou&kQ0;;r z1tu?*-k!ltC)xQB%qqzm#b`rLg_XOYK2HLlP?fCz8}O3{J&_@h0RBLXbYC zPP8GMtWo8;LMQ&&kY>j_>kpr29@T1+k_3pV_U*~r1pm_JO?4)d4uIGDnyEJtXO!5t zfPI>NkD7YS&DP`Vwp4EeCMUMbICif(Qht#w-<$vYpy*SBbjv^DO^1B?QP6w}a=z1U z4;0u;!a*j>H|h=fKQKD<)6a)8RfgU4dr+09lKmtZ8DTnmCbN5l_s_Gw$k-~cx3HY~ zQRS6WAUvGhu2j>VA?!c!w_r6|2l;dlSzEhzi*jK%o|jrgE=H1A)E`OWaF2$W@QZBj z3`~h~cL#Hi)#s2~SR^q7Kj|a1l19rm??izkb_pbPp=JZ1DeSjDTicDML`}fgHG-zJ zoxnxiQ)`mf7Q`xdI?JVO0rO?rFAJnJ41YfkVvKp0m%cKm3x&xaIjeO{ZDHQlLx=cejC)o2++4xRDs-j2Wb&1=D-E?QR|!f@`d9dTvL zF2`A_Ns|dbgVqX~63{#XbS=s-6TSf;?IZ1Sqf1us|Dq=L)=UzN zX0k6>lv=IY^fzq)#L63Wri@Saqz^$lSX$V;kIFc0HGVoc?lIWA=uT;{%#O56 z=ECAyA`}qhjha-;lgn`w7Bhy6_(O?jt*Ubix-u(TwD#eBtTumVY~)T~ia6%y$HACX zYUNwL-!2U0rjhL3JuFhmq6Vf(j$~GRMJF+qhr$BWX}tr7kiD%iof}-^w=(owAhVX3 zsksFDI2T%OVkGdlIl*mRK=g_i*|MF=9``2y$?&V^yr;R7%75 zsmFVUF9m0ABU`Yk4uYP=CM#WDLX5=$e}fGgA|CfLud!SDX8V0 z${c2v2+K|Nfd3tcEO-YWZqX&m*O!?00RBD~@aHz)i-SskIp2l3;8&P@2Yh9|XXOH( zZu1?ke6=EcIo}akaJOhJ45atTKXx+-+s#fW)KBSU39F7uWynE_4|ZbnP5?XlHlwN! zz42KAY<}o@7T&DrC*X9?&{nyjXbo|xKWQ+8NGF*6^RT-}XAx;t*WQS<2+qrjXcqF8 z{uWp?l9yiiT+me9gGbv_f5A9oFH~;5Lt*$QrKDa&JLN~5M7_v!bZCEC4r7O*enZh% z7*DRgX(;-L7gF~nbufIQsd9xIdViNeB-1x`vj|qAC^mShd9V479RZYhax8iIh76mVhxvvmbL&eEhLbmfDC^pXsH*Fw*SbyfH(;~O?K?qT`_12zEa0pRssUW?u~p=)_P z!-lr=dT!c;ZsfJvhIaD$T|D}d3|(aCR2#aD*USEDLOouqZ0L4gkGS82qM{9rwV|rn z4QotjA+M*{&|+R6z$*y(mhkH5TgvP6wq|9#R#@0_UT^0ZIJl8=Vlg*5qZD!F`})7 z?2T$MQMW=n`^03t!_Ux_5)n2`pJA$xlF?o(!49)A>V9g3TT!tyGS8RhvB~UL^){a% z_SOiEW}t_J&n@*%xWH;4BSiN}T}`WHS-D#E!2D*F`F&`_N`7nHMU5EzA)`AFxm|?* zzo`@qW8*nD6058)oL6aXAWfcinlZMFzrp@^TqzgxA^tiG%;(NBrE+71x~y*`hgvQq zuk%ZoqiHN{H+y&CmB_yb?lATIEdLY&_Eg5b7jw;c%h}}BAO3k}$M|wxJB|Ot9duT* z=y--QHQY)WS#?q>m?tzh4uVlj0 zUbgCm>*@bYxDeOc5+8rT{8EUHspCVFwwJzwh&^jGFja z=bNmXPvZVAbbT&cx7`yq#I&wxU->(+mZ6@u*ptE?(DwSgG!d#z25IaOEt zAkQ&0_SrMs<4>y@9gQ^&BD3kVWf6kFZy=VFYY9Hm4l~1zqFL8NpBx z)g8lwX)qc#Vw8Fi z<}VXDKezCq_|;{3O?B=BZjd`0G{90V{0DYP5;?sEV(U26aopEJAv#uRl>TJtR5KW% zgH7!Nt-mpAyy`_m1^W`igsVnEu0tlBhDJm&BR^i(+vY#rd{BJMFW5r#bK0Jwl=zt2 z_~0n%dOpZ_y*$ZbqT*k5)E!wnSP_Xug~lp4Sy$|>IL7zPB8N9O0dFQjBLy+JW{e!j zgq`8$$MU;$B3T2i zR-d#YR5hfQLtP@VF*n(wzF_6}!x~1O+n5LqWUYbayxm2Z{e$Go=1Qi^M?A^$u}X>E z(7Inz1;bDr$JM0C`J5zn>fDb|jd7GRzH5e){7{9N!+^;sZq(6TdJe6}vDdHd}jg8jj7cuN^Nlq@=vckDLI?%*zJ+G7QT1C&sl+(hs3Qt_ZToSfZ( z)NeD0&eh4zdc;iFInkkw6UaFSykEipSE{Lc5G>w-#oVLNHoVuNKZa(hQ z@=b5T=jhMCfukFj31+P=fdGF{&=(r<<}$$c^eUmQ-$&m-?<4pt|GC{QnD0fjo4o4;F7& z#gFJfZ-0x0k{`f~y#mwv9x4p8hiB`x4Z$_cz1MZ0A=qcWZ`jr!atp;?Y^yUXhixT} zw5`Za{DX}6d*xaZ^1ineLG%Gsf%s2N-kZIJl&-yX0VuK)yXl_u_qaxU+N)Sb~GSo2uSrcrdHVY?BEzOIqvq zr1!D&#XH*|On0h6VSfBr^B%e?O;_YjE4nGAJB4G;BM^nqdBg`B)$WwsWjXf`PX888Qyt+9r|=@;F>X0(4SZ-`@Uq8m9i`D585Sg{)2jd zoE5Aq;$|X7Z5@4x^J5gJPx}tr27BLZw0Sn0bhe4t^6o90Wg{-I5nfr7{jN3Np`TJf z%snJnvHb-$PAIBFL%%|E1(Gbf0*_LDPq3{h4VaSgWkv%Y+IAOE3Ebbqx#YDz=Ohqn z!*iZqC|NJ8E&|R|Aw;y3@@g7!gzz{R$sr(&c!XS>YN(=p-uwfM@w9vTp!l|HpB})2X@jOvK&KZVVy^-!f&_9aVLHrNW8hbP z75uhTVE~_Dp4tv>7_^f78^0R9ODnB!o+05{|3hK76<{I=9p z0leH&sXENLK`(oU|442-vxDlT&J5sU1}@9r_hWt)sQQ0;Cuf4?B>hKf{~&3RpR_7# z|IKhP`3ky7e z9QYuXc|2D6;liz{ADg5mmml)#rnBkU;jVkVAeKMa`}XCg!5@IcHrZRLX+hDGO?nj^ zbWg#Jdv%Xosh9%3?!yN48|)Um3`fdN{^bIGYpLkce}Ve!0b<#z26Lw~^)8KK6x86? zcy-e~H7uR+r5c(`hz0u>lWgXSV16;N&eC=%mI*KJ(DKw&ZAzr{0V)Z4+F=d?mi;+UZ7ZHP0`e`RCwHG`U}&ou`>K!;?;FG2BTs;i-<>ue{yiyJcaZALkk6S4pC&cUdaSBBi zgPD?}&Su`6)G2Ku?t{yNE9^2Oo@UunpS}`}?md-b)v1UlE~qP`MO7Z5Go}G@sBA z&V0_HjGU*OGbfjVuX-vP_qNrNsj^#br(;s(I;6=CZp(TnQr=G@^e0+Qt&`&J;)Hjr zm0{kaS9@pl_fJ&GnX#p+#SKHcYfaXrP?ZBqo!H$Lh(cjg?qc8XGEgYc%-eR9Ixw#N~nB9y50o`u-uhnpT$jm_bDL zqCT!7;;U0_5lafEcFX@4|F_!JSnVjkihiIWZ>n0<)9tRckaO*lD->g_gAy{4PB!fH zS)u)bCO0%5KE78)HBr}yj|&9%f0?GzxK~r`r?9uk*Y&b@3hO%F>PjMN3H}<>qs!xI z;c6`N!02MLpm+B3i>VNE6fU0*|HPs3yg69U(~7AxtQC4~K91MS3GAYR(#C0^`7Rj+ zQt|P;&j+GOBVPdt0`4H7CJfL<+#B4=Kv-oMRsqlg*V?CQ9V7TL=@}xe{ER5-kF;~0 zaDBKWVx{qcZ`?75 zlgS2vzW?sc2hm#HnQieTv!SC4_gXTJeQzyD=MC{x@ zfzik`B9WFo58YrO9;4CT$}fXgKaJi>VWk4KcDW`UF=_5^vwfmy+qUo)0tG1Dz`ywQ zUY5HP<0}*^IfBD3{uvP~OMq-F)Op_sT%5KcGwj`;2|1BbZ}q3!af5h-opd?T+x_7D zjB@~Sro!%?amo~wDcSL~9;@quS*I3;n}hRBhsKUMBB*8E@Gma8MXX>GWhYQ=Z{U|t zF-hvM{7nfpuLZ{K)~kg6hEUfDK`!z_P;3*QYpM4RUha1RudU=}w>#uwbDoC}I@IVQ z6JZ=lLde#}z%%@&zGW9;7B8loPgo&Zmh?-)}=dEQbg7AR`tMhmFw1 zMact3JQm@Hh0U00)(}f|)fQ8E?>NGO+n%{afIL6a`AO-TnnQL}Nvo;gN5M%{v`!*B z(N=IMRU~Jcub0$vJCC~Wv2IvSFf>HUXrhjUv|3I(C1iUQp6sYL5QltC7UQ*FEaI-u zOvgv4PGBMD4LCfPHJ{pz;Ckvfusi8l9jYm2ac1ON2=FfV3Foss( z$r8g>gSRnzgOSo)2b-zwQ$PIKtFe)bOd$_8FkluhL$_&el5lgr?dDT`w$1!Ux#U{j zK{jd|EFNUkN}*lHYWeuw)%!+=)CqIiKG%Z&_zF#1L1F3s@0qsdjjP^GboAFk6aMG0 z5W2^^dOvibzu5q@DN(HCd(^1;B44pUmqg-$dIaf>3UPnaS}7eji0(!uIGe*J@%=19 zCGx-2<4o?=uWPu3yK~H)FZpl5B)xTwV881ULzKNv0V^jPu#`h6GVA!TVZJUeSRSgN zOv(PI6b$oAyM@b<2$w3s375Ct4lt7n`F^oB9JA7XxZY=}X$R%B6^%Xwy8T5z^)>Xs zm`JT?uARt{r%+?+zKn)z#*LMu!bIlbq3Bg*48a);Y8v_H^%SVxtl10E|8QkNBHcJY z9iK~$XPit2uWCp)F6013FzxC}SSZn`C=+kvDkmX$1`n^IPfcoCaJ2Fp&zv+3;-6tN zR3@^@4pPe@QYLaBd5bGgWuL|-t!7^406bo+AH_5gFpmHef3+H^Q798%Tr;69h-{4_ zvwtGW{pXIhDSq2p?kdfmVB<$w!x`0591vq(0yDi|TTuKyIh3WUcu;a%ZyM*lnoGO3 z;iA???}Iuy#*|%hhBLggT5W(f&Zoosp%lgeWLsajSz{Z=9STcT87fO>E2}Jfv!Q6b zBPilDD?*E*zphaBUcA*?Gb{UtqhWm8VdN-3i+x)5r)~S-uzRoEs>$*65u>OwfT@{n z5w477$F!vy!~1^C*s>mj2IIrj%((+vqHHiUGoBbG0CiPTVdnU(;zE%<5IH*PQ{?V@m6TnM!SHSsC!QWxX`47~3q+EKdUT)9&kdP&fJN08l zb{$84AH9m#?_6#4Rvn!P_3j8QvVdJpoSWXd2C$lPl!ZXocMffXpl7+T8Sc?X}pX^hIWmn-wFSiwL&AO_UziT&Meq64&>>A z6G`=%^X?5cjWuT=3kIry+9%z3h!2$MMwZpyd|IScJ=MD=o}M(1_c<)~&gH$8uqhz0 z$&D+91B2(i#(qU|!3FJ*z+)4J$vGCH%Y0qY`W@6~?8w_2ZQ8!5@r$oS4?{-}t(zZNmsM$f+5 zrT-^@8&3#~i@y5`9hE70{ZqJksfJ5adBS@S_rW_Gx96V6I&9Cq_<&UVw{_|G^wcKq z6nK)6h>gw_eaTo?3tV4J-_`5*jyDd^1o7#N*J#|suYUX*;f2N41TD5M?_^hc1*QCF zu>A#g_kRWZ3k$Zc%j>*GB$xPfWl5>78z_ziCPc3qka!f&?f%y_unyjcJwivay zqITJ0)C{7`|91RVpnxHh=_YQmMqT)&q1>U(P?n}SRAzcFd}$$YKrS|)Jc28w zW_L<@T#cL2w+rS!Lj1QL{fZso)&4Di!5)1t<72Nf`20)%s%_6m0}0(4A{cUcdy&51 zK!Y1=qPKS77<(=i$;bGRhN{rQT4a0Dn@c;t?{8K6!qrGL6IxsEz#Fd0G9{V0Dh?Zl z0m;*QY9*Fq2o6BfhorP|n%7EDc{qja#tT;?C~bV87}fLn32i~N58TUv$^9grqP%-7 zOdh=LoBl;l^8AHo05Y&AOFzigu2X0=PE=q;dIS}#u>afH1ahMtDPb~f63u)@E^rgn3_4g z4-v^B$WAZUa|`2X=zo3#qGzqJione#o((^vk=J5z-;iniG{IqGM)K#ZB!bh%x^gIy zn)!JoI$MW6;FX^OFvSq7fg2e`QVtjsr}^(^2GjVd7oU=j57ESHnD(~S+$(JRqxO(m zkUK2NDdEk)gPkeT*Z2o3!W&bbs6O->w$S8{lqtr5O7lS5_6G7sqjiZ#{q`?JL9`$< zZi;Nc#LFLk24x7w00EEMVRCh)#I$G~$ZFh~VSEkHm>b4F^cttObU&?+Y#o;1R z?6|JL*i?302`@77rErvd(>+SC;actgVIflDhOYCTqV3$E0qo(x<`;qc@KGooVzyZh zu|^XJL*9sD#RlYcyV-qtBmVWGQs7B`B$lm#0&-f0Fl5Bs?amasaSCF8zvpnK{Lf$2 z;{j?O?bp;M9DC1d?h8n~M^{4$?~TH`Ks8m>fBfss$-xPdy=WvaPnv1e!$W5{d2)f~ zS16x3jb!*1gUt$|y621P|?TB_49z``3=sqnZr)EY- ztH#3kL}u8pPcUtXQ>g?pXToXZ896ltMcVSkdl{MVmY=|sbp#ZLDVzBVO=-WMQMB;cC%v2Q=1dhW?FnSf9%>swKu%6bw$J#F@{@ zRE4`_1|mquHx04C^_($-W_enQvqOkcqh?Ucd8#E+^su#YYt+<0ps>a^=NmOX2wrF& zI|2tu3Hn+MHX$K{$)y@ho|3&Q>xBii7Pq^KDEE_`MABg1XIp3~KF>8m=Ah$PG&Rl< zOui9wsIMB46k4dNBzUV5%xl;IuBuLh6(Txc)t9|m;fPQG)UAdYs&Lu?uj-=YyAVHgfY7&q7TAPiUGe8qa!_rDRw z2|-OaG4qHp0@V0t@1hDf&ZEMv-iIpm3T$QE<-RYK6u8YSP(zN>0I0D_76tV(&j&K+a9-O8}r^Ms7_^`L1XR+GA}@xOVp(x31kiX(><1zdF!0Xo6YyG_DW(=9!(XK?`8_ z{^Rc58uu&SX&haqCg2M~t)SFgtdxeRiwUz$;Kyj$BAu$RIC8nTxl zY;*Qf6x8(htTROA>6tiV7!QpD%G!~VdA6Zg?p?tCn~NkKmqWpA<+qs#o; zWc%(SY=*m%d`@=xSjkpq=wSpiUX=+qDC#-|o31jM)eQGfRvwvgWlV&VQ&m@T#Y*fa z&min>!+yvTEuE8lXw@kR&_4&)0X7L`kEPq9r?+#LKpA;P$&-=9^1N}eH;ztfoReom zpK+vE-SY{}VD65#{YbNiH~68Ih?uIif6p&^iMB_3dxVRZX;tbR*Qk!-RFAux^arcmdLB?Gh7JC%r-ls8V5ESwPN0e+Eu0-&LE@jQRAObg2?fjRmEmDQhJS_hm^N2A;m1Fq z4|lQ0H*s{oC=1&wYMAur`qbAyyd-*JX8XSpSmf6qh-RigCBRAJop^kV-cX`E4#1cF zu(wB8yZr|5(*|!iI)O7&$fVVy%91;o-_F(k{s+qtH#%FUIM}XKXz-zGjNJDOmLnW2 zn2JYPC?Hum?1BKC^whkt$5o05U!8ze#>CE-V?q7Hjd2Zqk+9Q-jI9+s}2 zEJ-u5TlIqkQf{22hEDL}^R4w<^hP;yNS{Zw>)>X4Fqpn~I~?Pf`gcpN{Y5*ac*DI<62zR>M{KJU4Sf;G@LR zO+1XLU6m1jj0l7CwTd=bfmI^?OkHZnmJxrRe@fyHHK|Zmna0Z8^Zte3(U2BLCH1}S zA!Fi@_lMv#-*>k!w0gLGls{)5Ca$|WU>ltF52`w^N1Utb0!j|6+I2~HN*8;4VeF~B zWBV7xYSndkF19VTv8qJd#Kgo z+Fj;R&F!~%XFZ)rpT*Tn>#iQkigDQM`F$7c@$Yu566;{fc7R;&p&e5XznJ%%Nro2t z?W$a6P^;~u-i#wCb`hxJqPX{geD7WY9vU(;ihJ76M!xu?Ni>hdy%o;<25$*eSv(B| z7$uH-#LnvOn>H{0;%jR;^ir?qd4_d5_+P+cZF&GI zi`w+4{$9L`pJJ3G{A?djPv68-+Q{B<_LsCLs9xT_ly=?uq~Z7T|4_dvRwq}#;F9}E zmTC+26Y~n|*Wc6;Vg_Qcs-FCPqr$nuKlTCF%>M0r!U$>@R+_3Q3pP? z>QfyRTq}0z_Y>aCHQ9gYPnUdp``nfm`l}`&i&v>1+cu|i1j+xA^7dV#nIp>_nd9rfO zlpIHrpa2ZWHY-B(&p=!4KlpQO(Dnh<2P@qqHnX)k?&exT%LsK6V%+;X%#Ad%tU+6~ z`a`F|X}6wRDbBUZs_lj*utUVAK5R8MrLnL5i0;!XIuGA4v{+}LrKCxHtcm}jrH%`D z&*Sqv82s`Vh)OKbwr{$6H(k!dh-hSkDKl<+zvWIh#NQPmpw}LMe5U^&DUQ}ikkL%t zthsY)0~*P4#&}u`z{dIhf7Lku#5R)NiDL`g)<7n;-ZybChlv(Eqt|%fmijT!(O~7~ zl~FHF0~QrseSTf06~f9bDOyMBWt+B}T)X$PljA&f1D9bRfG{*Dy`<=EltKrteg-#>t>Vh{Iz9-)vetQsGsGQ=Rqb9TxL)#Dhi>z7m!C9ATH$z_QvE27I zr1M)2F@>ZLRfBx(mYEAC65o(6eh;?*%5?fEay6p0F?}2y4AhtW!i-EqJ_Z z@aQU%a%^X+tXfGx$0kxMDop)7!FIKrzBx>&jmYqvKxgBG$S3hZ8=oj=e zol~?sV_|5?%NZ|6QVH6I*mmGgY{?~#7Wh!vwk7mAQOujUCts9AG(}Je{Og_BbG;?^ zU|{E`olHT>Gr!()Z2Ebk3)WlA<_iJRFlnD@!$j40hrFcd?zs}14;ItPzsE}K7ld9) zsLcPyJ7*g(^ZAz5fviyWYs}cU7(ym?ZI06bWPCL4{6W*U z0dN;O&^2l7AC`*^c_6<5)&FrZ0j{{Od#_uyj(Nly{|s0WhoFX1 zPxI5pZc>@6GcMOl$TTr&9pyFVVx7M<)NvWyM&-VY&tQA&fj=mBZEy9=eYOAf9hUpE zOg3TeOEvL$kxML1Z{9tDP)BXlV;+#;t|{!CTc(Fycy(` zn?u$J@)(s)-?7B38+xiqD(X(xiO#T*9;{RE=DU zbzH)5s?_(glc|FPZGmzU-#E%C`qC)p^dK88Tt{OOfgD)t{V5mQkGYtR9uX1{do%AM z;Vkt(pvj#tC<@D&p(jXusEsUp6V)Nu@V7@0fYOyuNEq>I)q zW6|tAU<(2)Emu!t={s&pBLkw1($(E{M=`7S~|i#ZxmFKfye< zz8mY#9!LJUP?e6H50S|I5KSznY)}#FbC{&vH=I)(G$@}P%5I*NFn|xfQAGHH|L-fl z%E6UWD=N=iZu@rOKeMlv_{Q;-)W=QW{i7d{MAJIspU*2Pm)r||jYOs>jPuGW$QDT$ z3Aw|d_{})c66pTo{2k9IU5+3WmmhsYM_+5hE7^7Cc8Y}s|*mZ zSv}*0&{Zx^e5Liub4Vf7cU+!7Q=T^U_paw>oP*C}O6Toq`*xe<`9dMoQ+&|&)3;jB zy9=S`xjeVuEYInMP(N~c?xZ}CDmU+=`aZhcNarCfZ>hM0^~<>O>E^$rtyI6z$4$G|vS*DISI8pKx;F zSMy7;=9kiOx|U4~N$+FX&x7Z33PpXr-W*Tkup+)Gv@vQq1kKE*1$~ln+Ez{5kih=& z5G~!BHb+7Rc}*DFjQ_Vvj@wmn+|Dgyxjd2mLheY9?l>TkTNT}LR4GsS9>$(qfJua~UUu+1iFc?IPnvS^pYTi04Oy=5@OzLLv~*tg8Z-oD+0 z&}|zsqxxs7Anw-Trx?U72Ei(SX*Wb)J89lJe0p^DzbTGG#Wf;+k zF!~-BjiKzQgVDaOcg1|y-}xxFWrtC9E-U?Y>+ocB_V0Kh^(*YO8Pa}WsiU*+RJb9o z(Fb`Ox-0wi143SCwzp2}ht$jsMbWl1ToQq=13clCg6f+LIz%<@lR1$VUzZ-`xcqJ; zk*z_~{-Lb_S@p@TS5NYWV=CKt^2U+rsG$k(l;Z5tAdXc?Rfdr%?;@WXW-ChE zC|fltPE{%OA8_F(D4c*YnovQUe}fA@7)9%*T0GPza?jOI(v>Wwx_5EztE^O5$F$i} zt>lY+`xRx!y7!fO=V-PTGS$=;vHW-}^tDx3Ah~}E6va4?IN2(|=Y3hhUeA8)EAfoy zxt&L0;Uj&VQW9hvaWZMN3g%wD=I7rs=HGyFnz_ynvT4-mt;Y3@?2NN;EjRyGjWyD{zR>7B zqmHZhUd=Vt@V0>t_Sj2~w(SBNNPWbWfu+36`l!!N2o!A_X>;l$u7Y|`FJBV-m|>xx z@s~_&4VpLWddLM%L+_?%b{5dV?ac7qbimQ$TtUJRG5i7Un^s z!XX|CP2~tp2u^>&sYStY7Dqn?r&;7wc^-QJf$;mslc|*LGf!nt57t%*Sy0V?*rkk( znahjo(k+W?GvgVo@1wkGO-y=Kr@Al`^R zYH%R%b?@fi^QNz}j3EGSRj+Ye(d&d!#oD&KYI0AY@dsv3wC0Y07~UVl-bjv3QZ)D1h1FR*XR5 zh?4-D zybbCC@^~wimU=GX4e>wtL|g*u)09bt6WN`t!bmNZR^c%?Ld-)i20DbS%4T=e!r-9g z?A_=Y;tz9G$YZ#P$zU++7R2SVIi3_yid+L~Ft>N0q--Vrc8*>UOKd zKt~MciZ*)^!wnyY#6Yp}c5rJo7Io*w)?|NMDyjgan=a$fRL*g6wik^LL$WQ*eoR z0XDh^O?Iz$G-BrfK~6oiEkqamsO(SJt=IgY+KX?DXy+g#Dv zr&wHM?H$tVux}hHc4oFRQWF&XBr)N!qjh%VDDsd6H8Wg@zl#sb{$@9ZLmR`+b2J`?0g>@kB%VyfX4chKnU$`I(!ylf$- z1p=BM0{zhe@gKYz3XGx6TSb7kF1iR*;XyrnNeG|W3Ot4|h49CR@Mjgm=lSKlIjY2N zAyCzpfqVgNWb9g-cLK;rPuCl`s=}JbJK9?d6clvEPkPXv$dIcLjQEZtDxgT{2fP@i zq~lH2fq(==BU$|2m}h&d(B)d4|Hcck;H*c# zHdPdK>fHuRxr=$}QSM$rxqEe&YlRtA28C5+&kPHj;Rstt3uXL*{@tGWS;F2Y*0P)gkQ6=wknJ zZspL%v2Cp)rukFT%5W1+b)^qLkPkg1ZlD0%V_>A-_WNRC32|87UxFiv-;h`KP9lQD zEYIt`IQ8TL1`hDIDmk0qElR2*8!@)WSqf~tCER`g_~QKT`|ZtaKcz-I*A&_ht>Ud~ z0^DpmFLq?3#{6d=YZoZ!e$H>*{hSYy9bn_Jf(R7+x;oE9KCR*A5bc&n+1vif_WY!0 z6aCt0$+BWe3{L|wSs4UN6WgZiddAq@U#O9Ao=43P|GdBCg~?my(YoxNs0<7Or>_obfU$yD~%_>`jAj7!oYI>>y3_+ zZ%+YrVvR`#p0ziRV`hz}>_`g8hq&P|HG6Pa|Ixfq|3_|e^`D`Er)9WqMwM%_8H|9% zOxawv)@fHdkHh97f2H$`X-*c-d?sQ9TH+IPU+XL+s-mLHP+pb1$h|1pQ2+#$kfzVc zsq+>J0l$Ft8Ix$|#m(jjK292AbqWr(AwdX|_O+ZS+po+T!pS~Oank`4AMm)t! z-xjbG$<7mXznZ_oH=8?S=wHJqBvlNNJ;GZMtQFMgS6|w0H0CTBx;IAg#@jDKV2qgThe| zP~+x1t}{%%(L=}S#%14Pbv5y_p>rbI@L1~S<|WL-h=fzqbd z&Q`0X|Nb;s1kuX7cKA96jnyu(MNYU7mhb_Mv5Jasg1!=tz6xB)6iapC)P*`~XMe|*!_nE-!-vAv$hnv?qH5Syq>{bRB1}av+6j~c zqt|o9Sv}tTbSO|B7!OngMuHp}(pc#&?i51;*{`U&0WsrsPiEah#CQR%4T1j2%NBAf z6A&keTu!SUkee2}bE*;$t9u9Z(3XL;{>e@Wf!Z8U?}}Fl=y(Hytxn{n7Z3jMoo*gn zB;Z&GeAwo|OACNY1?=8p}NTldZDMCgpT-n3MCu z75T^9t>v@<0^ADotyE6?hB?*oLY)?DHKzx*o>Pr-dIvrQm0x94zMc<9YA&17U(mvIE6 z4#Lvij6Wr!P)7MEd%8faVzh{gl{=AplBTFkBPU-M?@0yyW8c&_OEbH1X|=zDs`?6&sj1&z#G*a{=&j&o;@T3ll6zzFzzwvBW6)8G|XClvy zJdg3b!85j+^G-as^E}BjfP=wD@FaO|=Gpd`NYRlzlX&jqd7tNi5s{*~JooY(cxDXe7^6o(#`Yo)38j9Z%hO7Vy~q1!opmg|}oeaIT`ss8-Y1 zo%=sLB`|2K=xNVZW4?74012TF84jkUoo+KnWruHU%$!_Yn_K0kHFq88eSpeS)@Q2Kll3`}RED*0J zwBSYPZ@iM)a*U-#vc?}|xwKpurluaG{y7IbvJdjAIx%|%^B4!pFCvgq z0=EQ_Z*(q&ysA_!z^17I)^u?oTac^D7DnR30wo%BRvEOeh_N$QIWlEt&W7~Q7`JNJ#P?Sz*nD-czJkK*KR6i}=+)-NPURch1X zutqAFT9H^TYkBRzi4=G$Xw^2>g zvDrysVNt||#EO<;6vAk%*@Lg1SlI&C=Juvhm2wUWPJ_r=en*2+lLv|KlSm&f|759) zs`_x1OnM?Hph+&U3)L;TrO8#l9+?kxA+XneU?ksePdDOmr_hNYKLncS!<<^(qG z(gfqdC7nPJUIoNtAW~jCAQ&Oeu*C)bAXEaXG!bZx%)C%KNHL~X| z_ewqANBNyOXE2dD8O%cu9}A87^+)ET!#}+{aS)P441yNd~zjcW?m3Es-ZQ;f7iDP3|7P_5=1I z6_Y$Lgx%Y-To-_p)T-jN`@mVIWwD@dvL82^dseknFIIMBotIXHYBw$`Z`qG?ytUrT zwW(G8`R-d6ty`XYzP#33m%d3bsTcYZ+=oL(>llSaMfCJTYA6yWtHH=#v#?}kbz6@$ zZ@IA$I>!-5cMrMoQ<++#27u=`qO(8b#UUfw_Ez}LS*b`<0F!5T)M_i7Zald; zk-iv7<^;Ip#Z_Fz#ZQecE={Cs6ZqX^z}A=B1eA;2cfJJgmOb%*4@Hdl8*@N%Q2Nqo zIwDx(h7i=lIf6v$$|iSUsHI^;&W$Q>?09ByJm%z>HY!jbHsK8jiNZ!Pehx3Rya#8R z`e^&_Ms1Y^L%G8SCyT`OqOXLN=E~`w?08f2%pW`?$sonq@}PG7Y_p@&@NyGQqjSlL z8UmH!;TZHJG5-QaVfySQhVwld&dBiB9Fg$uSs>hKD>4_$P%%_C!La?D1o(-xuyA<) zMVUBuF7c4@WB%WR@fMt)0H={(RWeT8`H4zLXWl<_*#3>w2Yr)e3;MJSO|9w^&|GnH zXMa~_jIPqu`fm?^#fAE1a52v~mbWVPe&3pn9sM!?<9uCeRbR)+y7pk++cmZR8~3@O ze@ltEo)J>tBtMdD6K%Um6!D*D2$lhL4(0eSPObk+BNKC^x@f$en_nGv1Gtc= zn3jZOsIfm5ut9{yDRL6^x6IWlV|;y{)XO_LS*5uY4Pj=%)c1Dw(jDNgn#cwA;+ z%ENH0>X*5^()e94cw36S0Rg{D3%{b|8FHX5qdAAW{@$pXpT~nS*?t`$>6so6VAN`0a)z|>UF<3j49iwbGQWyzahDfy0 z{#<39WSiIVmol|)(dX-SdoH!CUwTZ9f%R)S-t$yz$gtRprtLlT#XiX$CHrpgWZ&rQ zF;K3i8B-euXedC=s2ce#uHlC5TJK-F$9|{Gv|R3`=;Na*Yv*i(6Y{$Bl$zO}MB6^K zh9hxlDPz}Y!?R4>fR&&?i2V(n5KM}Z+jSnkN{q@G;G67R>nG9F4B)+D?%pX3{LR` zUsu2AjtFsv){$ji+t60}qJx z?w`=u7cz2n)NJXEt?U>n4H#MCjT9`A>BU%`v2xhWIFuP`GQ*Fq36f=R06G_II5(h_ zmNS?y%ei-a5ZP?VU2j6DHx}haq>N2Zt#h8&X(%Urp-0}cyv(p?wr{mF?9_rCvCk-GPz!pp{klAvj zD)`UnHK-Xlzy3oTfg^IUb3vyKhUn}|8UMmic$+~m2-xRf&9a|Mfz|6#q{xmkUa>je zP9r?z&IR_X!KClK!E9Rc+aqaPY=N2mnZ&9qOJAOD9sebV$kpqKNwL#Um&h7ftO)O_ z*PF1uWrLJE*{|TL+R|uS5naJ5pfr$Ps;5`A43%sYt{n#^xXy(|VDEYVe_Bi`ovkM& zjWB8hnL+vEQTsnFb9a*4gkpvY3)18H9g8geincvivh+=)2f9J4s*R~arZLG2PA-|| zi3N0#PSe8~mw=w`<;mWB2SF(+RqJZ5rtmNC2()ST$bmJPo8FCP@k4#sMYvH_2@*)VL=JwT?u=uggt`RP{=NQC}dD~A(vYr zqsQlB$5{#IA!VPJAh!x>E4ItUDqL^{-Bjmw*fP`laCk@WF5Eb&unU{{%EkU{VPO|G z@D-4!=^LbZ2xU`spj*!}2p+|!28z@{RMsrLBfHw$DXc!qkgR~X$w9n&xpEv^24flk z2=q#RXR;_2nEXv~j0=V6`H;n|kIOWIyvXC&sh&MIt}D3TG_blI z1s$QG99u-^-}EANfO+8y7%tQBMX;OTx(KeIV2)!}GT)lc#VrBN1l$Ugb8brjY6$M3 zU~^9jS|zC=pp^jUzKwuN0_GE7DqQ1g4e4_h_;;>z3fz-T+vPm#_^1Azxi{W6AbB)L z&!%50zbvN?6-f=$Z6{lD^-~J~tD4pRlG9Mae=SuAl3+0i(nT3!HU6h$>Ty0>>}cJr z##Sm?gFn<}Aw0kE-j2d3=kWXJY!=>JyzGCD{~(SM+`-P{0!qM`!~fyw+)1zrdXLJ- z#sl}@(x4+d-SjVw^Zku@xyCo+t){JtFTg`)n;fly1MaDqBUE_tx%^b%xsjjpT}Ol$*S#3s_jHC4s&Nt?t|Xu|1@e4j-<f2=`?En> zi>$Xur>#-qpA+wdvJDNdoEzpb>4H@ z^QcWd*PkU@bm@Aq9(nEJ{zY}sk;If#kJH50X}Dm0tu~eU|1zu0$Zr5s5cJJXIIHf4 z^dxq>$FjHm3v~xSbNESku9~LwONm_3kDVp{5}kd!eIK zx6D^Q8RLJtqd^n>A@qNb_3&wIR?>&tcbLiwd=zz^$$jPI9ZKvtqdk|6LRiIqvvRf; zhae*#Oe<){2F4}LDC-*URFcdXk(C0-G%gT<*QL){7%Gz^54(uPAu~4X6}C&8&H5#h zAv!!6&~*UoVrbLbl(q$-+3E=JRsRD7Q6$R(`@C(oaEO&rNK>zW);f# zGJgqNmP^m(1bZ^mIAKs$hZH@PzR4bVI(t0b(5M0SlJ9bDf_r5I1m`A+D3MIGUj~rY=dE&!=Ddw5%xH zMP`ijK=70tXS*1;4mHV|hE@VyL|ZC|5_zjxxmZ0z-p0Z@f3hl=`YOjMNGJ_oIkyp~ zEk{V{iVH<+^2GCQFHbz3C&P8tWlut2)3$e!w>*2i34o}Xk2pj3QS?hTn$r156(z&n z!4^MwALWU^d#nvYLhNR#YjU^9U6q!56q{+f(w{;>**~-5@qfcUV91Xo^QXs`yP{F^ zWYrJp6C7vSo(dH*pBG)E<4js!7Dhf|k=M;9*f`f3UN$76%LX6GTB#v3Qr0e~e@X6{ zxpw;*eukLB=};lqd**wT)oKgo_xok=AXDpGIXRL$ra?C7H}%UK0RM(b9G4F&`#m_IC}<^qI>V85?F*Z zyK%e{$Z#BIe}g&>{WQ0YrLWV7GRz8cExx;VWAUMM8p^Nw-;4p%Gl6%bGmyXK8l+s; z?XqT%wmm@>=Be-%y?BGaPgzU)^X`v1QFPu(wL|A#2ksXd=N-UX*HjSsW(8XhEUBIe z_rH^)xNKSel)rHI2ySbwELqwH7gV+d{=Gm)%+)jiZ?kg=6Bx|> z4`=pu7+j;4hJ|OBpQN_)X*w#W90Z9bKMQ}X0V0P`8>WF`JBIM4mQ>}m4KSKOqvn;aOD@DSjs8`%_Xm@}0+lT_fMTZ@LTi3CZ_--nynUW_RO7_po)_ zZzRXb-6izecNd^5VqkAQfF5^F*gvyg-*HH%lVjdI=c>W zC(`2_=5!S}+%l97g&HG}cGeDmHW{fMzM|zQh-_q7c0z>@$QLd^Gqb}!9InI>U(Q|6 zmJ8CZVw%@AkPxZL?sL^&7gm2IyZazn=AD_w1g;;CGU_$&{i{0SiB{ktH;xA5cM7?b zIz=W|579B|cs`TX-kI#Xl}uj4|8+qoW$3RE;f$PDW9_&AwwN8n=;Tzo-zUlOmT~kF z=I`np4#HOi)>0qS!sw#LL@u_LTuII!{{$e}#Z+P{g`y-7Y{BxJG z7M&!;4z>BIyE>x-%hNZ+XwZi0kaTO3W}N z7rTV+)Zk#9 zn^WYVCp~PPLvJ=0SrY9ja=;DDpv`e6 zem`Jkd|i-F%R2Jdu`mx>$#EyL*jG7hV>yg#bs6}RHqYPnaGFN?1E#ujj*N4R2RL>G&#w+X;?dtNRAL(dP&Bla6z)_S;0}(rM2A5U@J| z^L7a1>%KwNa#?@7<2_e;)q4QU0Du+uv?Zks(SR({oE-HcXIsIp+n-pd0i2Gn7hSad zgiEqHJPrC3tnuf#H#^#we zHh-Y8If+b{{~%8c6jP3jToE}rlDRWPI*PIg0D1G1@CkzV9jSIUi_(sWMh<(qCJq=qDeElJpP^zG(HDzXj1 zOB}8w}gClo?rWaMm^Is~nl|L-#lmxpccF9AGZW zMv<`5h{`U(@8kp)@9n$;==<&ZmctM)($bxqXUYJ=H(5ttFX*lH$q?(4@2O81$B{$2 zbAh{-cO77Oec$l4O){B2cXJ4*a#f!XD z0RKd%YY0;Y1o>k=@Ll!4R<*4=qb*eRgu<%UyQ-ejox&T3tfZ3Qp2c&UE~=jh$%EKHo~QVk$~7eeVagIbm?>yE1Mzb*b6iNgE9 zGGOrdpAeu_Sob+fyX)*g`pu8)vP353E5Vi~z`dVV^>g)dQ&`{T(!}*Uz6sanDrt%^?6X1E9C^R79jQ6!wIbPwP`4^N7o|f~Vrjpbj zoux8`8qX_r!5Vz8=7WK&p$$?s{V#3!sU-{sN@7cG7!QK;Z8(#yL5llfk5t~H5396{ zS7C+PED&K;#w_q54M~kC;)7XWy;|i(_9bzzSzr)Bp1082Gn0*~U?h(R`U{S;htt~6 zj2)!o&9YlL+2|P~$jnVnnp&0^{`{rU$#vD0oDNB~_e~6c=91462P{jxya}HW{q;|_Cih;)f@-ZNkF-pb&a}) zg->LJ6=E>1zs+uT&!x))3t76Lec!VP*FGl)?ih11vtutLmvfJzxj zfz~0Ct0xu&?k-4C+ld8%ZcMouprQ+`e_7i93S>PHVVpf z27&Jv1R~YD%`?KE%ZDpa`reS)KSIos9S8`Ab)Y+@&+p#@Je_<|4|26kFf2DFJiq#==yhKQqf#@mlQ&uNp7%zT*D$<4c6q0CclaQy z^cK44EB5e%4sRH4J&x-7w*@KRG=MFnTvnLU# z*9$|9K14Lj3PT|fM>W4L46Q04o160yy~$<I8E{P|gG;L_~vd0Vbk+VKtXo6{E!hk%n&37RmvAu_lt#@K2 zBp0yo(@R+;s)d#QnSCYljB)*g{Y{fxoUZ~Mzqf5c>-f_v*j9CY@x|O^;J8KLZp_+o-Gy8dt<6x-BKgoW@a|yifOY0#+qt-bL87vRSF$tj+-oorh>plM zeCV$uGL<*#K(fd?%(hZ9m#Yd0bfaYBh6P<{pd&Rhe%)l}=(e=N6>!}Hk`?AU_7Sdl z(XaRi5YaV+tpD#+k)p<(P$4pG>Jc(+3AQ$>Lp8zTK$YEG$GQ@(bMUTe++9Q4RlQj& z)aTu*X`6HM^e|5#_*aYM>7ms^z7#E-qW<@ri@Lj>iTPxtP1p5{#^y(j>vl*da$i1B z_193IN64(N7E)`v5+!_5c7W^3@~|s!V_LT6)>v2W0Crth{!(*!(3LN*S6AM0ymjSV z7hzpl@qW;i$GN!9Ad$NAhrH)v8*8mA$MT`Sj)-K0peyg=wXiFX&3C0K$6P<|haEJk zGS^}LHTSC%8|a+w9!&V>5^6m-|CGWW6oa~m@Dbt74!zAk&RYBT6* zui+=$in#kv0v>=IOh1*>nVWTZxNCs@Q3Us2ksvsrK6bH7VRX8 zaFVDl$oHJB<=ar0Z^(o?Mu+(xl+Ra{tqCXQ?-INuLOVkQcXQaUn$!a0upPsXjb8gz zz+r`zgg9&iSfnHV@?CYr!$TJ&@&J7I3$@38NGrPBv=ustF z_LoSa-@BZf3&)J%8Ug66`fg;Vf!yMlnPw3VnNBHR#ePsC?4^?}+Km~*bPe~27UX;A z*77YY%-1@BiTIX$zF#!vdw+CZ%KmFU5It?Vx=BLQ>U?Nj^@y90O%&1GGPR+4!~uM@ z$m!tz3JzHHK5vOt`^V7ubYw>tkwnE98%%`Fy0^NxPnhMEXb`MAD;Xd*nuvz}Gdq(Zjl>1JP5Hz<;vjjg& zuuZ?4^OIj&if&4M%T()*T#TP!Tmt2YrOf*dNN}^VSqjT&hw~Dr*@1~XeJ=e5i$Z6v zV+p-Tf+7;$M|qBJ^tqwdd$0PeqUd#9#zvkD>63fgj;W!_J1Nn!54TD5`z9|td8ea{ zk~2pMcGqtDN`jqH0Cach2_fCRF*;8lr_*<`&xW=7r#N^M-JqwDdH9FA;l~^!*d~g< zH6MuRR-;hS)7AM<=qV;Sh6qPbDTD>1r<-{R>1pjc(bF9aLFnmn7h&{N@UbuKO* zBoaNH%zG~ORkhJm9UmQ$2lyY*({2j*kMuNVEA%w$uC371R3eO??m4kHJ!N`?s!D7>Olg8E&Q4z$$P63lz(Hzdz-Se}< z$Am9?;Nz3TR`AhYfDeQtCgVUjie0PaL4b$fl6;GJxXmJZ;h~h24iA@Ev}5gVdS-C^ zARj2?F!CI7P9)f*=goP~A(o;WIb*kioSk>rGICfE6wHtF#HPH+(O=~GPpb=AHL53) zdd`n40x_nuaDMEb7-OeMI1li3H92h&U*}jvfUi(sS&#JY@O6Sk7mANm^w4}DQ3%wF@5|k%9aVSj>0@6tEZ^BnOK`tt`U%s^`*T=p9>HTI@imahCWA zA>z~=EiDf35z-`TsUUcFg0~`5^|2J))FS&PEf&>r!AxZx&S?5zAWe3wkNiyKRF>WL z2Z0frESqGbt&6sDY3682}0-Ntgh=~8Z|1eZaiNrUdg0(`Dyh5mmP(^-dkC_xJqO7yiN{; zJkvkX?^VATUr?q_!3HogP-$h{Wes0B{D4ci;D)Taa3O&;^BUEqWq|)0XMnmQ{sm?% zxPf%9)+wE=2t&I+nNLAs`4a)oyb6QL(|g5a2XHk#b^BWyKQO_Q<2QWH0}tUNe3nZD zz@)92VxRnXA%7C1E=2BFS|67;ZFwH5m7A3-!8OMlkG5}GvYo#_Tl3iwe3RP_G$Ozm zCRTp+9?|P`fP8Ydy#^<)7uyo&+)i5orLgtux7Pnth{8XS%c_xBlFJpEp>X-|JuZ1J zE6?>L^$S8v8-&&J0V5nJt802gGcT|@K$)_KuZzUJbLYw#v@&Ix^_3@+NB0`qd$hL z=DP;_i;W0;id%iWh(NPZHxXdGrwM0yvDm|11rv_hVAsj0kE;R3(VsPq9+`Y3Tg2I~ z==5!Ub7tH%kd6Pr_hcWuqK3PLxiXd8iOox~XrbA8mXPhV&S_Sfc}oL3Bjx_@E|NK& zv=1Kl2Ru2n6@#nYe!VirVO?6?jvE@65pC<91g)Oj7o|sFE&9 zs9>K9ukCAVbC61C_WqMvk%6p^ySAgf7{dN*s45>6If^3f2v_Zsw1ETs8bHmp(c8F) za)pj;%2d=LNAfWj9kF~sQ>qQKGUHX%>drzl^kD2shj?w=vM5 z8)P&0lqxf-RRzpSU|eDj%doW>ZM%xZ@VL6FNyVDdl2d@XM>(nIO&A$GR$R-Q=wasI@?OH?m?5G8G!e6aJKiqqiyuZzlW!3rE#m z5I$9XL@!3tME0-%JBcKnL`)+h7$rN<+szs&fAXK-Q#2Ly{owO3qW&Zp8F}5*sQxxR zx6(|zk)dFRNj#)XL&hD#S7XloG(Td%z~$2tUbfErhY4|LZ@uqRM-7@=)(LBcgN8#% znXv_fW+a0~dWx|Onkx$i&1f4m6Upn$aL}yrZ!zy}&`N&L%vb)XmCv1`2CfSR&1%1z zL9}sHFlZ)>ut75zqwu9lV1wqp?m?sRr$NKy&A6%MdS)9pjr-_ajmFL47~j)G#?AZG zAU|#zr}-CCj!UkK=rI*tmy5+Loeb|}x(l-2K8Btj=t#+p6XrjKh1X&>crDH0sCa~k zU`R14WGh-W~EIY_9ZR(sUCwt(rYe_uC!X;y~Wy7TJHV4@%RU~oRQ zybko*O{SHHe^TpxQGL(^6N}83!_-9eLH}lUHIe#2g$E*a`EwGf|GYmXq$4}I2otFX zJg-98d%3~IEh4oNNu=)5JROT$#6j=!(GihPCx$S`3J%Kf+A_!_t{0HFu3>)_8{q4R ztfNKEnyCN?+PV4=mb7G zBB$~{z~}dP?Saoz2mf#QJP^38;PYiB{Vn11i`}b z$Q+!ms~&bOMb~M=JgbG?u=n^hEMCYrK$0|r9TFr;Ep+)oWi=Q9B>zvO4W(207_5=&S8MXKV zd9|KF3d!wk)aJj-CGnyavkWLGsKU5`cI<3s_Tw`drA>{)yi_duFknY#a5rjHAe6bk6$dOnRl9^fUU zj~}fNeH^H!r6U;^Vf69P%78xBQ4QkigG8c_b9m3iZX^Qw7|%yXeazKK?*{-Tg9zFGnAD z^VUrtpYPp+KIRNF`q=i||3)8pfbDM9DWvL29}CO!^l?y+dfZNV|08`EKh|zgIopHK zZo>z8ZC5EFcG@1@O(o-&=r`-RaSKzw+9fmejqFi_2MA6770eXU@tjMz{~$|X9sl{O zCE8g+Mzdy;d!BcWbG100%)yUlqS}UvIu`G4`7w1ZE#(cr9w=d=b)9rw4Y{NIYa6lCs+4N9*}*AH&aQ?R~%rwM`z!rSF!84yZ{!Ro#G|B=+eHl zlVeR+_Vt&(3zdPB3K)Z2vqzpgOc!zlGGTNP=ijX_jz1{WR4f3XP-4`&ZgTq4zD{hz z|E0y7r72nBQzuy0Oe4J8V)PmcL=N4Fm7x~b_|!+&Z!MSidZe5xVY*=xnAYMrxK>W* zy`kn#TEQ{y1^gHdD%asfW-vVD1hIpCN^Od+F#cEInN8`DR&x{;MXw)?+xJ)xBqX6JY?C)C~p#Ws)T7^ zoxgiRPgOh)iOv7Lojw6E{;w8}yol^darUN(m{V^0&eYgc=)FMmJZ!i9v? zd!OY`A=?I!cX_iJ_hb72_~12%zztwbMgGbX6-8z8Cz0>_xL;BKmTj2>@8wO`gv6S7 zktqCps=9x&7S|K0Wu>W2?ht1iS%JM_tf`kO$6kk!&mr3J7ei0>PhtXh6rMhQg6bcq zObRs>T~DP(j6;qnYB>)}utoNUHa7B!->dGadP*i%1?ub+`RvFs~Q6 zxXXh?=x$18@}7$=8f*lUj$FuRN5p5Mb)0W-qhOiYv41>RMZr;=Vd!baQoL-Sh32o! zt=H7gmEaBxN_~o+^t9MOl!i!BHJFNw8R!LG`C@W6Z4!}oQ zJFlXT@s31BKvY@y8QI-N+kSL@axjfrdk~G{UTPva*UyBnj-OWpRXFOcwrXe*^)RH8b#AI6 z^Ob56`p<%e5^avmAv~NsM6V(}@v_!`8ojpjIq~d3hk@FZskIg@gHD1=?W&6+i9)W^!J*;b7rM6)QAu0w#85+BMqF7nm^Dquyn zTO7wUTVXb3;$Tf+@D91^K&%-o0B%4&C4xXvNYfp%v2FGurLUt0g6f}t_F>w=;GlGE{0O{ySXE=0bfv(8oI7ZX$ebW4fUVfjZi(G+v$DHQN^vWdZRiE=WgS zmYfg8aw0WEf$&Zxpwf~W3!_%W|ICxw*t^oyX$m7lre57z7}>_!IcZw9yNqbSB)sL6XSuTTCQzxi65A$Cf~iNKTdJX5vAVqkAxv%5v-NFZV~ zh%BbYYe*%j2Qv~Jn`i%1t?F1(YXL4dDbjXo8U~#yBU95V6R8=*X46QSR>e5%p@>uh zT{BU`Y&1R9beJt2GEMera${n-$S7dZ4J+b!gH_b+g{LXDxHBY)aIU>!F`LhJuL zlEd-dk2BOfcqfRzvziDQnS>Z6c9DI&Dtn^FgUx79`>RH1nsF`FaSD?ZgGo`yKDl^Y zk{Dv{zDLcpj^L;*6dV^CZ%00_5@w;`_^u_29P9@sylKre=|zJG1jlM#(x)&dmw!ea zZC1Q_vGG^3Lb0(jFE(zo6!83yEV1v2l~ z8i1=xW!h-MnLYMh`=V_-UGUd*E}J1E!m>B2Sge`gRb9rjEfIOCs^rC1j?$Ai@xqEx zxqx_$_z_rlkbasXZzE5!a`X-1YODCeyRbNaCu`SLiPYVrt00ipkOqZ_ z(P|FApRdw6Et`mc2xucAuLIN5j=Tm&pq-gyf3pwFlRkyeKIN6CxrmVj9L-hAYPil8 ziLRPQhmPT6SA~n}Xu_HSc)H4wz%G!~6D=*2)KW3$j#h64YXjkJ0$M^@^bdq|FYlXxS?K>gWkPy~KuDX`hrf{|^!G;3$cg86 zC~V?+0;>^;=O!nfuNcki|3y6Gh8)P|)HIXJa4Jg!3FU#!p-|r5);$R22%cbpP~KHF zXg)6%%B3V13guF|uRtiDJi&zWOMC0%Vxc^CxP&r|Q-`Ttnw;|O&J8_#sGZD$!|bz! zrkJK(&RK8IGdUZ1^_gHO#<@*pEn%UZBbu&)O3zoZT~9?jue9<C`|YCD=a>MTw*r>b<0}XXHjIbBQJW}saam+^Sx+Zy8KV$ zBqEFMD14n9M;<3uw0i<~z2uvUsunm$nA5diAGr@`B93%6cqf#Zf>jFu>y4#evU5x} zHFJ5#mUCuaOv_rQC@c;;=A%0JB%K)Q|FmRqd!~T7YtN}XW!7`(1sqFx4qNjuNd>LR z90Xm>?c+l-SS|R^uEzx5wCgO_#kcP%x+J-3E#4dIo(l;YBN=q3 z6o`(Qy^+@OhSUwFP(;q-6G;>pR;v}38)$w1K#6u5ImB)N!Yw{B&GwBhxgr*Y#}-}% z`TJYGRspJJyn##>$Opt`1jcs`7 z^ZDM1BmGZSxKjRU5QfTJS4t(N+*VY|y}%F{k{C1tsW_ta_}yeOO7iQaIO3V|PCT7W zeevZwMQF)@&DmDTlb2hqYy8v=tm@9As99S+7F7CP8zYfJsf$)pyHoN*+{dZb$(>$u zZYtS6>#fItZoR1Et)Z>Qb-YEi99Ei`%UCmKW5a`rg8GB{zNY#c>jN6IAgBggb{ z0Ram;@!GC0!D}zN?0>lIo0sOZKY5YthsgGx6!iHVtj};x8g^UZ|LH=`+b27jHD+Z# zQBykirq^&|igw<~tFbgZO5@}6`r`B2Nh3z4O=o=G7r#|0@Yg0LQSLw7)nwkcY(|aW zs`L<)Ey7)}_n1wk^=pfE<1;l5l0sI$=6V{qt5A%id?h!X+c9&~F3@04z@qhiL=5o% z_Bf+dT|j&fzVsvd!z!wsb?- zXqs(7#K6$jvu(ufrINf+TSkRqVB;r)#lRCUgkoR?PnnbX9$9oL&xir}1)FsJCdq~J z=r%mSVE!Ho7DIA(93zTlXULJiBcE|VUJrRA)fMq}KDdw%@=zUuN_N(JGYKs@^UhhB zgJ?tRVe)d@-B^RRmk@RpmnL!6j{kzQPS!WMq*peOR7X++{u9*JtvI=&#G>|Z!#Joo zb9kxuE@=19IrfvR^TNRsfvBzZ-kW$8cw2BPAkIzRKaNf&$ju7%p0qdi@aoWzV;@GG zG3COxBUG398~mgJe*;3|P_NSbAygp+iP96}`l1v2>axoyn@je#yJCK$=1`Dn^c;rF z5*2)9>!4@-N0HC{O;9MgyC}5W0C6)~r@IJN_?!P`4vw5m2jm-oiW(!@Mj{jAOQ*dVa zoW!OeaiJ2q`E5FMAi4Hz{!&1@l51d5X@3Fed<#vWj7tCSS4sbicKy?C6r4QYw3cqZ zZSanviUj5atS?%owOwYT?rsAd)v>GQ{~i3F4h$w^6*SwSO_tRCL`F=L<-?u}1{~vU zht^mDB^OzgnL|}(F*&>LWm-$0utO#mAU11e=Z=t&E(~-)pW1+yE)=w)DDz2m-2<1aH1o3M*uq)w%M_dRVG-3fA^JmWA*MC)E6n@xVLPR=kqrfm3Fw`O$c!Yu4J@`jq+kUnn!RkR8& zs(Z+~wdEa>lD3)SJN z4yjzE&MAnzO=^ux?X1VHFhgVzW47yUn%(<*8qc3kG;kA75ToNjjU8C%3-m3Ig zrMF7|sF}9>s7Oy7!fMBwGnFW|D5c46*@&7YDpX`d2&yJ`r*Jz66P z@0I=|U;)0Emx|9Plyxe8x1w0b7x>!)NP16#*jWA$y(l17dMZ$JiGk%!9*HdUk`-J~ zZoSNZmBe2!)#mReIDSGxu7ennvHJWPaUF!boxO`z6$*L#(x8yHFMa%RHuEsVG_5;h zBL~^Z-a*S6$=uSXB*H6cjQ{6)iPt;=7Eze%(~U0K*l%!Xs#gT>wj&z7OPZ4i)Z>Yh znMYz9kYL^zgoI|2DIn3mS9W=W_XO4Yx6p3n=Tkci={0i93*=|cD{uA$JjoE;!YkgD zt&QG*43)87RM%qEdI3ko3cVM06LCax{J0i=oCZK4KQ1sv)Lm>aX24*uq*zp31D`F2 zSOND1d^Q&@t>z2Bs~qReCqFyB@e z@ol1jZ#yq9=O-d^^SdJ~+CGzy4KRgZEK9k>^|Pwin;_z@Emp4;tV0 z`lkR$I$|ik+;4C%%mi^{9DKW0QQ6}!`qj!v#n;2o3%$uT#>!miS;)$hOSZy1!_>KSvBS>+KonX$kD>%MiyFic*Cqb;$+F z5Jwv9yht={JC0iOUjs{{m!9=i%#Hs*{U;p7D*vSSiswnZysFofme{)g5FRp@p)WI) ztXmzd`&~Q~Vl_oOPa-QnXTA+C5z5xQ!w#@wloqf3{T-!bNkcLzl631jadNZ02I|fEXXFp_U#zs$|IrF@^SKrwi;SkK@*;g4m z)mls-KR+L$wV(FXs@;rS&O#*y0C+T_CVYMPR=Ntf;64!R%KA^XOoh zeFRe)z*uCx*wtI9?(;6dUZj_Wl>@tYD`3wF>~8^>YvWOaYy68~t}B8WAiPZx=GO}5 zf+Com2A6Z5U>bl)rjJ2`UX7CH4^LV8VZL3D$b=&YyJ z+NQdN5sD<<`X@t5oU34y90+RMSdhJ+nZNb{Tf~Y*>Z0*gXG zc|VL4S{5y8*^n=`?ByqDnKaLrcrXQiE)T9*#R@!kung8XJBfQiEs@_v8C!+B@|j<| zjBYa2euA0}ZodP#FaW3oU}O=2E(QRTW2HWCf>Kr9({&FxGQIXOT*90IO`uKNZh69l zM43Y5{mc=R5M8uk)Wv%-CYuV`O1yg{J?2*nZhN7UT^!aIzeaKy1Y^iwVAXN!Ya8_)GJ7idY-i0Ga#T~-CMY{yN`~!8*3crmzR!qeW!U8*CTu}b= z32$5f_=P%K6$J92&TP2^o!NzHUX@x_NwQD!1Bf{CBmhyI^QFqFU7@YDk<#lcCnwV9 zkNzDpUMn6xEnf^ilJW85dHWRwfq9~3TUQG^&Z+ojZQQq0uGx7sjo5?q7<>4>s`Mf) ze5yQxM2N_-d6 z&d#MJT6D@Hz6XiPd}5mWj#Bac?0!og4spSew2TLCAK>^bpch;Am4FQQ-<2Go%FOi+ z?DMY_VR`?gujk0Rfh^bmIkJBJ;xi#>Wy0IukK89tIZsIG>(NKBWu~7%&w_51qW3jO5@llgDdbKIX|G3?6q1 zoT!N-iJFKE`6+^CLwFHNFe2oCsQZ<0D;e`nz4zMKgI+)tKnH2l@r8GJHb>xcX-G&D zod~{V0=omTy(MN+kq@Leu(N^~i21i^U~+t6wRW;nkp(W#jxRjHb2fI3WgN7am%hja z{7;MdWc)RP^!ls@mNn^9uVSY1*K=z;gRKhH;(MF}Wc~jbqlp2B^x+UeHnz(E_2|4+ zFE?;|k=pG@QtZ~T%&oqj0c$?06$)awqH^_S(%XWoRNr2~o5 zwc~Kry+|itSCI*r9X^wO=#_W&aa~pHN<8g!_>o50IVSbauYsj^Rva1h&ZJ8JOo*qQ zvnCd!cD_U<`I-KYvpN_`7ZYzN-~Yz3;#WDFw7UWON5qlISBwY>&dCX!?RS7@#k~9L zVY2vw2V*U&e-S4w{JGd6ghBUn8OFR{`DMVoJG#7nbOU`PFZL|Y+1O)pA50zeZ(jN$ zck+M0yh{Y>nD?uB=G~iCWJVw?z4Gagifse0jlea9{p}Bnn73oNk>_p>+7~fza7q#H zA8}$sx_c1+Hl%7vZTb$mu={HR4XLJ)q;W}YqM{)+J;%Sia}2GK8($i{Dd91+RsIOL zw2*(Ra{T*Rbus_`8OR*}{&SdNdlPQvgZQ`j?4~g% zolaeDwq2g%+zQ7>7*0NlcFDX+S5BqgJooCZfJ$TG=rv;m(6N;EP8#qpSwML$%&w?1 ze6=56<>&PpuM`7i=Us46hmYm-CiehbHZruN^()SvoKf=N84EKMU~2)(PrRz6iPVR4 zsWxZ11Cf8sCAvvVquPtyoKNNBX==I3Z6~=PDiL*RVk8BHE@gQEjRD<||e`^p2$3yeu z_=Ta?^`(d9$MMaFid7tg7Y*gF`)(*ZPbEe({cZckcRddC&sx?FjqA=tw&(OSX;C9h zi|S^S3wfH}_qR}sf_idCshq}#^6aQwq^E4GYvT~@Zgx(0OY0*Xj@g@GPg`FF4u>ep z3B}ewjo~NPb6E#V9Oyzo z|K>pN5vZ*LbD*_=_WJ+)qanZ^S@|ysbPAxG{F5E%0H7y0&~3g5+6-ukzrO=51M~<7`rI}k;NrT56qp8R zh5z~8)~u0$?&d)67HC;fv!(-D=|Am2tE?djFVX=ZpM7u>Y-PlPRb~LG@%sbv!toog zSu}#;NIk)Q@l@d#SMWtGd50H%kqGP9!+o(J|3$K$4>Wp*7Je`V?W&u^jYAk)yBhMQ z_?v?_>Cchw3?ew3Pm(>iPV#*Y+>nhu{vOf|L(b}jGNa@9T+d77PWxmnJ_1Y;7Ffd{ z@`hH@?z7yP5e~JXzg^R(xq%=I;;&iDGz@|<;^NVuu1!&tkAIIn=n#}1qF~sJw;#_o z}_Q-Wxn~{*6(^jJKATzR1J; z4<6t(ez73kjK_T^QrD#L9QwMcYg0ISVFzVtnZPwlZJ$wejD3JR65^Ow#ITk9Gh+GqOJ)I2d=+L%MwN4Qly79Rdji02u}d!Rm%Kl)e}azvpO@^GwWS zk%pnq-;2E9DI9R4HW&k_rG-%U2`XLw1L`S~6nXF)3c+Vvp}EybL8%uMf;GER_f)Bc zLn>eDw+q1!b){B(5!YP`!M1ayu7ewF^XQ8N@Yz?&5nvrJ%&}Y&=T?gO%9j*^{Z(M; z^0Z3L3qT_%Uqo)SQu1EeU9Qy1FP3^avg2j}t3h{h(QjB5$yD76KG)72o;|=}&N>Hmt>x{d?!Ok=w~bo&Fu; zLW6#d2FZw5H6z-tHo%$@gJIG^{Bs*Pxv9|UyE>nO_ct_Kat|bxEkTwYc0arUu0%#N zc)_3jTg|^NL?ZV`x;G3G^`oo&^YoSv$6AeG@}E0ShJJ111{RKCG|xPrf(2$?iFo_d zu3N;(K|;O$FruMjSyS#{LV&ijcb^=bLB_|eOimSC+$)DE?$`KZ(1xz{EW^gE zt4q(wrlzh(k|onWEJJ_irm!J6hegw+e4u+YY-KJh_*^^}kv*q_Px$HP``5$jaZLX7+DN1^B+5U@TeK2U)=X zg};~+_yl0d+{Nd09ewNTI$ksMw)cIsV^>?AaLV;<_P;DiNIV66ofkm4CET+c-c!1O{BV- zoj`0bg$Cl zh)kg_5pmz1!MCCOGg2Dqs_ukTj%<{FEHKpe^cFVT^VI~T7AgIK-x$1+kN92={x^cJ z$e{(ix??zxWIA>MD`apXcBm3Clm8YxqnMJXW+k{_3g)f}iH#7xE1L zFF5$GegPhRx2sq1H4c8c!G~S6VSp}jGPkIUR*>$72G_BAOiMU*=tkG2zEgu%6{$hD z8FYafw95Z@-N(3GtgrK4q<~{p0K?`(4V&@y68WKhqd8vt4(aj?CSi-~GiQVPzjar7 zBMyvtMI*q?5V16qg=@Gc4>S_qE+}LiBuk{C4db4%b7VW&2dmvVvOpzU<^LF5xM6k~ zpN<_h1eLAr`-!omdeII{t;$MG2-K?UtEEFE+L)~pkQ&7 zD1BKrb{aTH;Vh=~n9b^!B1(4$CBA`Z9j_8E%GYmHBrkv~#Sf3o5_z zQP|h@-Udm+UrMT*Fgx(IAtgAiS$vD_>kfFLb=$gePei+pQm?fv=iA@dwiI7DJ0uUL`UP;`ioh6w=DZOr1)_Lq^NvRw6Y3p~@>JBD?6DiPn>n6A` z+2NQCrh5r9dCKgjse|>~pKU6zDd00iZNU_f8Nk5{EI%W>oO98e4BHg!NNvyF zpYs^4o3+tgMjPS>eA;@5{u>z|_bCUZsln^barW*^|vvvy%Krek1OboS1?VsU+UA6l+m z)9E(nuxkn}Z;*+!M)NyH*<`zozeLM((gy0i<#wfHQi{haKl)wgd~_&=0B`aQUa}pG zMfz;)&bJ+Ye}qny*+T&;mQ%ZOuF&_CSE)iCKg|`Q+awA8(<9H4on-KQwO*`)*T^xI ziLoQED#Z+xjg45VL7@}dI8vyLW>+GM`YC5qGRbq4+(Z?mC4#herPWd$X$4MI)& z^eSOyX!%D3I88C_ZRY%-Sz4(v3|ITdfSRmc-b9C5qxXISx5`}pz7F+VKB(hQfsq6^ zu{rV&Q{%I-iO)GA&B6YnMkT<0o?%+|fDjk5K?AI0E>y^1h<2tKUmaO)dV1ExV`TjM;@{+g z25)j1Y_-tryn%!H1V9?))W$V*IL+crwh&~xwQ`l>TAx1t2MI24v7M`QmoNO?buK?N zy1J#Ake$J2%W1%LJHSnXI`dW%n!_HLl1O*m;0O>Ll5doS%bHpXaIYv6ehrt#=ZYTT z*OmSyr#fN?7hF^l-c*{64oolHn(DgKQoB=K13dVbA1yYURB8P=6;U%5IRBg=RdS&t zTX}IqqGN33#S<71CpR3hD(}Bq7rlSjxpk?b+b7ePY;!JB^<5<2e9BE6@VLeKy?>i? zQ$yL%I?-}B_3z3q<7SrBb>1`L{wLNrOxZief;qJb!fQN>RsD9Xnp(I%WfNB9$rrUL z6Za8mH|<9S>B&H?z^^azCOL}HZKHkBKGcrivPS(O>Q!x6bMlc!jz8nQQ0ASyda%qp zl;wf*Hm&B#HmhHewjFw#=I;#7e1%}1#n=5tJ2w+d6ao0@CO#?JtX9U@Q3I$8HW_Zv zgRJV?NM(XxvN2qPBXu@bfz)Ec%K$-!O@?=M1U7~TU$MNqT%K9gm+(wnZOa(-S6-M5 zuT%cmQCIS(O<}4aqfLhYd>X%<>M>8HruuM;&zL|#w!;t_JOHMTkYeXeZkh`n636T@q`h@Fx$FVLO~F^fnp(v< zl3=xwX$I#)8MCM);hix(3{5H%j>H7v)8l9){g?}1hZRE!YukE9r-JhUP7DN(J9%+16U0z;3LKJ+Cos%Sv>cKfI5unOw zlJu70vsY6)Mkq*U?vT;0%ePT$BHK{Xn5XJJmi=QMYw$1xnVt(=BH8tNv~xUQ zNrFgr9wzwoK@b0`lz6S@){h+*?X0jOFj~FeaKJKdziSxx2xp8Kyq4d(BW8C3s4UH8 zUg0Oky&vs--VN`z7}a;YBi+hrxdWM>lS6#wWv?vYGXU|V!HJk zJ2Q|^g-{5ygVx`U?o>wQ9oZo>Bi!KDKRlsei}l&9U?W-w$k>FIjcs{B$l&(cdUMC; z+18)e*IaRMwryy3RsYcXIY%7~a@W3H(B{UmGs;>kP#DO##4?&io|Z=wsSC@%=0va| zFlj~qWeO~Dz@v7_-~hrsE1x$)E$o04rSW`zB%deJBWBuitEtNNx3aAjjp?D^(b?d1 zqRjtQubLkz2lCu_>WV8l;P~wv`5hAP^$R;gB6Z=&#?+;v=ZhpN#F59AP3}~|px%wn z{g>$-;b}lP2n$iYhMlRYk(;e#ty2`PBpi(It&*5HZn_Vz}R{Gli2y2RHmB~%n*b5MWY$Xv7SHiG4I4v=gq<25Z10qdh0p5Ij@o7F-84W zO+a1}ZdC%K_j&6}4xRDItx4}u{>*F8%(t@6`;d4`EIsJoeVt(0gK0PrEc>V}vzDn^ z?(~~l_dUhlN4T8K=DCTKHRLGtYCC`b{}3sc1tR6Zoh4E%Hf@1O8Ah-iti=f@QdauU z0+kafTQVk67U3Elij>cEq;ol_(gf6yTSxcuOvArMi>EViN}obp`3t`4#1~|o5)eD- zt(KE-Eu<5*MLKr&DPpA~HZMfOAxdyCX8wqdN4aoCNXd0iuq5pq?Ng!cR&peWs7-8J z?jxnsi%bGriLG-+pDeM3S5GLm@bSieM!-nK7V?YZ)WB~-U>y?e+|FKLLe{3yiwm%wXt(~ zF?n@3SB7LyLSDU;k(BKC#01qN{li&>$di35XlNBvEU20c3m>}Q-tk$s^_F95&Ocm& zYGr@qm^tSkE-JtAWJUBfDWHV-XILz zn8y;{%b|go5%p2)rCKmu1j`)IdcH;bSrN$PaeXsScm5=n8P0#XT=CP2R&)jgd3amw z(KT9=^{VOI|7`VzINRiz|M+J%3SqG6UA?kkPve7nK0w!r-nb-@U6MmG^Bo3B*qlG* zo3r!QnnP$c4Ji$pG9L2yM>_{>xG=xx8yHP*AW`%C0lE1-kD}Ta?OYOkmTTh~M;5g4 zx;I4AFKOfKOs76}nz&Zed-JITSvt1o6{b_u=;J9N+g*i?q3$8Lza-OS6-JI!$y=8R z6k%e4m@`f+5L&_peYEqd_AxSHb8g&6)3{#$4>DnMv=hw%ntfz4Ve#t)qa-tr9Av_O z-6Wat6<)qL7|~&vW{PFP!%q#C3DYPxkO`oVOgJ=<3Ex2=Arlg+Hg+N}CKLALHVBSO2^wXpyx~1Q<&T1n$qaK7S~qUdvS01n00}p_!IO}C84`wKV{uW$Bc5S$Iju| zQ2Zqx`8dmjl36UqWS@AUhCQ=QqT`X_nrf#fI^JL%as{#by(@JJ7BQqVx5uLT{AWv; z%Yr40qJ;lsWdCHAa8it${rk&9^`g}&9Z+Yrf8AHY)lJ(z@^d?Ddz22v_0~G3Q<3M= zpQNO-FI%{8cvA@v|Kog;8Dp0Q47A$5LzPKS3Rz(05suW z1}J>-XCNeUj$6_-L82q_{C;KA7=8SwsIMV8G*Gz|%(JiNZWF4lc6=Pj8<0R{61Q zMO5>}g!fkYZvbV^eB!+_humYIicF68UddB~@n+P35=MlNS!32^``G;4E_(InSdL*p z^k8QmbY_M99!6)~l=&uojp{0*dgbTc#=h-gcv<|blzV5NP*u{(+jfK-X1{FISy1lc z;$WmJv*rJ@{#3S)QPf%fhXrk}_clv8`8}m_zk*^Nrw^<3PJX|PP2w)edL+(1C9JK+ zm+;bh`1lg$wbrkVXVZi_BfC*o7|p)%{fd5h#1AL!-_eTt_wkjO&qw_`5~lC{3Uc0g zDbMCYH`@6u^(MR#?*}aJdxw0=Il`f%xdEhs$*j!V9AMhev8QT)qbuc#C^;hbR>p^16TUyY8=eiC&HtfLfQg`d&l#(u=cyYe~tdvh*E^BN}ztNc_c1*JWcz<4TD=(TozyH8Z}75y?dcqj-W0A_x+={Vse4<6r8> zl~P1%`i`m5D`crW1G12^@b6zK=_OfzT(Zs5U-0Kxs{`<}-gaTmA_vW-?V7KzT>RtGo+>`=O{GLCiDKdi82%( z=q$5-pC-NB8`b;*B7_h@Q_P-9=j1{j9e#7T-**^K8C$2O%h#iMI`en56Z3E35j1o1 z$h?4LuroG_8)G3fuxB9QXWq&0?$pe`vK86Of|GzIYe`w(7w_2x$ZTvCBp&lr-NH3C zUg73_jOHd^cR`@t_q?&hkqER!f~m8KCbEYdZc|eiCOC$Rkn4dEJ$>;PyRFjr8sajt z619`!tp~B^u@FCh%FRc0Tis`YtvI16Nxy`%QQknNjz)NsdvPdO=-*>TZ&3!`I}{|` zZn!J!Yei*sP!@qLIGPUgCJDClbLwUXS7UI3?JiDcLw1wQ?5b>ZlEYm7&8Wq&-QOxD3{}6`FI39vMilR6UNb3@J zc&Mc~EXIIU6FG@$0vYT)HZqmMDD(Z+bLrC7Z_=G@yQ5!J&hMUOY$Io99?Xde z|D_?V<@vLeoBDT36w{|`Jxmq^E{hyYHPHAK{5Zo|B4Cc4$Z^b}xroBk8o<)Ew!01S zF>9Fyc+pNt7&>dV$V|+fnY5?%VcPN-+Z=8qe(_%ix8+v!dDr2;RzE7BTXKN@#BrH@2oB$6mQi6q!VEEZ_<)P!enr?K?Wbw|ke>9p)+8-Y z(o>yv55%pm&e84sRsT`{4Ec|fi^~Rk?Q}e8)7o5GuhN>yRM`0`21r5^2@6O_xYD#z zBlZjlHDQAG+IvW-vU0=}{^j-b%36v2pHnst4&vUqY_-mut2rtg`{5c}X%Bbm$id#^ zx$vjjgW21E@Kbpyxo^bKn$>jY+F|aaJ%9_g1a`F9U=;ywr$GjMz2?X>G+2$o)klv; zkp|j^MWpqqa7>76k8od{qA%#hrlMY)7u-HUYZ9&(YXi!)i%JT@VS$R&Y;`GrEA`i} zIMQ|K$84@6{72@b{(gID1-m{c^&7o6H96$+hm)_)%g+j%VTek9gjye}NNk&9@=s#Is#bTN|o{yBd|l{p2GY?%y7a`+4B-1>6d7 zpJe$+p4^P~98MM5PQ?8OWQ`}TQB*n~|D*0FK8}=(`y^7bkl&EcA|;RTQ(9=k$(`3; zO3Vp%x1YeZV9>R{yF zS$u1{XE)NByN(hqFA$C364V}iqftAxcivvj#}j&F)~t-}GBBY(9eZ3GrMzRB;Np*3=^wsgJ71gesDW(PGgw*eaO zdfkHc@2{M6`CC88`F10l==1mjE9F>9k*$!Ft{(odCav{0GeYZanAUba>WmF4?gT1& zVI`}OQ-@Jv46R%4@%cUn=`eLa?>KxU> zyQr1OvPaJ%TQr+LG}>7s^5*MzCoCze$CY)j$|~@jPJu({hdtAQ+zv<%!w(mR=eN#u zx!py%y^GLPMYlSadv#Ina`TBMcW;;5T9k_zjBG~+)HzZ%jqr8XoD~om}P1(%#U$paB z5!8z`QwqGbvCrl<*r|}v%@*%a(`5m>s!z|NP>&$xi=}n{>Hn z6y*+t-yY&}n~HMPb<_QmTQ%fdUz0GypWqV0KK|e_b^f*Opjh0^4c@g<-~6uz@akdG zx|NpY41U7I8xH8ZU2d!>cex!2qw=4*+~GyJYr@>=E_X{yVZCc1tmx+d{G!4h zyjFDcuXJhFiNLK_+K+-XQIs^%%|9hb6GcfA(fp%>v|g7sptOTY>*yOM!Y=1MAS~}8 zVJoAZSolQ8rF~LZF|n8?!Xe{^^smf7$_0#oPl=DcbZGRNDF%#U5NG$Sme1z$8RSo% z1^Fe>LjrfnC}b%B{lEd06cD0op2>tsRsMg;mlyk+goZb%+P~brkK}zZKSP@6t)0Em z)NaB~E^IfEPPtLaO`rM|>VKy>Pvfp0z|yA<2jJaD4ZCYmh;oR{w_NT@iL7zCvkG$; zSng1A|Lk%vF3gqo;^xU4T<+Au++K3|@G@n!OgbtYcU_g%w8&X5oy(J5V3n>a*W^ln zw!#Obx~d)ASO-_1gR61rd%E;+{2N=KbKKo4Muj2x5oB^ac{8@nM8$}+eW$Jocmqc!H84k5v&dhF8 zL2$$2-_cRup&BiYUO{&gVE5H0(uyo;p z)>Qq63!IM&o02oMGMo{H*BP!aQZw5fmgcL+i0@H1_pyygxfqxtuP#-CCNJO^L(a!) zI_ZyD`i%l-DHFD|ZJuk-ACxN%HP;^5=FxZ2f4P|5v|a8R%C<%8yy58BWsRi+nPIG= zOw-k=QYpkHT_z`dVI#*`)z%zN%e&^J+JbhlknYD|Ddl)yL&7l7Hbyp9 z{%5TP;yuCA;F5c>XZK=s*K1dOE22socAWdt-=Xcgi-cC-2`|ISrh2+4L~fIoIl4HaK7HH|8$2$QmmT8j{jemYe>_B% zi0>i_IgkN$k006S{iNE3KDnu^jO6?c+zqK?2z_$Ock-c6j>GZs6vCezL->;`%k-5( z_cWxAv(P7(j!b%&5c=eKZewGivV+BYlg++$A6*zC32)YVWxh`MmSC~8pBAWp1-QQR z!fLy@{<$UMgz|Z>3~?V|-EZ_hl4FF*Scv&Cn;5q?I&tl}-RgEa>sxgPtZd!2ZrsYY zt5j7lU>oWgl-e^qbLNlBO_g=rI=t|y9mZ&&9Wz_H9sLy*#8Nw_5 zk8TePxxsaIxbggECQE1;rGL#s|Cuy@77rM5>&I5L#x+EHxyi;|W_Lfu05FX$KDH?f zEYI2!`vsZK@+{Yo4!5aK#DLS6%GbBF7g8)K6NBFe@C7QV@Hj&M{h}z1(y~(1w>&PU zb2|*CpXBr<{x%$$OQd&tgq#w8kGItBfqL&<^MIUVA}IDX%4}#Eai6=ojJ?~w+sZxGaV}xoQ4ljv~79F%3mKtL$r^2tQ(j z#sfCtwR}`N=Nh~XEM_}}5(JCfvbeNllDGT8;X*hzhXhvWvYY+9*~*BtDJflk6Q}Az z6ItraN?aw@s12!!RiKKfZv|t4d0jZ}XqxRF4cZf*`J^Xfu0(qr-KS$EG;JszR9=-spTOIgUl- z0yAC0PhH(VHsDuOzvT`wbHMd)!xDq}F0^Io zulY}(Ua6Sa;URYJ!WGepBXuDJ7_*GI&V`TWHppap(mXC%<39E14z)w=P)0_N9DpO7 z^A5G6_1|IouU`M1T$idnhmvl3h;i0eG7XfI=?UGmKRBax2;(rBKJGw*d!+6jy;g)u z|JB{m30tk^l>#psMoSiIgBik>Qa7~k*kyGQ>v2bGe#%V=N_dIBhojd z&!_}!K98bVL;A{*iPYTDJ@$M_1zGdLEV(JWn@AJh4d#PP?wlaEk!ss{ZbS@i;xAI& zOxBH9w5D@c&~YS8Qw@z=0oBZl*{K_0PE7zVUOF$eywnIx_5(9?I@>fm<^IbDIV{Du5^qFO}Kjt4EHv~>hLe9RPXWqjO< zc`1=T{N^i4^YKDlV-ns`rHQeZj%=$)P=)GEl&(P9$$Q{AnYkwH=MHz>sZyxbfv}g( zI6k#9k>a}>Mz6Ojwk1B1N$uA0A9Y&Vkk&C-WZ%-Vj?YRjnwx-t`kZ+Sy`Xb+nR{le zbXtUL(whl1b2RqShfYPQAzKafn=qR8bN^uZsiY#kYyS>^2Ey`s6BP2WLWFD1m;MFN zTo;3zDSoe6$QGuo8Dy~ESIM9JeqSykli7E{Dl*~xfca(c;amXWGrM=vzzV`=`d91C zA~Q0(^TD4NX%L&L-ZE(?&GAg*kz4brrWv4%Rg&CAa!~*ZSw=x}W;;lBENjZSe2KX# z>-iL(#q-Oo!{Ib_pfbd!O*$(Ysy! zI407&MHlT~KVt+M|M3GXiU=_A@0)`;a8-D!c=aXWX67oM`cH@QGkJh3w2N7nKTf=f z7X5`{062#ov0!0Wbf3}?%n3T$IHkb zJ8@*&h(_Z?>gXn6T4)MyRw`#oxn2P6(VZLRKq>l@GHS_K zAR*#1#Aob_1!rsx3xFzVL%094{hd6y+*pLID%6bc`88#1nWSUSW6Tbw;n;0B9CpDn z?>9!{n-ktcYB@&mIWo5&&aq(mjm}|UD_5$?_dd}eWS?>n64J8X$e|_UEGG8E|H*-o>5Wz2i3e* z1$#@xTet&daT0~h!wue!`RaTxAWdNziG=6MsLhvA13aUGj?|Xe|S9 z55Y*2Yy6M2@fL1ZKYw4yy8VaC2Y|`=S;)m?yqFxQp$`{IqDSio3MOM?$`QycTDRP9 z;Svf0sz+~Fil67KWg4`3@x#H)U{Yr4s2DuR=QGG9wOARjw}=LoX-qw2TEX`lef>;tYSUh>eP+OgG`EnEe}#iU#kML^S?N zB6XY5oJ)5OxJSqTxTNOWS6QR z`;eL)?fg23at88IbR})#jbEY%S{>QM1K8Up-pP+?6YnMvOQa&txeVLH+kh1vyz!57 zc^$z=+Qj=4&zw*EHB6ft|7%|QBH!bG>MNn1(|Bwuxx2rKBo8m-)rRhv)7BaU$5idi#kpL5g?zUi@2L{+C$$X zVdC@gs`YN{p>DB_gxPvMuxNI%1gbr^0feelphU=@>{Y^NxG5|1 zZ@fnkg{&KSAWj^V%nf>g8TY{7Yw)2eZoIlZx7}Od`<%)>=Bdp7M5E8Wn{t!cIhREK z7KG2B{D0+=wAQJRwPB>9!mfvk4IVxpAETnwbqs71?fkn4%SEM^q9E-7M$S_y)*Vdl zx%=Ax{o`kP62pW7Pe4{%AYJ#{}FIJaJs|oUZ`Rn_8IvKbG=90#$kEKob@HZQK z_;(UQZ8c`MxZU{g)E@Y6Z5Le?IcfnU^j1gfIjI)19uCK={p+uX^;&}xg_i&1wjmGj z-B7-B9Y&<7Bwzu@72V%aB`=~`Z?nly9g#!NqAD#1Eou^;uIakZ$E=H|6WNB2N6S)E z=M&Wof9H;YJudxbf}8Nh*AmgC&g-1reDJW6hSEL)gb}HOdkvWTitns@GQ~YlX}jym z|HIyr-sxWHhbYA7w;}0fEfWek!<@zd-4SOzvz%ggUgW1M%ztXS5sz7_q2ypUR+gGl;QEp81K)Mk-pyyJjn#O6d3C{x4U!sX>$+ zR-*Fn`Kl}b7nCn&?`B%uuCvqAva#(bow_?uQp@l?2^imhYpfb(B!PUHYERnoSZIs-hYS`DNsmg3@h_LaJ3z%I;G0c8+H*{~AJTKe5o>??# zMCK)V73R++;K*qQ6UGDV4kp+IkeR;-j0Q10A`Pq6N~D!W2rCS6jRuNiSg})XtmqRa zMq6G~`hJCL$}{Ybg3ukzk|-|VKDoOiTV_pgmv7*282LN7iscgRyjq*xJk$n-ePaOn{O_4Xkw3s|X{p}J9LIzVS?gQ?tcfq+ zJ~mNnhLD%0i_(d@#hQC8E=i8Th(KVGEKr4uv`@p*)%pzc(19stwFt!~{$T7X8P*PD%9djqrdZd9EGkJ7IsshpJwCK-{NBnVC3A?#9#Pd}ZPHNU{hr zo}7U3++l~>eB(`i8AS*P1uKFG2w6CfxDK%dZ1Y7%+dX{!5CU;TadDj^K0APl z5Tw&pE!}DZS(I3id6?B@&_IS3M?gY}3{R`Ru>2i@@;~F-^u&!F17C^GzJ_lT^Xk00 zv)9`swVH@xA|q{HQ@2n9UP9FV#@FZ%_n(6o^YJ$fu0CCh?3lbpPzgaZObqHAgNk-? z;TGdIaw9uSUhCqDg;oU_$SAyLX@WCcWS4-Z$}%4X?zX>)r==XTPt+ zzrwx4l`ahC`R=`p_vy<2o_nw0eTLo}d1t4sxru~!6580G3uARq{gpQ_SjMX&7Wx3DiC7%2BTZ-s9REQG%p~T>UaHbKwHw9 zOhWcc`zFUs7W&_65(lO?xg-;V`zfgZfm#RJ@bcvoIxrc0ETGLHm*%kfq~nM^QGlI|4Yxm-QMAsw%nFvx*87A50a*p>LPpG}d!U*Yu*??fCacCg zWxcxKo>FO2sXY*h-g1BJ+=6(+x6Z-L*OysDA!A3K3gDn#xq>9?m7nquGUhAzjb>wY zNEmwMY?ooZa>loVUU`S%Oy0MHkJKyQ;+eRCmNDueUQ*-Vlmat$)JR_1hA+Gd?E4S; zpPTXyUv%|ewmZ$Z$dgiqL4B3C#{cJR*N1Q0L>4*aPU}PUQD|ZgdncHr4g(^W6`q=d7b7xs0JJ}K8HE$N`I=j%%^vXD=5brYCM zoUy=V-ImY7byI10Nbo1)!f-GMv#~vY#(LmLYP4ZGo=&|Au^z1_BHaAU8+Nq8Nn~cB zyVA@;iwn|_(h*T5wl}5V8!I6tW#ki*+!*KfCgQ^&?ptrv7`9MXEM{@c&y)SW?r zS*+RWM*ctwaKDOrHIof+(B+G{Cj_}KY&G|!Aosz-+-&T1FhHT4Hhvp0X-z_(pIgGI zGU^bnb|+qgCFGVnU@l}wwEmT%HRLM)_$~(1K;{C9wqeA;I*Vtz{1LuU7zIre8P2~e z?OxLCj8~8>g)dY*f_XVuircY-!m0U^VIo`GxgRl&4kb)MS?k^u*i3=8;BW?huFIn~ zz`mmu)r|h#WtL(juJ(WMo+J<*I6nX3l%rthBh2 z%*{NV+r4s+VI@0N4l9|#uVl|*C5?LxD><9rn7!R^6@Py@dRWOl{Epej{qE+ke*5kF zpZ#VWNIeC=#Rm;5xrj1;z2C5sQ>%uR{Dhy!?=F7hzA>!i#RG`b5Bx}eO~KFY*P8LC zn^r)jUz|Yqxn0_JrKBU9Y%Y1BAvFQBX!$obX(U9JzQb{{N&raa_)g^d6rZIdZr<5K z;ym8Ae~B(jP3YnAX$s+yb$KMgr#!M|iL7*~3wb=+rS|f;+NCb%@hF$NhQ|}eS_x}; zJi?`}_sgeA$gNPXRxX0#G+?#AR*C8IJ1B*1F^T-hBXh))tNq)4Xy+s_inI$DgTU$E zXY{g-XS3U^Wg-G$19Sgs@zudiry0V`Y6=LWy%cQbX|vZntb+&DvglO)U3`EekGC8T z6xq~jbewos_5xtC@rw6xXg1a_xw1Cltuzb2ZMakRBezTB6&PGr`g>i?U|GvKpSLJh zCy~AKA4~-UO!3xJOE#41LR%Skvauu#iT{HpAO!@tgyK+;ezj7e1{9;=coz`hY5$28 zBG{&}7@4Bi%oYtWE7W+O6D#E^e9l*r!t6ex6r2t!fqvXAI zSyTo`{!1(P?Bm;V(wjWA*^~L!i)Cp50U3=u5>hRG#p8wPa3Q7)LL zMn!(dmo#P(JF*3?gQ~bTbn3gtfxJSD!O+D6w9HYT0;aj#CyR1(h#gyu7aEWr6LNT+@6NrKvUgteT?Q^YrDEl zyp0_nIZle7=uVjl{#xbJsTTK%N3k#5*HE-Q82~WE5B5u z2~Kt*#sR+|5Yc^}dV17i@`?M+i%!Co&wB*oxIjeH5!so$sNMn*5gM$27~_c|hEhDha+=k0 zwwKu%Ry0MA1}c_f#wfxSCJgo&+}r0`OnD#vLM{E0*kmsJ(9{dn=JFLl57@PF#lE;C@X9;p#Zf6}|{`l6+b zU?=|rD;#H^we3p$cbX)0UC(vT3g&Y+A!%*@YFNUSi1O|!pj@=Am7pT2Ym}g`O91}SerN>lI#_-Ko2VE z-0ti*hm{omo<8i0?}cgGF+B7>kH7lqJsfdi{DxB)arj3(kNJ9yVH&yUSTNN*8RwAZ zdj(r2X*(I9D>u;g)h2%)!a^o2|K&?UlX>=fcj?C8Cx_c6i+Jiki*1u1@}OPuhZR5B zc}6TT@4zk2JMb2YgG9&TbGo&ylgr62*gDDFCnPvO%ody1_^^2f$}{35l~S}<^3a&a zyr)yiEGN68op{8QU=*xRH%xD6S<{$4hI2I^so_j?Q&5E8l;mhiWFN;;;ari@@qQ;X z)A5ZYnoAOYtL3PwuNd;HP2BivKi!gr6}$G(c<%o`SCatJ(Lbd1eEm03|Lw&%kLpq~ z%-+d2`4Y2DKL*2%{wElQGOt-i{7r8%iOG$9@hs>fI{q6ilzT}t_kBtJkAr+x|8z`p zqgWcAyozMXAP!tf=BJi5d84$2EQ)*P5+&f@)lM!A->f#wa1Eb+woCg&Y1nw;Z^~{X zrBn$bQ$Gn!ftH$(wVn4`{S`nB03wy!1}GxY-#!tE!sCkO=b!_&-U@#Odv`KrV|ypO zFnfeIYx(7`#>ul}UCZ$Il(J=0bOLj;9Is8vBiwq5F)|584sJK<{>7lzdj9Yfe zNPsE|fyNk6+fTjt=Jbf&AoN6v18EG}ec(+fFk0fpKT$ip_?!Hxb}fLaNW)6xKNbxa zksA}XW){Wd&i0RL@EXdXHgciF|LhI5hF#{N05g<^vydWTwf}(yTuSaC6>Z_^9 zac7&rF7mV+q3C9WC@!DO+Q6LEX~wp*N=y6?rUnOMs|qGhKGDJfV^$Dfr;;c!j9jn0 z{AA5M!#gCBZO1}dRP~KE&`w(xYEhGj`O{|};izMpB5RnSE6>bq{>R7Repfw&7%6rS zf@(=_bi9oj6?cjOul%l)wDtV&Z0y#n3#%%BL;)(&ZcKbX#IGgfWn&+ke_M_id%M~i4MVAvlgbteP^x#6|KURtYrdfIr(n$_WRFv+f?=e6k?>Zs z@~h7>VM%q?@zhlfvpz>ux3^WCo4Wl|$QQ`S{-HqD`Y*k1M`kUAZ=}K^ajXm?PMY(CXC{W|CYQ*u6s^Ey7LF=6wcg3_Z$!;$77gc%h8@eM< z4r5TL!(_xztTW8;bWzxK*kxw8u4)}_glL=iKa!fR@Gsm@Kwwr;VFtBGrAOKpC`UkVxxtFD{H z;Rcn>K$5y#RX1x@Hd|7m%*wd8N|$O7cq?`mGo6Bowa#??bEcHOaVy$}*|umck;lsa z#WH|%6*CKo5ZTrt{>=i!&3gQt|H=d6%#B09dBzSP4|0DsZp@rwJ+#ul4PuGs6r0hn zz8HVp<8d_BXwXQ~R%fl;dl;mx!~N;|v#AjAFT-<~k&B@A&k(J<*0n_lSlJ(=%V%Co zLv=C)ewOCxG)-GCcZ6a375^>gA7}lq1qf?9m<4rTRm6;;rmh&t9B~U?qYwq4B z|0L*Jtv(?n;WBKYy_&u~jJ~~rPNi>e+b7qzSBr>+eS1eb=-Xgbp}zed>oBC$jPS|= zq*Sd*DMk6y%wObxGEdv{_&z%SlY75SdFFqjH7;VYFJ2|Lg1Yn+_>1ssgol*-$=9Q^ zKSVV|Ghh7B7I$4x^W+&uC1tN$MCalixpss)al6uLxf|v@^}4Bs!A?C2 z#40|TLwdo@Ef2oubmNWcS)5V*buVh-!l_WvapLWKv4HeBHh8gLUnGUMTJzs2!co`L z@J>srC?In*X`q;z+3Wv?a3o>u9HArq5!BO>$2t5d+bOe9yHzTzjDKeE;DcV#JvH*; zEK+gXNhnfrt;KvQXJgl5NKj3}pr$^#$#mQu<%)kJye%PFlKCYk^R3~pty9Bku?(>^ zI30i6N|wj48Dc4&_8O+R;QC^CJaGAgT5u429?+%B8Wm~r#643Ngsc46iI%V&*_1mq z^c^Lo%O6|-?+5-LgHH{SADKT1u++qn$eVJ$@RA@lGrZ7~yLmwm6_B{Neer6oOXv;l zZmOMsWVB@NuCQKljDtxj-eV1Oj8k)=@C|uHJ;h6&a++0Xp?||h$0vQxGoi=-%L^Jg z$$1iszje=ZARRkE=(S9l0X3?r+m83ACRb(lE<^-<0%BA6AeR;EMlIqD7xK5xxTOI? zM>vG$`FnvxLI2Th+1QUSG(>*KILvY(E#~(eE;sX%8%OO`1 z$hGJZUUDs}4XG=C4F*Qw`sG@dqi)!{_r2py18>eI=Eees1^v5}&O#?`T#P)BGb@k#KLzG8GW(RWQ1(0>enm&-4S)DnEbubf4D?G%}{W_Xenp1|y8$5@Q zH9s?X1)(Bu!gs|RUf~!eo7A!ANV7!7=Awb_HzBl1?aNdDR7B!GkA?#Q3r_0vnLR4&>`~iN z6d;Rk|Drug+wpI872Bh9l)pGOVXN#>O92~XkK*3H^Ky;5l+SWQ>qor&hrQ79mg|L^ z@X+yZdUXp+>yhVD56ExqJ`wEJx{#% zzs_h6qo7Dwe`u~T9GLP}_%A=L1>hz}Xm0`|VPs<53s>q6xt3eiXRFeumiu$gcJ{1h zu)r~LK^JGZlXDuQ^_x>u+u6(EzLOv3_~;OTa(uK`A>NXcros)H0BwZVK(6Tpa(lqU z5(ucFm3|~Zz6RtO<7|i~=g`&Q0edZ&maF~VvM&zN+eph1Pa?&LS13|Mz#@@y)0>V~ zkI5^+op*xcnsfDZHc)1+l}EV>qy^sgva~?_wNzZF0>;||Ni&7^&_>hd&~p}?D<(uC z04G?L4&|Xoj8u41EU%jwB|iC=Lv3F}jY)aer;Kf&1zEdv*i4G&NV&Q1E&iHYF${lt&AqUaYYm5Xvh&_Lndp2^kxo1ZX0_LGK{+o^Q z08wHW<`I3DmmH$%8XF&>hzV9WhG@0%Y#~zNaC!E1N1|;VUOoPYtBaJyZ0yB+?K2B& zpI%t|e?+FWqUY9i*NFi-m*Y+2U%qY&GAGWSN3w*m1*w?D*7~K~nrGyYJYEsX#Fzd6 zv%ZMI5C&W8A7ZUY60B~G7k4}E$vMQS|xK4+_(FKO4U!mg#T9C>FzUX$R{ z9Un{!B)CZ*8^bC!9JbRPlW2vOkV>gM4Oy+wzPJ#xo)S)eVuS0ApUUMYj(ksIVv2V= z`6c{8du~gP8{R?3%CYTwDWph=nalmU7o2QqDwHjeU!EB(FC|BeN0%4j^#xIq$7{iB z4zG1+Nc70^03q&U1eq_99*w_;hOE!>G}NF)CCaJz5@1zbym7wL<2X$RaT_!AA zR%)c^AfOi9%+e=PX{H&V~(BA!SYQ_mBPYYpeawWvLP_E=K z4if(pj3WWYHDK)5tli288t|9wQZRy~(d-Y8`oEk{hv6dQM$kJe@-2LoXD?aCV8Z^v z0qvz~A>@Fst&LK$B4=%t6QmCvt%B<)IM-hiRyMbW!s`E~nC#E7BlX7jP~Wg1ATL+= zad7(*#pH2$a5}|gV{naf9}V4_Vq$CJhx5zF>w(LStZR62s2l%&VLfo3-~Eb1Y$-RQ zVrJdFb&QOyt)uP#7#ECK6!ggRXR=|x2O?;_MWkG$*9tUlOCQd|04dOQPIiq6xVC7B zLw`g!3RJPsfbOgO=69w0re!VDw%neze8$;KExE!NN8QICJ*?#4{N~gTE7>7FtmHO+ z!;TqNav{I^#ITav`F+6e@rGd~8;%`T^4&?pN_K1MhavZ{$lp zxBxV=rz`p4-6=V`)snN9<`J=y%P9FROB`JCGXjbpb&L zB8xj}RotIzYz3_vw0hs~@67Yu+yvVH`@VjB$aCkJIWu$SoHJ+6oH_HKAwQUv-5@I< z?CdQ@8|2-9T)aid+h=8sL>%ND1)0^x7O3vy#va%x0`h806gC{np5u4-A&LA+Ny+?| zcLB1Fifm5LZ9O4F(~*V@skbI6BeWjAaf1|z#{5pv(GtdZ{< ztj$9O8T-o?AiqJ5ZnSYAkQZ5EPuh4mD{JH)2l<~n|3}EzW@WcFqw+@1-y-CitgMj{ zf*f#z^VvdXvBiS;%l3!GU-(p=6xSBrkarW~U#)CAV?Gz&t~1|WZxh;$#d9F3fcH9Q z+8kBSXCiuzSW8;XB+Bx;iQnz~E;D@t#b+o9>q#ha32Y;qdgjkVpTG{1&n6%9jcPud zU}y0ERcy&6UBDu_Kde=oRwQtWHES1pjk8J7-sWtkDui<5PIv(LO2Z#&cn&tct0K{- zQXSiBcDr(kR1aS}Npr{|z)kw=5P9%lj4*KU9|Izamwpba`fK37>dJbw~U zKDyGib|z^Cs*add+O1UrD~w<_w|&}R=B!qHAT$0t^G-Hz_Qo)CM{jRsMf9mz7T-$# zm!=oJq0jRKi`fQ^{kLbDgMMEkAC=0p9pB^2aVeGW@tUWll!m^?l{BoI@9`S{q0#Kt zMQlSx`~O0$@M37qjDYQ*TYRIUO7HE;8J&jE?HQQwuVA};e7l>eI0okSQYtx=a*yHW zR5sQYk#7z8Fv2=UaJ6-xZP~xGxw%rUejzb~dxjV@ZWn8(^v#s+Q3dm`e{{DhF8N=o zkiGz{IN1o`9h^YWgv;K)ns}g%vA;b?##pAi{5yF|-Hoh79L9M^IW&80 zv}Fp3U3c1iN#%_|B-=i1TeiuL;d8c+pd3?%q_KskS8`R8j^8(T?hw7+^wwmgeXh1W zIn>zn@?|=C-=6-k(j%}<qYfBFay0$~mwOikDM#h!4zva4jB<2bq(D>*BfNOe<{N z+0wGwcR%)^)EP>pD>7)f)gyzqGjNcGiK|2@foZa=D!F$hb|un>2I+GZVX*{92u0aff)*pnAFa2(6~k3KJGYtNxl_$?&*C zB6$Km-!`EG=+Hy#8S)6yErLY08nS6b7`j((WX+Y_`vkKYy#_^ea(T5nUqmD6NV|Xe zb}p39zUfoa|EhACa#im7PmvxHOO_ z+WMejw7$;B^G)mFzyY*#fiy~gn^8SkcKQB&a<*^eWOe>B-a1%z=HI@by0!TH+08?@ zZ#b}vSD7UuD=x7SxrP=7nq&~^EOol-8lQcAR|ChJBXd+B+jSQVrw+DumDP$|lZgrJ z1zM}ul|QFY_u5B*NfU)t!}e&XDSxiOK2D=c1TWQY8B(Ymu;p}p*a<<0g4ixfd$KZ2 zqy9oNhVC_~KeI)duFM;Dbzbx)7osTAc`@H>O33+7-i$7gH~8YDuCx8j>Lw8vgEkvR z9>1LyO>bcCqlr@}vrwf2SNw6aF;ucLO(akKccRZL*#0MtB+DHFo6N7HDD&Wm-qELa z2x(YBvYh0;(%@+QBmm7z`Vx~3And)AzCDp0CvrwkwDuuAoUc&oBb~W7GZXhyVGT=R zxs`K-lWsY32Zo@HRqJ3$Gb`0PUtrT}LN-lgVj?)@CgAFD@7MI3WPDNgJ%q(;9HahI z>uWAY66?{%hc6NiU(@NhK>64~43@3Iye-9Jr9Xe6Xm%Zu)7XEA&qe3BjIwNiWNR)I zp4+YP;tm3FtoE;>Fvw(FH1AdOyv9AFr-?Vg$&8HuMEXJ- zm)D~i=Y3tK9xQITySZ4zNyOD;mb_#XVSyuaBSJ~mhK`M?Yh3g`t)DYw#H*CBl$r>KyM|d)*(lqPE2PRvwuh0so za2~bVjRdAznr7QLv(vH4v1*By_S$vsO8;;SB16d|Eg4hcTVcxKE=A-M`DlWgJl8<3 zP4&?inpsBe&;c!SWaM;TqZeA_8cbWQU6M<8bEHScs1?S*;}&V40p1Gm&4{G?I0r=H zJ#9q7Xr8PxZH@DS5#_)rY&j3*v)Hz7Y@{$KrepP|xVh^zZ0}N84&lDW+AWPr`hv|y z{$Xl#q!WqB6V{;%c_>k+2jF;KY}OrN%$N zMj?jAQ(Jco4d})Rxe&i8@GJcZPnmHqNpr~kK9sGu?5!Jupt5XB?V8at7 z7w#4A-cR67Cra{n;)U5KJWn#B`}|X>b|&+Gz#X;cc@oXq`&&!IoZ&D%Bl_q*ElE)I z5}fR5spD^?c3^#jNZE(oQ6kJOdO!~|Hu1%#j0WvPI4OmlZAtH{(v6cU%;jRGf0Yfw zB6sWWLjW-uA#py6dv+7c72GPFIH3K65PLL zbC1aqXbZd(7Qlo6izscfkTLZCfaT_XTh+9os7T>xn;(|r1#0Z~@ z{BKq`MlEvMMo5N#KUY2JAl^xIA&|9Fo7^g5G-F-Wb_>+jzQAU-#tA+h6&pD)_BCZFUgox>rsJgQ^+o6_D`l8zNJOE3$>Iw_9VZeTn;hhHdi zv3IBfZCv4FQWK@Su@>cY&1%0_tMs|ZHD8dX*|^<5C01NZg2%)~TJ3~Of~M7ON$|9tVLyXGiTrd9dD_OU zHM#1P7?Z2;c`{aFdrK=}{B&$E@HJ;VqUnnBzbc3HVlUrKgbZ*GnHjx_6^QcQNnWRE zGh3DjRlz*co;{D8BdpnO&lcyA)0R8z{F^qJ&xwK?1xu#MRbWGxWa^sIvurMY!$}L%7bk4t<$A&45N${3;_MjK>r3xTd&Y zsc8}%Cl(T*C}^F(^06No&Jzo?gZ;dFhh5hBkMnNsk9Zll4eXsypc}oc8^kee6+Wa^ zY~#9eDz&<dZTgL&=;_CygAL#FW8w$EQu_1Mn_UO6^!)VL3wr>~N zP)C_;ftU(Dyu}A66UWeYRG+ZRmr)SS&jc+r%UYbs!T!4Wno`>3ON~x;bS2%|pH9p^ z{_NbGJ?nCFe#`U3uezQg=jP_bc|JWaH)q!QxjDbTAUEg13As7{yf8QCtBcrEx!65N zT#}pfI?q9uvM0lnwSTfVdtD1zu+z-QrW4sed7$_|?4Mw4%Ior<4|Yc)-<~aWy{+>` z!RhJdQW7>aj6trf)5m1~Mh+SW=*imuu!3)*gqqSSd9U?mu$(-LK{oyLP59tuvI@NA zX}*>tyQ4|H)&YN@3wUVPMGbZ;eaSkb_=UwKPwfu7$icqA!Csr2LHK4ksmO+1 z6vF=JpR?M%M|ar84)(SVcFTW(?T^dCNpT4KmTcHBk|hf#B@XtVNd+fE{|oF{*|1AO z*hgl=zAziM7dhPl{g|EOAp2&>I4TQhWPg5mqrzL0w3u>Kw0(A_hy!|W;%pgzl-Rrl&U9pky-UutbCj%tCy!6^*u3QVn zw~qy3sB0i!vf~~!FQtSTf`fm0oTt&$RAd+3P-oNq8vaK-FUjjJ6qqKHhIW*mC3W203yrD37xmkkTN!+(>wLw z&YXeZlP;5S81$Y#Un1Wj-T$PH zYY%^kv@VOC%3(WkVg!>73&;N~#X&dejK+o|qY) znF>Okyy^d{88w)oqD(t&U|DaiE21rPfzoyUlr~oygp*M#`<;>?UOG-!$ABa21gf@r z!f+n=3Q9Sg*wvxC0F)s1T}ki8v(;M5S#P;&G^!hGHD+#VC;STsoyc)~Uver_+ws`+ zxA%|1rvh~w182~_RCveo3Ngch20eLa5?eH7%7-7@W(1$P%qWK`1c?P}jJeSN}TW=j{)NX1L|2f<469w4T$EmEQYW zH)rl2qNDo48s5$%Fzh;P7(4SA^{VlF{6C;iuTyT{Cwci##&ms=1D1~cfup}c{UY79 zW2~i{F#LumKGE?o4fqc=NhMW78@AD@W!2cxqE*+dk$CS!pa)&p6uHqs7@OW~E(_3e zDH$Ak(y=Qj3ViTJK)9vc0nd$AL*S|M^cq$~yGF0?#7N6BBAed;!TS9pCe*TMj+e2xUAQ%#vP{7hE)D_O*( zF0pcvHKRq&nzb|us-VVHWjp=HDQi}>v-yMx=6>>qqHR^+7=y4Za77Azu7Gp-53&4o zQxm|Ylp=?dfn+ao*^7P5CN8_=SqL}si|p(aKUlw$`G-s~!Cc~uB)?61W=zPpLcf#wFI{6u&ht*~mnju(`4v6Ml>A+fk~i7!%ng ze953{g0f|m5F1nzqVv z6>pyFiR@f|uv`baT&WYyq7=D>M9_9C0TAFEupVGdxL-TuyN*#XE%@-?Iq=7{2vTO9 zvgmEgSOd3iYevSmI`##?l|Ld;h`W)0GcOFs(Sn=3YBlp>WG^M##DjsPnpkG+8m#i) zn+~(tR&-F#*h%>7@vw2HU|+Z>6n7=;Wfm(LzL+`{#utu?>CSq8l>y@a`J}xw)9ub~*5cmA zqD1p@B*iaF>=wr4t+t)V-HnGQN^9~a9qe{f%8U40lt_*^Hqkb!Akn-Y$M6w_iMDH5 zlAl_VXgj+w;hj;~Q48~0P%eMm6pfd{N;DKP7c5i*`RE^UO*r8+?jxd!Ox1ObliYQU z#jb5Bj@?-Srd3>jP#){;5VsvU*F4XGwHIwt#Q(%5#eENV8wXMx(y@)l5`dX$@fMd0)D*C93w zC(^MND)aJ>R6UqH5AI&eSXWC^P>TY?4*)STu)y@I{XZOr>#*X95dR7|!v~;4G-7k6 zIufOJG5zajk$#4X%`hq#PL*3weg%|{{oQnkNL=l&m8}lU@8}HnjQVO!D{gv52P6JR zK0jkKP$jxh5__0DaOZ1NotC1;UU020;WHI+@_#(}?i4!0MBJ7B4Tp+U6p4aG0}BQ;;M$yM zi?$Bv(Z8$RtRR*sbMrcy&S#|3ho`=Wf@EKX@ViVtEA-KO%cT6ujLWMvB>%|r=u;=7LsS+fUirdL z-y4`a$Q?OMkTeZD8&qUZ2YFjT&UT*cf}FD%?b~EnPt`-2#&K z?P?#FGYdwn2aE{675hf=ql;v|I>8_p3G$ZFzlt2;z}i8cL=DiNvecef`hCy5a9H!u z+KFdVqSe*3>|fNhW#Sy4RoamdvqEK^NGR{_EV3^Zq^>^DsOF&)td2cg9XEVU9ohb} zT@;e8?COoHV*+*5tBwcm%Btfn^7X7^^2BlU<5_)4=5M2ZYez5d!ejg!St;7#$aC{zv*rjPYfqzQ{ug(Pu6Y))4b# z3j}PpYmOcVNhEaF&^n$9R`+)Q>gX|*$v@>uNu2Rzv6ITnHer{vP$!BIjmxfIo~%y~ zdAG9d+V$0K7gIaIeYV~THKpw;HVMD3Zo76-b&@L*+s5fx&%^%dxVb!Q+s4(TWAEWQ z$2vLTw3k?}>)s)gY>uiVFqMK86i~U}K~SGWTRhR6J~x`&ncB4n^DrpTRe+6d2WI)+ zhF*n?!vE)Z5s{0Icu(JOFZ*uZ?8eAZytoaeo;OC$pdhq!SRFW~W`MYv0tY<7J;&f` z+)V>Zy-)8si0)5za7t-|JXf1H21M7l!MKa$tF7Eea}5N#in!|uMSR)yF174NiH`hw-%PzFN;6TB(#x%xRC z3INR0Q|DZ{n4({hhW9jiLi*OhtuKdho^V{I1N+jF8R>R#5KTT#@0)+_)o_;GkaiMF z6-On%-z$qsZnK&kl}zd#?mSN9Ep?a{H~5S49_~C2kh1`)3Ri6ZSa{`^snu{t^YXiz z)49>>AK6{F%m26$Cs(WI!(Ue(FsuFZ_6W<5a^=@&mnZn{tbJVh-R*Pa$qLy~DVns z{?V2*T>(<&>@B5@d;Bh6WxTIZ-b3VFZtaWuTiy0!+WHTKdF*V`_H?W_aD>6#f=nNA z*U^H$F!J(vQ*S!y&!F}N5Adc^%i}Hh7v%VtkXVVi8jI0m6!1p308h>m>;hxUEn-J~ z9`|vg#RD6r=to<0EGqTnZq~nf0j~#5^#4uMf+VV8s$b)$l++e4gu(b7*=0m`o4eXP z0bn!IOj?ld2B0_p+&duO*?6Hi^8KhNi+mMNBNTE^6TH-Uct6Ek>MHrIB=g7f9+EG& z2z4XhD8K2n|NJf53qJ`F9iQj_g&#A%x|udK9BhK>HC!$j20XgrO!aHivDpkHG|=)F zKG0vOnyLR$CS5AFJYE-}tevgI53DXIS#KqO0d=e7WXm0|r?ez2_12~?A#Fz5XSR+e zZzWYLjs%_uEn1$HIZ0T7m+nx~bQ0MD!YYWUR&UT;oaM$IiSchLPm%x3Lqz5f}2&8~Q~jGrmHTVy!WUe@!TVFI;ov@gn`>ynEl<1^!Twjd*=J_E#uT?OsG89jf=M zKLok7T0gi?jL*Tqrye8=bkjoL>nRUi2W4BrvKx;kMA-}C_kQOOD{SQCnTTDPisoU-A5 z9^{-}B+1=W8gIGs<{m=h{ao@Yt%Uje}3eytsCjRxo_ zH^5WZ+7*iT%!P%cV^@3=oo+V<@i#eJT-};(+*#8QJoeXPg*YieEQC|I`AQhBlNBR5 zx;-5mw1*p3mc%*DQ`hxsIL4-mdo6$DY6`DUuRuF7q!05fgttBtJVBmE4P8{*cPG_->`!(Jp#pIh#vZeL1e zK~2XR2RU$m6BmK<5ZtrB=%ScbVa34dMc^(Ez{#Eh*{P90oexr&_1`g&4VN%=OD`oV z-MIN3*d6TZBLDS5^lueuv-?@Bn4y!Bvt7xx7jLKEMc`X!peBckKPvwt43-`ULV7~ zy}1*eqL!bDIS^s;QgXD+NQ5dyPp%D|KmR-Db{e@izw)sn2xL^W7QtX%MX(A+ zpm)?WcXP#Q@8p=B)3He~iKc{Kl2v)g+hqA>q`zW*u(Oclf4Y!{MJv{-9Oo~BLW3zZ zBR!X+OwlBU^sG6w3ZFkWhu*t$)*O0ssYt`AfDN7D9C{URsYA?5FnBEuTdl`1W4tv~M}$F;AU>tA(|bfpzwuxa)Z zHewM3eP|5~HnY+jn6!$-{pfCh0qd3q|s0e#{!-4!4Un{A1FsziZQXNc5J| zsx{DYhNur4NcF^6%MjcSv;xFFOu??z2_CFkn_QjQk7b(Dc3M9!B-FKnpX}lUYb%|i z#R##;g=!JzB20&p(;fLeW(8$po;#sf5+qVeZVTcpwb_|wB-$FcN-rrLH|NTm0Lt>^ zh+U~B-H3J_*~U8Na;sx)Q!_m{nI*nNBu6j*1@xY)!nH%UxGLOlH`B6b3b(81PS!dv z2Y@!DQtfvNRQpZ$J96FI92;?6_Qn=D?vz@6@jyonFLDfMMtUYVjV8YgWcZS!7hV5Y zVSznI&xJ|m1j2zBc((42CIehmIyXNqakbcXjo`m%4-LnWOwTtLj za_E%;qo4_t=%OG7TNHF0Dp&;)ZDnHdhkZE~G&A}X`!JPxvLbyyGs{@DzpF1G1ys|J z6qY(t*nek8;X5OR^pUjM)?0yU)L5IUwl>7+c9z^sw8aC#nVTGg4EzP;qy`sRX5q+G zR0Q=Cd@*TmY$s{HI`uuMMuhwqkbS_XC-(a;@FY?QOEtMkPNoVH4d>c^E)3Ij){yULe zgozAh;pR>VV5YHT$7qd0od zcm|P7_tE!IdyIS_q1=(&_1We*52fqrSd+dE*H=^tP5Fz&N?b2`l`xZx7lVEn&Y}T>`wXkiyEDXne+Rm_Y1sJ2 zF_;|xI-5qv^7fLP@)NUPyQY_06O2oi-U@$9TC= z1)?n{GOJLi=kodd(U}RL6t0OZw+h(e{0zfEP-D`OwsAZ&TNk$&E&d58HieR%qwyM?H-1gTy!nW^b zZ8wt(%e~R8yjzf3jLfb87NS2zE#U*Ru~xL7?qK6pXzQT?gn&xNu3-o(lIM?2G1qHI zW5b`&!CgL#G5-(D&HhKdNH9$@kZybpFonF$0Bk(fxXy7$3 z@2gFN(#2C=)Z&^H`!#hN*NaQpb8enmL>J6%&}Np6g{&E9-pf^O&&om&z1_aSX8V&4 z-3)MoXfMhNcRKxO#J)|wgcC$;OHx`Xx}FS((1OPABK`I!-5Rpfdnlw>2n$5K ze%)R&7fvVm#(KH=+EtfRrb!@cVXz@FZ0G5C;C&;b+j%5&?5s&sD7r|s$30inh8-WX z6K_S_4+9ruuQMyXf3wsavZ$@PMpEXRhf}9edC;NA6TM87+8s#Yi96&(mq0bi>pIn4 z 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