TCL Blowfish behavior

120 views Asked by At

I made a small script that zips a file and then encrypts that file:

#-- cut from proc --
set outfile [open $out wb]
set ind [string last \/ $in]
set in [string range $in [expr $ind + 1] end]
zipfile::mkzip::mkzip $in.zip $in
set infile [open $in.zip rb]
if {[catch {blowfish::blowfish -mode $mode -key $key -iv $iv -out $outfile -in $infile} msg]} {
     tk_messageBox -message "Error message: $msg"
     continue
}
close $infile; close $outfile
#-- end of cut --

To decipher and unzip:

#-- cut from proc --
set outfile [open $out.zip wb]
set infile [open $in rb]
if {[catch {blowfish::blowfish -dir decrypt -mode $mode -key $key -iv $iv -out $outfile -in $infile} msg]} {
     tk_messageBox -message "Error message: $msg"
     close $outfile
     continue
}
close $infile; close $outfile
if {[zipfile::decode::iszip $out.zip] < 1} {
     tk_messageBox -message "bad zip file"
     file delete -force $out.zip
     return
}
zipfile::decode::unzipfile $out.zip $final
file delete -force $out.zip
#-- end of cut --

Now, all works fine, except if the deciphered zip file is bad, meaning that we've used a bad password or mode. I would've thought that catch {blowfish line would get me an error, but apparently blowfish doesn't care, and will just blow garbage into the output file with the .zip extension. In any case, in the case of the bad zip file, the script is not releasing the zip file, and will give me a permission error when trying to delete it. If the zip file is a good file, it will happily unzip and be deleted. I would presume that blowfish has the file locked but won't give an error or let it go. Any help nailing down what I'm doing wrong would be appreciated.

Update: Run the same script on a Linux os at home, and it works. At work on Win10 was the bad behavior, I should've noted that initially.

1

There are 1 answers

5
Donal Fellows On

I've found the problem bug. It's in zipfile::decode::LocateEnd (called by iszip and others), which doesn't close the open file handle to the zip file if it throws an error. I'm not quite what all of the conditions are under which it throws an error, but one is definitely when the ZIP index can't be found. Which would be OK… except that the handle's open and on Windows that means that the file can't be deleted (because having a file open locks its directory entry; Unixes don't typically work that way).

It's definitely a bug.

Fortunately it's using a normal Tcl channel, not some kind of complicated C thing, so we can work around it.

# Assume you have Tcl 8.5
proc safeIsZip {filename} {
    set channels [file channels]; # Hope this is short!
    catch {
        zipfile::decode::iszip $filename
    } result options
    foreach ch [file channels] {
        if {$ch ni $channels} {
            close $ch
        }
    }
    return -options $options $result
}

In Tcl 8.6, you can do it a bit nicer:

proc safeIsZip {filename} {
    set channels [file channels]; # Hope this is short!
    try {
        return [zipfile::decode::iszip $filename]
    } finally {
        foreach ch [file channels] {
            if {$ch ni $channels} {
                chan close $ch
            }
        }
    }
}