Wednesday, August 27, 2008
System.Environment.Exit and Mono.GetOptions
C# is apparently such a well-structured language (or its authors wished it to be one) so that it doesn't have normal "exit" function. Indeed, why does one need a special "exit" utility? Program should end simply when "Main" function reaches its end. Simple, ah?
Well, let's take a step back. Microsoft .NET platform apparently does not have its own "standard" argument-parsing utility similar to GNU "getopt". Obviously, it isn't a huge problem to do a straightforward port of "getopt" to C#; see one such port (not free, unfortunately) here. However, Mono project provides a better alternative: Mono.GetOptions assembly.
Blog post from PumaCode.org blog gives a very neat overview of how it is done; basically, for testing you can create one C# file like that:
using System; using System.Reflection; using Mono.GetOptions; // Attributes visible in " --help" [assembly: AssemblyTitle ("go.exe")] [assembly: AssemblyVersion ("1.0.*")] [assembly: AssemblyDescription ("Mono.GetOptions Sample Program")] [assembly: AssemblyCopyright ("Public Domain")] // This is text that goes after " [options]" in help output. [assembly: Mono.UsageComplement ("")] // Attributes visible in " -V" [assembly: Mono.About("Insert About Text Here.")] [assembly: Mono.Author ("Your name here")] class SampleOptions : Options { // Long option is the variable name ("--file"), short option is -f [Option ("Write report to FILE", 'f')] public string file; // Long option is the variable name ("--quiet"), short option is -q [Option ("don't print status messages to stdout", 'q')] public bool quiet; // Long option is as specified ("--use-int"), no short option [Option ("Sample int option", "use-int")] public int use_int; public SampleOptions () { base.ParsingMode = OptionsParsingMode.Both; } } class TestApp { public static void Main (string[] args) { SampleOptions options = new SampleOptions (); options.ProcessArgs (args); Console.WriteLine ("Specified Program Options:"); Console.WriteLine ("\t file: {0}", options.file); Console.WriteLine ("\t quiet: {0}", options.quiet); Console.WriteLine ("\t use_int: {0}", options.use_int); Console.WriteLine ("Remaining Program Options:"); foreach (string s in options.RemainingArguments) { Console.WriteLine ("\t{0}", s); } } }
and try to compile and run it. With mono, it is as simple as:
gmcs -r:Mono.GetOptions getopt.cs
Under .NET, we have to copy Mono.GetOptions.dll from somewhere first. If you have Mono installed on some Linux machine, you can find this DLL under "/usr/lib/mono/gac".
Of course, you need to know where to copy it it to and how to use it. This question has two parts: (a) how to compile code which uses Mono.GetOptions; (b) How to run/distribute code which uses Mono.GetOptions .
How to compile.
You can use compile-option "-r:<full path to Mono.GetOptions.dll>". This will work.
How to make it work by simply using -r:Mono.GetOptions.dll with no path? That's a good question which I cannot answer at this point. A "standard" location for "global" assemblies is "c:\WINNT\assembly" (or similar); note that you can only copy something there with Windows Explorer UI, since it is using special Shell Extension to properly distribute DLLs to some internal directories, not visible from the shell. If you've copied your DLL there, you can reference it as:
/r:c:\WINNT\assembly\GAC_MSIL\Mono.GetOptions\2.0.0.0__0738eb9f132ed756\Mono.GetOptions.dll
Or, alternatively, you can append the above line to your CSC.RSP file (e.g. "c:\WINNT\Microsoft.NET\Framework\v3.5\csc.rsp")
How to run.
This is simple enough - if you have copied your assembly to "c:\WINNT\assembly", it will run fine.
End of story?
No, not yet.
Your test executable will indeed run... till you'll try to copy it to network share and run from there. If you do that and execute "getopt.exe -help", you'll observe "security violation" failure.
One might ask, what kind of security could be violated by parsing a command line?
It is very simple, indeed. As one can see from the source code, function OptionList.ProcessArgs() calls function System.Environment.Exit(), and as it follows from both mono documentation and Microsoft documentation, this function for some reason needs permission to "execute unmanaged code" to run; by default, such permission is not afforded to programs run from "local intranet", including mounted shares.
Is there any way to override this default and grant all required permissions? Perhaps. There is (or was?) utility called caspol.exe which could help you; whether this utility is still supported is not clear. Rather, Microsoft suggest using UI-based configuration tool (part of Windows "Administrative tools", available from Control Panel). When I tried changing that saying that policies from "My computer" shall apply to everything coming from "Local intranet", it indeed fixed the problem... except that as a result Windows Live Writer refused to start! Fortunately, this "configuration tool" has an option to restore default behavior....
.... And all this trouble from simple desire to use some kind of argument-parsing in C#.....
Tuesday, August 12, 2008
The mystery of a plus
Well, today I noticed that "long" listing in one of my directories appends "+" sign to each permissions string, like that:
drwxrwxr-x+ 13 user 500 4096 2008-05-10 02:07 DivX drwxrwxr-x+ 34 user 500 4096 2008-05-10 08:44 Raw
I was used to seeing this extra plus a lot under Cygwin, and never really gave it much thought always attributing this to some Cygwin peculiarities. But this time around this wasn't Cygwin at all - this was pristine Ubuntu 8.04. Given the specific path where this pluses were present, it was apparent that this is somehow related to Samba - these directories were originally uploaded with Samba daemon. So, I did understand from the get-go that in all likelihood Samba server is assigning some special flag to these files and "ls" is trying to notify me of the existence of this flag. I only need to figure out which specific flag it is (and how to remove it). This should be easy. Or so I thought.
The first page I stumbled upon in Google search had this to say:
The character after permissions is an ACL or extended attributes indicator. This character is an @ if extended attributes are associated with the file and the -@ option is in effect. Otherwise, this character is a plus sign (+) character if a nontrivial ACL is associated with the file or a space character if not.
ACL stands for "Access Control Lists" and allows for some fine-tuning of users' access permission on the level of individual user.
Ok, so what now? Same page listed some ACL-related command line options to ls, like '-v'; none of them was recognized by my ls. Well, that was to be expected since these were options for Solaris ls. How about Linux? No problem, said Google, you can use commands getfacl/setfacl to get/set ACL for a given file.
getfacl does indeed generate some output, but it turned out it does so even in absence of any ACLs, simply interpreting default Unix behavior based on file ownership and permissions. Attempts to set and/or clean ACLs for a file failed, and for a good reason: apparently, in order for ACL to be in effect for a mounted file system, mount option 'acl' must be included, as explained in some details e.g. here.
Fine, so if this is not ACL to blame, how about extended attributes? (Actually, extended attributes is not something parallel to ACLs; ACL implementation is based on extended attributes, which merely allows to attach more or less arbitrary "extended" info to each file in a supported file system; ACL is about how its attributes are to be interpreted by file system and kernel; attributes themselves bear no such interpretation). For one thing, this seemed as unlikely, since to use extended attributes one needs another mount option, "user_xattr". In fact, I didn't even have a package "attr" installed, which provided utilities getfattr/setfattr very similar to getfacl/setfacl.
Nevertheless, I installed the package and tried running getfattr on my files. None, as expected.
At this point in time I decided that the fastest way to get to the bottom of this puzzle was to look at the source of "ls" to see what kind of test makes it decide to append this extra "plus".
This was surprisingly simple enough to do; though this is somewhat off-topic, here is a brief overview what I did to compile my own debuggable "ls", for future references:
- Install packages dbs and debhelper (for build);
- Install packages xxgdb or ddd (for debugging);
- Run command "dpkg -S `which ls`" to figure out which package "ls" belongs to. It is "coreutils";
- Retrieve the source from http://packages.ubuntu.com/hardy/coreutils;
- Untar, change dir to coreutils-6.10 and following instructions from file debian/README.build run command "debian/rules setup";
- Now change dir to build-tree/coreutils-6.10 and run " CFLAGS='-g' ./configure";
- Finally, do "make";
- Now your fresh debuggable "ls" is available as "src/ls".
Anyway, it quickly turned out "ls" is calling function getfilecon() and upon receiving some non-trivial output appends "+" markup.
So, what is getfilecon()? It is part of SELinux toolkit; SELinux is, of course, a special security-enhancement system for Linux, which is totally separate from traditional Linux access restriction tools, including ACL; it works by assigning special "roles" to files, processes and users and then keeping a (potentially) huge policy database of who-can-do-what; it was originally developed by NSA and open-sourced in 2000.
On a practical level, SELinux-enabled system (Ubuntu Hardy being one of them) have special CLI switch "-Z" for many file and process-management utilities to report/take into account SELinux "context". Indeed, this finally allowed me to solve first piece of the mystery - force "ls" to tell me what it means my "+", by using "-Z":
% ls -1Z user_u:object_r:file_t DivX user_u:object_r:file_t Raw
The only trouble is, of course, that SELinux was never enabled on my system! This could be seen with e.g. "id -Z"
% id -Z id: --context (-Z) works only on an SELinux-enabled kernel
Anyway, enough suspence, here is what (I think) is going on. Not unlike ACL, SELinux keeps its "context" in extended attributes. Whether kernel has SELinux enabled or not, utilities which are told to assign certain SELinux "types" to files, are free to do so using e.g. setfattr. These attributes, of course, will have no impact whatsoever on security of the system, but will be reported by utilities like "ls" which, again, simply looks at the specific attribute.
In fact, the name of this attribute is "security.selinux"; indeed, I can verify this with "getfattr" :
% getfattr -n security.selinux -d Raw # file: Raw security.selinux="user_u:object_r:file_t\000"
So why woudn't "getfattr -d" report this? Isn't it in the absence of "-n" supposed to return all attributes?
Well, yes and no. By default, it reports all user attributes, that is, attributes with names which begin with "user.". To truly report all attributes, add "-m -" :
% getfattr -m - -d Raw # file: Raw security.selinux="user_u:object_r:file_t\000"
You can, of course, assign this attribute with command "setfattr -n security.selinux -v <value> <file name>" or remove it altogether with "setfattr -x security.selinux <file name>".
This, finally, answers the question we asked in the beginning - how to remove this "plus" from "ls -l" output - and thus concluded our investigation.
Bonus track. Some SELinux-related references:
Labels: ACL, linux, Samba, SELinux, Ubuntu, xattr