M migrate_core.tcl => migrate_core.tcl +7 -5
@@ 5,11 5,13 @@ interp create migrator
migrator eval {
lappend migrations 0 {init plugins} {
-- init plugins
-CREATE TABLE plugins (slug TEXT PRIMARY KEY,
- version INTEGER,
- hashes TEXT,
- trusted BOOL,
- priority INTEGER);
+CREATE TABLE plugins (slug TEXT NOT NULL, -- corresponds directly to manifest
+ namespace TEXT NOT NULL, -- corresponds directly to manifest
+ version TEXT NOT NULL, -- corresponds directly to manifest
+ hashes TEXT NOT NULL, -- dict of path -> hash
+ trusted BOOL NOT NULL, -- bypass security checks for plugins with this name
+ priority INTEGER NOT NULL, -- load priority
+ PRIMARY KEY (slug, namespace));
}
}
M plugins.tcl => plugins.tcl +111 -14
@@ 5,6 5,8 @@ package require sha256
# - name: string
# - slug: string
# must match /^[-_a-zA-Z0-9]+$/
+# - namespace: string
+# must match /^[-_a-zA-Z0-9]+$/
# - version: string[]
# ordered like {1} > {0}, {0 1} < {1 0}. {1 1} > {1}
# may set:
@@ 24,10 26,85 @@ namespace eval plugins {
variable plugins
variable log [logger::init tclircc::plugins]
+ # generate the script to exfiltrate data from the manifest
+ variable mf_required {
+ name 1
+ namespace {[regexp {^[_a-zA-Z0-9-]+$} $val]}
+ slug {[regexp {^[_a-zA-Z0-9-]+$} $val]}
+ version {$val != ""} }
+ variable mf_optional {
+ description 1
+ author 1
+ license 1 }
+ variable mf_procs {
+
+ }
+ variable manifest_reader {
+ set manifest_dict {}
+ }
+ foreach {key check} $mf_required {
+ set chunk {
+ if {[info exists %key]} {
+ set val [set %key]
+ if %check {
+ dict set manifest_dict %key [set %key]
+ } else { return -code error "invalid value for %key: $val" }
+ } else { return -code error "missing key in manifest: %key" }
+ }
+
+ regsub -all "%key" $chunk [list $key] chunk
+ regsub -all "%check" $chunk [list $check] chunk
+
+ append manifest_reader $chunk
+ }
+ foreach {key check} $mf_optional {
+ set chunk {
+ if {[info exists %key]} {
+ set val [set %key]
+ if %check {
+ dict set manifest_dict %key [set %key]
+ }
+ }
+ }
+
+ regsub -all "%key" $chunk [list $key] chunk
+ regsub -all "%check" $chunk [list $check] chunk
+
+ append manifest_reader $chunk
+ }
+ foreach {key _} $mf_procs {
+ set chunk {
+ if {%key in [info procs %key]} {
+ dict set manifest_dict procs %key [list [info args %key] [info body %key]]
+ }
+ }
+
+ regsub -all "%key" $chunk [list $key] chunk
+
+ append manifest_reader $chunk
+ }
+ append manifest_reader { set manifest_dict }
+ # generation done
+
+ proc vercmp {v1 v2} {
+ # normalize the lists, check for equality
+ if {[list {*}$v1] == [list {*}$v2]} { return 0 }
+ # compare every element
+ for {set n 0} {$n < min([llength $v1], [llength $v2])} {incr n} {
+ if [set cmp [string compare [lindex $v1 $n] [lindex $v2 $n]]] {
+ return $cmp
+ }
+ }
+ # they can't be equal, so the longer one must be the newer one
+ if {[llength $v1] > [llength $v2]} { return 1 }
+ return -1
+ }
+
proc enroll_interp {tid iid} {}
- proc load {dir} {
+ proc load {dir {allow_internal 0}} {
variable log
+ variable manifest_reader
${log}::info "attempting to load plugin from \"$dir\"..."
@@ 43,6 120,7 @@ namespace eval plugins {
interp create -safe mf_exec
# prevent escapes
interp hide mf_exec package
+ interp hide mf_exec interp
# prevent hanging the thread
interp hide mf_exec vwait
interp hide mf_exec after
@@ 51,7 129,7 @@ namespace eval plugins {
interp hide mf_exec read
# this is more than enough for any reasonable manifest
- interp limit mf_exec time -seconds [expr {[clock seconds] + 1}]
+ interp limit mf_exec time -seconds [expr {[clock seconds] + 2}]
interp limit mf_exec command -value 10000
# untrusted code time
@@ 63,22 141,18 @@ namespace eval plugins {
return 0
}
- set has_required [interp eval mf_exec {expr {[info exists name] &&
- [info exists slug] &&
- [info exists version]}}]
- if {!$has_required} {
- ${log}::error "couldn't load plugin: missing required values in manifest"
+ set mf_valid [catch { interp eval mf_exec $manifest_reader } result opts]
+ if {$mf_valid != 0} {
+ ${log}::error "couldn't validate plugin: $result"
return 0
}
- set name [interp eval mf_exec {set name}]
- set slug [interp eval mf_exec {set slug}]
- set version [interp eval mf_exec {set version}]
-
- ${log}::debug "manifest loaded: $name ($slug) v[join $version .]"
+ # hooray, we have the manifest!
+ set manifest $result
+ interp delete mf_exec
} result opts]
switch -- $untrusted_result {
- 0 { return result } 2 { return result }
+ 0 { } 2 { return -code 2 result }
1 {
${log}::error "couldn't load plugin: $result"
return 0
@@ 86,9 160,32 @@ namespace eval plugins {
default {
${log}::alert "unexpected return code from plugin handling: $untrusted_result"
${log}::alert "return options: $opts"
- ${log}::alert "THIS MAY INDICATE A SANDBOX COMPROMISE!"
+ ${log}::alert "THIS MAY INDICATE A PLUGIN SANDBOX COMPROMISE!"
return 0
}
}
+
+ set pl_name [dict get $manifest name]
+ set pl_namespace [dict get $manifest namespace]
+ set pl_slug [dict get $manifest slug]
+ set pl_version [dict get $manifest version]
+
+ ${log}::debug "manifest loaded: ${pl_name} (${pl_namespace}::${pl_slug}) v[join ${pl_version} .]"
+
+ set stored [core_db eval {
+ SELECT hashes, version, trusted FROM plugins
+ WHERE slug = $pl_slug
+ AND namespace = $pl_namespace}]
+
+ if ![llength $stored] {
+ puts "new plugin"
+ } else {
+ lassign $stored st_hashes st_version st_trusted
+ switch -- [vercmp $pl_version $st_version] {
+ 0 { puts "same version" }
+ -1 { puts "older version" }
+ 1 { puts "newer version" }
+ }
+ }
}
}
M testplugin/manifest.tcl => testplugin/manifest.tcl +1 -0
@@ 1,3 1,4 @@
set name "test plugin"
set slug "test"
+set namespace "aleteoryx"
set version {0 0 1}