Archive: Create folder share using system plugin


Create folder share using system plugin
I am trying to create a network share with NSIS using the system plugin.
I do not want to call net share from a command prompt since I want to give access only to a specific user for the share.
I am trying to implement this idea in nsis:

...
!define NO_MULTIPLE_TRUSTEE 0
!define TRUSTEE_IS_SID 0
!define TRUSTEE_IS_USER 1
!define GENERIC_READ 8
!define GENERIC_EXECUTE 2
!define SET_ACCESS 2
!define STYPE_DISKTREE 0
!define SECURITY_DESCRIPTOR_REVISION 0
!define NO_INHERITANCE 0
!define ACCESS_READ 0x1
!define ACCESS_EXEC 0x8
!define strEXPLICIT_ACCESS '(i,i,i,i)i'
!define strTRUSTEE '(i,i,i,i,t)i'
!define strSHARE_INFO_502 '(w,i,w,i,i,i,w,w,i,i)i'
...
System::Call '*${strTRUSTEE}(0,${NO_MULTIPLE_TRUSTEE},${TRUSTEE_IS_SID},${TRUSTEE_IS_USER},$R8).R7'
System::Call '*${strEXPLICIT_ACCESS}(${GENERIC_READ}|${GENERIC_EXECUTE},${SET_ACCESS},${NO_INHERITANCE},R7).R6'
System::Call 'advapi32::SetEntriesInAclA(i 1,i R6,i n,*i .R5)i.r1'
System::Call 'advapi32::InitializeSecurityDescriptor(*i.R4,i ${SECURITY_DESCRIPTOR_REVISION})i.r1'
System::Call 'advapi32::SetSecurityDescriptorDacl(i R4,i 1,i R5,i 0)i.r1'
System::Call '*${strSHARE_INFO_502}("Some Name",${STYPE_DISKTREE},"Comment",${ACCESS_READ}|${ACCESS_EXEC},-1,0,"F:\NewShare","",0,R4).R0'
System::Call 'netapi32::NetShareAdd(,i 502,i R0,*i .R1)i.r1'
The code breaks at the SetEntriesInAclA call with error 87 (invalid parameter). I tried to define the EXPLICIT_ACCESS structure as:
System::Call '*${strEXPLICIT_ACCESS}(${GENERIC_READ}|${GENERIC_EXECUTE},${SET_ACCESS},${NO_INHERITANCE},0,${NO_MULTIPLE_TRUSTEE},${TRUSTEE_IS_SID},${TRUSTEE_IS_USER},$R8).R6'
including the TRUSTEE information as well, where $R8 is a valid user SID which I get using the LookupAccountName and ConvertSidToStringSid calls of advapi32.dll, but I am still getting error 87.

Any insights?


CF
:(

strEXPLICIT_ACCESS is not defined properly. In your definition it contains four integers, but there's no mention of the TRUSTEE structure.


Hi kichik,
thanks for the input!

I realize that the second example I used actually is wrong (blame it on copy/paste) as I redefined the strEXPLICIT_ACCESS in order to contain also the TRUSTEE structure.

Here is the correct 2nd part of the post:

...
!define strEXPLICIT_ACCESS '(i,i,i,i,i,i,i,t)i
System::Call '*${strEXPLICIT_ACCESS}(${GENERIC_READ}|${GENERIC_EXECUTE},${SET_ACCESS},${NO_INHERITANCE},0,${NO_MULTIPLE_TRUSTEE},${TRUSTEE_IS_SID},${TRUSTEE_IS_USER},$R8).R6'
System::Call 'advapi32::SetEntriesInAclA(i 1,i R6,i n,*i .R5)i.r1'
I am still getting error 87 and I am not sure why ...

CF

Can you attach a complete example I can test?


Hi kichik,
I am attaching the nsi file that I am working on.
Thanks for taking the time to look at this :)
CF


When passing TRUSTEE_IS_SID, you must pass a real SID, not a string representation of the SID or a string representation of a pointer to it. Use `i R9` instead of `t R8`.

BTW, it crashes right away if the provided user name doesn't exist. A little error handling won't hurt :)

Also, $R5 should be freed using LocalFree as documented by MSDN and not by System::Free.


Hmmm ...
I tried it with the SID buffer (R9) but it still gives me error 87 ...

This is a test build hence the lack of error checking ... A good idea nonetheless :)

Thanks for the help as always!

CF


Did you change `t` to `i` in the definition of strEXPLICIT_ACCESS?


:)

Yes (I get error 87 with 'i R9' or 't R8')


Weird, it worked fine for me as attached... Make sure the SID came out right. Though I don't get 87 even with an invalid user (but the SID pointer still points somewhere).


Hmmm...

Both the SID buffer (R9) and the string SID (R8) look OK and I still get error 87 ($1 on the message box) ...

:weird:

Even worse, if I pass an invalid username then it works ($1 is 0 and $R5 gets an address)

?

:weird:


87=The parameter is incorrect
and
0x87=An attempt was made to use a JOIN or SUBST command on a drive that has already been substituted.

not sure if its in hex or not, but i would guess the parameter is incorrect


Finally got it!

I was freeing the SID buffer ($R4) inside the LookupAccountName macro and as such the SetEntriesInAcl call would fail with error 87 (where did you find the 0x87 error Anders? It is not hex, it is a standard error code after all).

So for anyone who is interested here is a macro that will create a share giving read permission to a specified user. I am including more definitions in case someone wants to modify this in any way:

!include "LogicLib.nsh"
# Definitions and user flags
!define STYPE_DISKTREE 0
!define ACCESS_READ 0x01
!define ACCESS_WRITE 0x02
!define ACCESS_CREATE 0x04
!define ACCESS_EXEC 0x08
!define ACCESS_DELETE 0x10
!define ACCESS_ATRIB 0x20
!define ACCESS_PERM 0x40
!define ACCESS_ALL 0x7F
# Permissions
!define GENERIC_READ 0x80000000
!define GENERIC_WRITE 0x40000000
!define GENERIC_EXECUTE 0x20000000
!define GENERIC_ALL 0x10000000
!define NO_INHERITANCE 0x0
!define SECURITY_DESCRIPTOR_REVISION 1
# ACCESS_MODE values
!define NOT_USED_ACCESS 0
!define GRANT_ACCESS 1
!define SET_ACCESS 2
!define DENY_ACCESS 3
!define REVOKE_ACCESS 4
!define SET_AUDIT_SUCCESS 5
!define SET_AUDIT_FAILURE 6
# MULTIPLE_TRUSTEE_OPERATION values
!define NO_MULTIPLE_TRUSTEE 0
!define TRUSTEE_IS_IMPERSONATE 1
# TRUSTEE_FORM values
!define TRUSTEE_IS_SID 0
!define TRUSTEE_IS_NAME 1
!define TRUSTEE_BAD_FORM 2
!define TRUSTEE_IS_OBJECTS_AND_SID 3
!define TRUSTEE_IS_OBJECTS_AND_NAME 4
# TRUSTEE_TYPE values
!define TRUSTEE_IS_UNKNOWN 0
!define TRUSTEE_IS_USER 1
!define TRUSTEE_IS_GROUP 2
!define TRUSTEE_IS_DOMAIN 3
!define TRUSTEE_IS_ALIAS 4
!define TRUSTEE_IS_WELL_KNOWN_GROUP 5
!define TRUSTEE_IS_DELETED 6
!define TRUSTEE_IS_INVALID 7
!define TRUSTEE_IS_COMPUTER 8
# Structure Definitions
!define strSHARE_INFO_2 '(w,i,w,i,i,i,w,w)i'
!define strSHARE_INFO_502 '(w,i,w,i,i,i,w,w,i,i)i'
!define strEXPLICIT_ACCESS '(i,i,i,i,i,i,i,i)i'


!macro CreateNewShare USERNAME SHARENAME SHARE_TYPE SHARE_COMMENT SHARE_PERMISSIONS ACL_ACCESS MAX_USERS CURRENT_USES SHARE_PATH SHARE_PASS
# Get the user's SID from the username
System::Call /NOUNLOAD '*(&w${NSIS_MAX_STRLEN})i.R9'
System::Call /NOUNLOAD 'advapi32::LookupAccountNameA(,t "${USERNAME}",i R9,*i ${NSIS_MAX_STRLEN},w .R8,*i ${NSIS_MAX_STRLEN},*i .r4)i.r5'
System::Call /NOUNLOAD 'advapi32::ConvertSidToStringSid(i R9,*t .R8)i.r5'
${If} $R8 == ''
MessageBox MB_OK|MB_ICONSTOP|MB_TOPMOST 'User "${USERNAME}" does not exist!$\nAborting...'
System::Free $R9
Quit
${EndIf}
# Create an EXPLICIT_ACCESS structure and place it on $R6
System::Call /NOUNLOAD '*${strEXPLICIT_ACCESS}(${ACL_ACCESS},${SET_ACCESS},${NO_INHERITANCE},0, ${NO_MULTIPLE_TRUSTEE},${TRUSTEE_IS_SID},${TRUSTEE_IS_USER},$R9).R6'
System::Call /NOUNLOAD 'advapi32::SetEntriesInAclA(i 1,i R6,,*i .R5)i.r1'
${If} $1 <> 0
System::Free $R9
System::Free $R6
Quit
${EndIf}
# Create an empty security descriptor and place it in R4.
System::Alloc ${NSIS_MAX_STRLEN}
Pop $R4
System::Call /NOUNLOAD 'advapi32::InitializeSecurityDescriptor(i R4,i ${SECURITY_DESCRIPTOR_REVISION})i.r1'
${If} $1 == 0
System::Free $R9
System::Free $R6
System::Call 'kernel32::LocalFree(i R5)i.r0'
Quit
${EndIf}
# Add the ACL to the security descriptor
System::Call /NOUNLOAD 'advapi32::SetSecurityDescriptorDacl(i R4,i 1,i R5,i 0)i.r1'
${If} $1 == 0
System::Free $R9
System::Free $R6
System::Call 'kernel32::LocalFree(i R5)i.r0'
Quit
${EndIf}
# Generate the structure that holds the share info and place it on $R0
System::Call /NOUNLOAD '*${strSHARE_INFO_502}("${SHARENAME}",${SHARE_TYPE},"${SHARE_COMMENT}",${SHARE_PERMISSIONS},${MAX_USERS},${CURRENT_USES},"${SHARE_PATH}","${SHARE_PASS}",0,R4).R0'
System::Call /NOUNLOAD 'netapi32::NetShareAdd(, i 502, i R0, *i .R1) i .r1'
${If} $1 <> 0
MessageBox MB_OK|MB_ICONSTOP|MB_TOPMOST 'There was an error creating the share!'
${EndIf}
# Cleanup
System::Free $R9
System::Free $R6
System::Call 'kernel32::LocalFree(i R5)i.r0'
System::Free $R0
!macroend
In order to create a share and give it read access to the user jdoe call the macro like this:
!insertmacro CreateNewShare "jdoe" "Name for the share" ${STYPE_DISKTREE} "share description" ${ACCESS_ALL} ${GENERIC_READ}|${GENERIC_EXECUTE} -1 0 "C:\<some_folder_to_share>" ""
In order to create a share with full access for the user jdoe call the macro like this:
!insertmacro CreateNewShare "jdoe" "Name for the share" ${STYPE_DISKTREE} "share description" ${ACCESS_ALL} ${GENERIC_ALL} -1 0 "C:\<some_folder_to_share>" ""
and so on.

Thanks for the help guys!

CF

Sometimes we may want to create a share with full access for everyone, without specifying extra permissions.
This can be done with the above code by using the SHARE_INFO_2 structure. Here it is in a macro:

!define strSHARE_INFO_2 '(w,i,w,i,i,i,w,w)i'
!macro CreateNewFullShare SHARENAME SHARE_TYPE SHARE_COMMENT SHARE_PERMISSIONS MAX_USERS CURRENT_USES SHARE_PATH SHARE_PASS
System::Call /NOUNLOAD '*${strSHARE_INFO_2}("${SHARENAME}",${SHARE_TYPE},"${SHARE_COMMENT}",${SHARE_PERMISSIONS},${MAX_USERS},${CURRENT_USES},"${SHARE_PATH}","${SHARE_PASS}")i.R0'
System::Call /NOUNLOAD 'netapi32::NetShareAdd(, i 2, i R0, *i .R1) i .r1'
${If} $1 <> 0
MessageBox MB_OK|MB_ICONSTOP|MB_TOPMOST 'There was an error creating the share!'
${EndIf}
System::Free $R0
!macroend

To use it call it from within your code:
!insertmacro CreateNewFullShare "Share Name" ${STYPE_DISKTREE} "Share Description" 0 -1 0 "X:\<folder_to_share>" ""


Also added a wiki page for all the above :D

CF

Hi,
Your macros are very useful :-)

Does any one have another one which allows to unshare a directory ? I need this for my uninstaller script.

Thanks


You can use NetShareDel to remove a share from a computer, assuming that you have admin/power user rights. Note however that this will also close any active connections to the share ...

Here is a very simple macro version of the above without any error checking:

!macro RemoveShare SHARENAME
System::Call /NOUNLOAD 'netapi32::NetShareDel(, *t ${SHARENAME}, i 0) i .r1'
${If} $1 <> 0
MessageBox MB_OK|MB_ICONSTOP|MB_TOPMOST 'There was an error removing the share!'
${EndIf}
!macroend

To use it call it from within your code:
!insertmacro RemoveShare "ShareName"


Let me know if this works (untested)
CF

Hi,
thanks for your reply !
I just tried your RemoveShare macro, and it does not work :'-(

Let me explain:
In my installation procedure I use the following:

!insertmacro CreateNewFullShare "MY_SHARE" ${STYPE_DISKTREE} "blablabla" 0 -1 0 "$INSTDIR" ""

And in my uninstaller:

!insertmacro RemoveShare "MY_SHARE"

But while uninstallation I get the message from the macro: 'There was an error removing the share!'


I will try to investigate.


My bad, it should be a unicode string ...
This one works:

!macro RemoveShare SHARENAME
System::Call /NOUNLOAD 'netapi32::NetShareDel(, w "${SHARENAME}",i 0) i .r1'
${If} $1 <> 0
MessageBox MB_OK|MB_ICONSTOP|MB_TOPMOST 'There was an error removing the share!"'
${EndIf}
!macroend


CF

This one works flawlessly.
Thanks a lot!


I don't know why but none of above scripts worked for me.
I managed to share a folder using standard windows command line like this:

nsExec::ExecToStack 'net share "name"="dir_to_share" /remark:"share description"'
pop $NET_SHARE_RETURN_CODE
${If} $NET_SHARE_RETURN_CODE = 0
MessageBox MB_OK|MB_ICONSTOP|MB_TOPMOST 'Share created'
${EndIf}

if return code is "2" than a share with this name already exists