Skip to content

Commit

Permalink
Handle symlink extraction on Windows
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Dominic Della Valle <ddvpublic@gmail.com>
  • Loading branch information
djdv committed Apr 18, 2018
1 parent 0cb22cc commit 1399169
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 2 deletions.
40 changes: 38 additions & 2 deletions core/commands/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"
"strings"

osh "github.com/Kubuxu/go-os-helper"
core "github.com/ipfs/go-ipfs/core"
e "github.com/ipfs/go-ipfs/core/commands/e"
dag "github.com/ipfs/go-ipfs/merkledag"
Expand All @@ -22,7 +23,7 @@ import (
)

var ErrInvalidCompressionLevel = errors.New("compression level must be between 1 and 9")

var haveLinkCreatePriviledge bool
var GetCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Download IPFS objects.",
Expand Down Expand Up @@ -244,7 +245,42 @@ func (gw *getWriter) writeExtracted(r io.Reader, fpath string) error {
defer bar.Set64(gw.Size)

extractor := &tar.Extractor{Path: fpath, Progress: bar.Add64}
return extractor.Extract(r)
extractor.Sanitize(true)

var modified bool
extractor.SanitizePathCallback = func(input, output string) error {
inBase := filepath.Base(input)
outBase := filepath.Base(output)
if inBase != outBase {
modified = true
fmt.Fprintf(gw.Out, "%q sanitized to %q\n", inBase, outBase)
}
return nil
}
extractor.LinkFunc = func(l tar.Link) error {
if !osh.IsWindows() {
return os.Symlink(l.Target, l.Name)
}

err := os.Symlink(l.Target, l.Name)
if err == nil {
return nil
}
if err != nil && haveLinkCreatePriviledge { //any error besides link privilege
return err
}

// otherwise skip link creation with a warning
modified = true
fmt.Fprintf(gw.Out, "Symlink %q->%q was skipped, user does not have symlink creation privileges\n", l.Name, l.Target)
return nil
}

err := extractor.Extract(r)
if modified {
fmt.Fprintf(gw.Out, "WARNING: Extraction output had to be modified in order to be stored successfully\n")
}
return err
}

func getCompressOptions(req *cmds.Request) (int, error) {
Expand Down
174 changes: 174 additions & 0 deletions core/commands/get_sanitize_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package commands

import (
"unsafe"

"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)

const SE_PRIVILEGE_ENABLED = 0x00000002
const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800

type Luid struct {
LowPart uint32
HighPart int32
}

type LUID_AND_ATTRIBUTES struct {
Luid Luid
Attributes uint32
}

type TOKEN_PRIVILEGES struct {
PrivilegeCount uint32
Privileges []LUID_AND_ATTRIBUTES
}

func init() {
// [Done] Has Developer Mode privileges (Windows 14972+)
if isLinkAware() {
if isDevModeActive() {
haveLinkCreatePriviledge = true
return
}
}

// [In-Progress] Has UAC privilege (Vista+)
if !haveUACLinkPrivilege() {
if !requestUACLinkPrivilege() { //try to gain it if we can
return
}
haveLinkCreatePriviledge = haveUACLinkPrivilege()
} else {
haveLinkCreatePriviledge = true
}
}

func loadSystemDLL(name string) (*windows.DLL, error) {
modHandle, err := windows.LoadLibraryEx(name, 0, LOAD_LIBRARY_SEARCH_SYSTEM32)
if err != nil {
return nil, err
}
return &windows.DLL{Name: name, Handle: modHandle}, nil
}

func requestUACLinkPrivilege() bool {
procHandle, err := windows.GetCurrentProcess()
if err != nil {
return false
}

var originalToken windows.Token
if err := windows.OpenProcessToken(procHandle, windows.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, &originalToken); err != nil {
return false
}
defer originalToken.Close()

var linkLUID Luid
if !lookupPrivilegeValue("", "SeCreateSymbolicLinkPrivilege", &linkLUID) {
return false
}

var originalPrivs, newPrivs TOKEN_PRIVILEGES

newPrivs.Privileges[0].Luid = linkLUID
newPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED

oldSize := uint32(unsafe.Sizeof(originalPrivs))
newSize := uint32(unsafe.Sizeof(newPrivs))

if !adjustTokenPrivileges(procHandle, false, &newPrivs, newSize, &originalPrivs, &oldSize) {
return false
}

return true
}

//TODO:
func haveUACLinkPrivilege() bool {
token, err := windows.OpenCurrentProcessToken()
if err != nil {
return false
}
defer token.Close()

var linkLUID Luid
if !lookupPrivilegeValue("", "SeCreateSymbolicLinkPrivilege", &linkLUID) {
return false
}

//TODO: checkTokenMembership()

return false
}

//TODO:
func lookupPrivilegeValue(lpSystemName, lpName string, lpLuid *Luid) bool {
mod, err := loadSystemDLL("Advapi32.dll")
if err != nil {
return false
}
defer mod.Release()

proc, err := mod.FindProc("LookupPrivilegeValue")
if err != nil {
return err
}

//proc.Call()
return false
}

//TODO:
func adjustTokenPrivileges(TokenHandle windows.Handle, DisableAllPrivileges bool, NewState *TOKEN_PRIVILEGES, BufferLength uint32, PreviousState *TOKEN_PRIVILEGES, ReturnLength *uint32) bool {
return false
}

func isLinkAware() bool {
major, _, build := rawWinver()
if major < 10 {
return false
}
if major == 10 && build < 14972 { // First version to allow symlink creation by regular users, in dev mode
return false
}
return true
}

// TODO: Replace with `windows.GetVersion()` when this is resolved https://github.com/golang/go/issues/17835
func rawWinver() (major, minor, build uint32) {
type rtlOSVersionInfo struct {
dwOSVersionInfoSize uint32
dwMajorVersion uint32
dwMinorVersion uint32
dwBuildNumber uint32
dwPlatformId uint32
szCSDVersion [128]byte
}

ntoskrnl := windows.MustLoadDLL("ntoskrnl.exe")
defer ntoskrnl.Release()
proc := ntoskrnl.MustFindProc("RtlGetVersion")

var verStruct rtlOSVersionInfo
verStruct.dwOSVersionInfoSize = uint32(unsafe.Sizeof(verStruct))
proc.Call(uintptr(unsafe.Pointer(&verStruct)))

return verStruct.dwMajorVersion, verStruct.dwMinorVersion, verStruct.dwBuildNumber
}

// see https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development
func isDevModeActive() bool {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", registry.READ)
if err != nil {
return false
}

val, _, err := key.GetIntegerValue("AllowDevelopmentWithoutDevLicense")
if err != nil {
return false
}

return val != 0
}

0 comments on commit 1399169

Please sign in to comment.