Bjørn Gustafson - Jørgen Kjensli
As computer systems are becoming more complex the need to automate the tedious tasks of the system administrator increases. Cfengine is a program that does this on UNIX systems. Although Windows NT is supposed to be easy to use with its graphical user interface, it often makes administering and configuring the system very time consuming. A tool like cfengine can therefore be very useful on NT as well. This report describes the work we have done to port cfengine to Windows NT and some thoughts about future work.
This report is the result of a final year project at Oslo College, Norway, by Bjørn E. Gustafson and Jørgen Kjensli. The object of the project was to port cfengine, a site configuration engine for UNIX systems, to Windows NT. Cfengine was written by Mark Burgess and the project was conceived by him and he engaged the authors to do the job.
Cfengine is a program that is used all over the world, therefore we decided to write the report in English so that it can be available for current and future users of cfengine. The report goes into some technical detail and we assume that the reader has knowledge of C programming.
Cfengine is a language based tool specifically designed for configuring and maintaining BSD and System-5-like operating systems attached to a TCP/IP network. You can think of cfengine as a very high level language-much higher level than Perl or shell: a single statement can result in many hundreds of operations being performed on multiple hosts. The main purpose of cfengine is to allow you to create a single, central configuration file which will define how every host on your network should be configured. The cfengine interpreter runs on every host on the network and parses the central file (or file-set); the configuration of each host is checked against this file and then, if you request it, any deviations from the define configuration are fixed. If such a central file had to mention every single host on a network individually, it would have to be very large, but cfengine uses a flexible system of "classes" which helps you to describe many hosts with a single statement, or make complex decisions which pick out very specific actions for specific hosts if necessary.
The job of porting a large program like cfengine can seem quite overwhelming at first. Given the vast amount of code, we realised that re-coding the program for the NT platform would be far too much work for a project like this. Besides, it seemed unnecessary to rewrite all the code that actually worked on the UNIX platform. There had to be an easier way of doing it. So we were really presented with two problems. How can we compile the already functional code on the NT platform, and how does NT behave in relation to the resources which cfengine controls on UNIX. In the next few sections we will present our thoughts on these two problems. Then we explain how we went about implementing our ideas. Finally we discuss some further work that can be done to make cfengine a valuable resource for Windows NT administrators.
2Dlist.c cflex.l.in edittools.c item-file.c patches.c Makefile.in cfparse.y encrypt.c item.c popen.c STRUCTURE cfrun.c errors.c link.c process.c acl.c checksums.c eval.c locks.c proto.c cf.defs.h chflags.c filedir.c log.c read.c cf.extern.h classes.c filenames.c macro.c repository.c cfd.c client.c globals.c misc.c rotate.c cfd.h comparray.c ifconf.c modes.c tidy.c cfengine.c conf.h.in image.c mount.c varstring.c cfkey copy.c install.c net.c wildcard.c cfkey.c df.c item-ext.c parse.c
It is not important to know what all these files do, but we will explain the most important ones and of course the ones we will be working with.
Before cfengine can be used on a system it has to be compiled on that specific system. This is very important because it configures cfengine for that system, and no two systems are exactly alike. Cfengine is compiled by running a script called configure and then a program called make . The configure script basically checks your system and sets some variables to define how the system is set up. It also creates makefiles, which are input files for the make program. They tell make which files should be compiled and where to put the output files.
The files discussed below are the ones you will need to know about.
To port cfengine to NT, we need to compile the program on an NT machine. This is only possible if the compiler understands certain UNIX system calls (i.e. pipe, fork, etc), or if we rewrite the cfengine code. The latter option would clearly require a lot of time and effort, and since the creation of the porting layers such as UWIN , CygWin and their likes, it is hardly necessary. What we needed to do was to find a porting layer that supported most (or all) of the calls needed by cfengine, and then code the rest by hand.
The porting layers we had to choose from was NuTCracker from DataFocus, OpenNT from Softway Systems, UWIN from AT&T and CygWin from Cygnus Solutions. The two first options were promptly discarded as they are both proprietary. UWIN is also proprietary but is available for free non-commercial use. We were pleased to find that CygWin is a free software solution, and we decided that if it could compete with UWIN , i.e. it supported all (or most of) the functions that UWIN did, CygWin's library would be our preferred choice. From the documentation of the two porting layers it seemed as though their functionality was just about the same. We therefore decided to try compiling cfengine with CygWin and if it worked satisfactorily we would use it. If not, we would have to test UWIN as well.
In addition to supporting some basic system calls (pipe, fork, etc), the porting layer also needed to be able to execute some basic shell commands. They are listed in cfengine's src/classes.c as ps , netstat and mount /umount .
We found that both CygWin and UWIN supported the ps command (although CygWin seemed to be a bit quicker). However, the output of the call was a listing of processes using the porting layer. All processes running directly under NT were omitted. This is a problem that we must solve, and we will talk about it in a later chapter.
The netstat command worked equally well for both UWIN and CygWin , and hopefully satisfactorily for cfengine.
Since it is impossible to mount anything under NT, the mount and the umount commands are not needed. On NT all devices are mounted automatically, but in a different sense than on UNIX. The commands seem to be supported by both porting layers, however. We will discuss in a later chapter what mounting would mean on NT.
The easiest way to check if the porting layer supports all of the system calls (such as pipe, fork, etc) that cfengine uses would be to try to compile the code under the porting layers with the gcc compiler provided by the porting layers and evaluate the error messages, if any.
Another thing that works differently on the two platforms is file permissions. On UNIX you can only specify the rights of the user (owner of the file), one group and all others. NT however uses access control lists (ACLs ). You find this on some UNIX systems as well, but it is not widely used. ACLs is a much more complex way of handling file access than the traditional UNIX style permissions. If you want to give users other than the owner access to a file on UNIX, you have two choices. You either have to give everyone access or add the users to the group linked to the file. All the members of the group will have the same rights, so it would be impossible to give a number of users different access rights. With NT's access control lists you can basically accomplish all the combination of user access you can think of. You can grant or deny access to as many specific users or groups as you want.
Setting the correct permissions on files is an important part of cfengine's capabilities. When we had compiled and tested cfengine on the NT platform we started to implement NT ACLs in cfengine. We had to write the code ourselves. Unfortunately, creating and modifying the ACLs proved to be more difficult than we anticipated and we were not able to complete it within the timeframe of this project. However we are well on the way and we will continue the work later. A more detailed description of access control lists and our work in that area is given in the section Access control.
One of the things that cfengine controls on UNIX systems is mounting of file systems on the network. On NT mounting means something different than on UNIX. All file systems are mounted automatically at boot time as c:, d:, e: and so on. In other words you can not choose to mount a file system somewhere else in the directory hierarchy. It is a bit of a surprise that both the CygWin and UWIN porting layers have the mount and umount commands. We will talk about how that works and discuss if it can be used by cfengine in Chapter [*]. On UNIX systems the file systems to be mounted and where they are to be mounted are given in a text file often called fstab (at least on GNU/Linux). Since NT does not have such a file we have defined its location as N/A (not available) in the file src/classes.c.
One of the things that is specified in the file src/classes.c is the location of all mail. As far as we know, there is no such thing as a default mail directory on NT the same way it is on UNIX. Therefore we cannot specify a default directory in the /src/classes.c file. However, this would in reality be a completely useless piece of information anyway, because all mail on NT is stored in a completely different format than on UNIX. Hence, the default directory is defined as N/A (not available) in the file. The mail-problem could possibly be resolved, but this is far from our biggest priority, and will be put aside indefinitely.
The location of resolve.conf, which contains network configuration on UNIX, is N/A for now. We might try to implement this for NT later on if needed and time allows us.
Configure is a shell script and it runs on both porting layers. With CygWin we had to make a link from /bin/sh to where CygWin had placed the sh binary. The make program is also supported by both CygWin and UWIN .
In file included from cfengine.c:37: cf.defs.h:196: sys/protosw.h: No such file or directory cf.defs.h:198: net/route.h: No such file or directory make[1]: *** [cfengine.o] Error 1 make: *** [all] Error 2We got Mark to look at it and it was easy for him to put an extra test condition in the code to make it not look for these files on an NT system. We ran make again and got a bit further before a new error occurred. The error was as follows:
ifconf.c:456: parse error make[1]: *** [ifconf.o] Error 1 make: *** [all] Error 2This was only a typo in the code, elif instead of else if. The next error was:
ifconf.c: In function `SetDefaultRoute': ifconf.c:362: storage size of `route' isn't known make[1]: *** [ifconf.o] Error 1 make: *** [all] Error 2We had to ignore these calls since they are not implemented for NT. We did this by commenting out the relevant code.
export HOME=/testdirWe decided to make several small programs that just tested one type of functionality. The test programs are quoted in the following sections along with the results of the testing.
control: actionsequence = ( shellcommands ) shellcommands: "/bin/echo Ohh yeahhh" "/bin/date"
control: actionsequence = ( tidy ) tidy: /testdir pattern=*.html r=1 age=2 type=mtime size=10
control: actionsequence = ( disable ) disable: /testdir/default.htm /testdir/links.html size=>2000
!<symlink>destinationfileOn NT you just have shortcuts, which are binary files with extension .lnk. If we can find the format of these binary files it should be possible to make cfengine understand or at least make shortcuts. When testing we found out that (not unexpectedly) the links command worked the way it was supposed to with CygWin type links . These options have been checked and work properly: single links , multiple links , include, exclude, recurse, type=absolute/relative and type=hard (makes a copy of the file instead of a link). There were some options that we did not test. They were: copy , copytype and nofile/deadlinks. Again these refer to cfengine internals so they are not affected by NT.
This test program covers some of the options we tested for links :
control: actionsequence = ( links ) links: /testdir/texinfo.tex -> /testdir/jiffy/texinfo.tex /testdir/ +> /testdir/jiffy/jumbo include=*.c /testdir/regexlink -> /testdir/jiffy/c/jumbo/regex.c type=hard
When we tried to copy from another server, a UNIX server (nexus), we got some error messages. We did not understand what was wrong so we got Mark to look at it. It complained that cfengine was not registered in /etc/services, which is a file that does not exist on NT. Mark changed the cfengine code so that it would use a default port in the absence of the /etc/services file. We still got an error message, which is quoted below:
Checking copy from nexus:/iu/nexus/u1/kjenslj/downs/paper.tex to /testdir/paper.tex cfengine:RICON: Remember to register cfengine in /etc/services: cfengine 5308/tcp cfengine:RICON: getservbynameSystem name is RICON.iu.hioslo.no and DNS name on this socket is ricon.iu.hioslo.no [main] C:\Port\CygWin\cfengine-1.5.0-alpha7\src\cfengine.exe 8063 (0) handle_exceptions: Exception: STATUS_ACCESS_VIOLATION [main] cfengine 8063 (0) handle_exceptions: Dumping stack trace to cfengine.exe.coreIt was an error message that did not tell us much. After some testing Mark tried to disable person authentication in the cfd (cfengine daemon) running on nexus. This solved our problem and we were able to copy from an external source directory.
Another smaller problem was that the function that cfengine used to get the username did not work. While testing it we just sent an arbitrary string as the username when opening a socket (this is done in proto.c). Later Mark found another function, getlogin(), that returned the username. This is updated in the new version cfengine1.5-alpha8. However the true solution to the problem was discovered a bit later. As it turned out, the creation of /etc/passwd and /etc/group solved the problem. In other words the original code worked fine and the modifications were removed. A thought for the future would be to try to compile the GNU authentication program that the cfd daemon tries to start when person authentication is enabled.
Finally, we did not get the purge option to work. This was probably because we did not get the syntax right. It should work since this option only affects cfengine internals.
The test file that caused us some problems when we tried to copy from another server is listed below.
control: domain = ( iu.hioslo.no ) actionsequence = ( copy ) copy: /etc/passwd dest=/testdir/passwd server=nexus /testdir/dummy/pub dest=/testdir/ recurse=1 exclude=*.htm
When we ran the test program quoted below it was clear that regular expressions did not work at all. We had a suspicion about this because cfengine's configure script had not been able to locate the rxposix.h or regexp.h header files. It must have found something since we were able to compile cfengine, but wherever the functions it used were located they did not do anything.
control: actionsequence = ( editfiles ) # Test editfiles and regular expressions var1 = ( "Insert this" ) editfiles: { $(HOME)/cfengine-textfile AutoCreate DeleteLinesMatching "This.*" AppendIfNoSuchLine "You can make cfengine work like @m4 macros@" ReplaceAll "@m4 macros@" With "a dream" BeginGroupIfNoLineContaining "excite" AppendIfNoSuchLine "Sticks and stones may break my bones" AppendIfNoSuchLine "But words will never hurt me" EndGroup ReplaceAll "words.*" With "whips and chains excite me" PrependIfNoSuchLine "$(var1) at the start" AppendIfNoSuchLine "This is the end of a non-convergent file" }
Mark suggested that we try to download and compile GNU's regex library rx1.5 . We downloaded it, but when we ran its configure script we got some error messages. However it did create the makefiles so we tried to run make . Make did not seem to do anything, so we started to look at the messages from configure . One of them was that it did not find the tsort program, which does not come with CygWin . It did come with UWIN so we tried to copy it from there. It worked, we got rid of some errors, but make still did nothing. The library was supposed to be installed in subdirectories of /usr/local, but /usr/local did not exist on our machine. First we thought that it would make the directories if they did not exist, but when we got more desperate we decided to create them ourselves. This was a smart thing to do, because now it actually installed the rxposix.h header file in /usr/local/include/ and librx.a in /usr/local/lib. OK, so it worked, but it would be much better if we could do it without UWIN's tsort program. We tried to do it all again, but this time we replaced tsort.exe with a link to cat.exe. This worked just as well. We now built cfengine again and regular expressions worked. (Note: we had to run make distclean for configure to search for the header files again).
c:\directory\file
while
CygWin sees it as /directory/file. However it seems like
CygWin handles this for us. They also provide the utility
cygpath that converts native filenames to CygWin UNIX style
pathnames and back.
$ mkdir /etc $ mkpasswd > /etc/passwd $ mkgroup > /etc/groupThis information is static and the files need to be regenerated if you change the password or group information on your system. It could be a good idea to have cfengine run these programs.
We were now able to run this test program without any problems as well:
control: actionsequence = ( files ) files: /testdir/dum_fil.txt owner=kjenslj group=Everyone action=touch
We put in a lot of time and effort to make cfengine capable of creating and editing ACLs on the Windows NT platform. Unfortunately it proved to be more difficult than we had anticipated and we were not able to finish the work within the time limit of this project. But we now have a very good understanding of ACLs on NT, so we will finish our work at a later time.
method:overwrite fstype:nt access:allowed user:gustafb:rx user:kjenslj:rwxWe started to look in the cfengine code to analyse how difficult it would be to implement this solution. As we got into the code we understood that there was a better way to do it. A solution that meant less change in the existing code. The format we decided on is as follows:
method:overwrite fstype:nt user:gustafb:rx:allowed user:kjenslj:rwx:deniedThis was rather easy to implement but to make it work we made some changes in the CFACE struct (see below). For Solaris and DFS you can specify the access mode as r, w, x, all, default and noaccess[+]. In addition to this you can use these modes on NT: d, p, o, read and change.
We decided to wait with the appending until we had managed the overwrite functionality. Based on one of our earlier test programs we knew that to create a file with a new ACL was as simple as stealing candy from a baby. However we did not want to create a file with an ACL , we wanted to overwrite an existing file's ACL . This is slightly more complicated, but even stealing candy from an adult is rarely very hard. What we needed to do was to take every user in the list of CFACEs and use one of Windows NT's functions[+]to look up the name and find each user's security identifier (SID). Then we simply initialised a new ACL and added an access allowed ACE with the correct NT access mask for each of the SIDs[+]. The new ACL now has a list of ACEs , one for each user specified in the config file (if the user existed). Another function call gives us the existing file's security descriptor (SD ) and by including the new ACL in the SD we just found we have successfully overwritten the file's ACL and in effect replaced the file's permissions with those specified in the config file.
The logic on how to overwrite an existing file's ACL is simple enough, but we could of course not manage this step without running into some problems. Our code was pretty much based on examples we knew to be right. However for some unknown reason our code still would not work properly. After several hours of debugging we finally found that the NT function GlobalAlloc[+] had failed to work as expected. The function is supposed to allocate memory and as of today we still do not know why it failed. We did not receive any errors or warnings though. The program simply failed to work as expected. We solved the problem by calling the C function malloc to handle the memory allocation. This was not the end of our troubles though. Suddenly a new error appeared, and again it took us several hours to find the cause of it. To make sure that we allocated the exact amount of memory for the new ACL we had used the function LookupAccountName twice. Once to check if the user existed and then again to actually get the size of the user's SID and to allocate memory. We commented out the first call to the function and miraculously the error disappeared. We kept the program without the test knowing that in certain cases we might now allocate too much memory for the new ACL [+]. We were able to fix this a few weeks later, mind you.
There was one issue that we had omitted to consider though. We realised the effect of appending ten ACEs belonging to the same user, let us call him john, to the same ACL only after we attempted it. It seemed that the operating system interpreted the ten ACEs by only showing one of john's ACEs on the graphical interface but with all the accumulated permissions. This was the exact effect that we had hoped for, but no matter how NT interprets several ACEs belonging to the same user, one fact still remained; we were using unnecessarily many ACEs and really wasting disk space. Maybe not much. Maybe not more than some tenfold bytes, but wasting disk space on such a low level can quickly lead to several gigabytes of wasted space! We knew we had to correct the situation. We also realised another far more serious issue. During the planning phase we had not considered the implementation of an important feature of cfengine, namely the removal of existing rights through the use of a minus sign in the config file. We realised that this last functionality demanded some restructuring of our code and decided to enhance our code to the best of our ability making sure that no memory or disk space were lost, and that the speed and the effectiveness of the code was optimised. However, implementing the removal of rights and the new optimised structure of our code turned out to be a little more time consuming than we first anticipated.
The new shape of the CFACE struct and the added functionality of getNTModeMask solved the removal of rights. However the problem of possibly creating too many ACEs still remained. To start checking the new ACEs against every existing ACE would be a fairly heavy and time-consuming operation. Besides, because of the way NT interprets two ACEs for the same user, in order to alter the rights of an existing ACE we were forced to remove the ACE from the ACL completely before adding it again with different permission bits set. This implies a far better solution. We still have to check every user against every existing user before creating an ACE for the new user, but we do it before any ACEs are created at all. We do it while the users are still in our list. To implement this we had to create a new list of CFACEs . According to our plan, this new list would eventually end up with a struct for every single user that is supposed to have an ACE in the ACL . Each of the users in the list should also have the correct mask set. To guarantee that no user that should be in the new list was left out, we had to iterate the old and the new list several times, but there is really no way around it. Well, actually there is through some fancy pointer manipulation, but as cfengine is a delicate program and the winnings of the manipulations are so slight as to be almost imperceptible, we decided not to go through with it. We did not think it would be worth the time either as we have plenty of things to do.
The way our code works now is that we first of all extract all the ACEs from the existing ACL . Each of these ACEs are checked with our old list of CFACE structs containing the users specified in the config file. If there is a match on the usernames as well as on the access type, a new mask is calculated by the function getNTModeMask. The user with the correct mask is then added to our new list of CFACEs . The users mask in the old list is also set. Since the mask originally is initialised to be 0, we will know which of the users in our old list of CFACEs that did not already exist in the ACL . These will be the users that still have a mask of 0. So when all the existing ACEs are transformed into CFACEs and inserted into our new list, all we need to do is to iterate through our old list one more time. This time we take all users with 0 as mask, find the mask corresponding to the mode, and then add them to our new list. We now have a new list of CFACEs with every user occurring only once with the same access type, and with possibly modified but certainly correct access masks. The last step is to iterate through our new list twice. The first time looking for all the structs that have access type denied, creating an Access Denied ACE for each one and adding the ACE to a new ACL . The second time we create an Access Allowed ACE for every struct that have access type allowed and add the ACE to the ACL . We add all the denied ACEs first because as we have mentioned before, the order of the ACEs is of utmost importance. When inserting the new ACL to the file's security descriptor we have successfully modified the file's permissions. We later discovered, however, that we had overlooked an important factor when updating a file's ACL . We had omitted setting each ACE's inheritance flag, but this is described in more detail in the section on directories.
The way NT specifies the permissions on a file is through setting different bits in a mask that is a variable of type DWORD. The DWORD is 32 bits long and can compare to C's unsigned long int. The 32 bits are divided into groups where, counting from the right, the first 16 bits are for specific rights, the next eight bits are for standard rights, while the last and leftmost eight bits are for generic rights[+]. For reasons far beyond our capabilities of reasoning, NT has chosen not to go with the intuitive solution of simply reserving one simple bit for each possible right. Instead one must set several bits to specify a certain right. For example, to specify read rights, a full five bits must be set. Six bits must be set to specify write, and four on execute. To further complicate matters, some of these bit masks overlap. So if the bits are set for read and execute rights and you want to remove the read right, you cannot simply unset all the read bits, because some of them are used by the execute right. We have not yet understood why NT has chosen to do it this way and we are still waiting to be enlightened. It is perhaps to use the mask on other securable objects than just files, but the fact that it complicates things a bit still remains.
To make things easier for someone in our position, someone who is attempting to set certain rights by using their own program, NT has defined some constants. They are in fact bit masks. Some of these are GENERIC_ALL, GENERIC_EXECUTE, GENERIC_READ and GENERIC_WRITE. These generic rights constants are pre-defined combinations of standard and specific rights and change from object to object. By experimenting in our test programs we found which ones would set r, w, x, d, p and o respectively. However, even if using these constants had the desired effect, things quickly turned more complicated when we needed to subtract rights. Removing rights would obviously require bitwise manipulation of the mask, and this is not a problem in itself. To remove, say the read permission, we would simply AND the old mask with the complement of the constant representing the right. In pseudo code, something like this should do the trick:
OldMask &= ~GENERIC\_READThe result would be the old mask with r removed. However, since NT operates with bitmasks that overlap each other, removing the bitmask for r would also remove part of the bitmask used by w and x (if one of those permissions were set). Unintentionally we are sure, but nevertheless a misfortune that the documentation of the access mask and exactly which bits are used for what has failed to be included in every single piece of documentation that we have checked. Through the method that has become oh so very familiar to us lately, the method of experimenting and trying and failing, we managed to map the bits used by the different rights. See Figure [*]. With this new knowledge we could manipulate the mask without using the constants and could successfully remove any permission we wanted. For example, a file with permission rx will according to our map have the bits 20,17,7,5,3 and 0 set, whereof bits 20,17 and 7 are shared. To remove the r permission we can only remove the bits that are used exclusively by this permission, i.e. only bits 3 and 0. Bit 3 and 0 in a 4 bit sequence can be represented hexadecimally as the number 9. The complement of this number is the hexadecimal number 6. Since we must work with the complement of the permission to remove that permission, we would now do something like this to remove the r from rx:
OldMask &= 0xFFFFFFF6Each of the F's will not change the existing bits they are ANDed with because the F represent a four bit sequence where every bit is set[+].
The problem with removing specific rights now seem to be solved. There was only one thing that needed correction. NT has some predefined permission types. These are No Access, Read(rx), Change(rwxd) and All(rwxdpo). When giving a user the permissions rx or rwxd NT automatically interprets this as Read and Change respectively. However, when setting all the permissions (rwxdpo), NT does not interpret this as All even if that is the effective right. As we could find no explanation for this anywhere in the documentation that were available to us, we had to experiment again. We discovered that for a user to be given the predefined permission All, the GENERIC_ALL bit must be set, i.e. bit 28. Before returning the mask from the function getNTModeMask we check if all the bits for rwxdpo respectively are set, and if so we return a mask with only bit 28 set instead.
Setting No Access was harder and it took us a long time until we figured out how to do it. Again no documentation on the subject seemed to be available. Experimentation told us that the mask for No Access is the exact same mask as for All. This made no sense to us as these two rights are the complete opposite. We were on the verge of mailing Microsoft for a hint because this made no sense. It was only by chance that we later discovered how it worked. The documentation claims that the graphical user interface of NT 4.0 is unable to show Access Denied ACEs . We therefore assumed that all the ACEs showed when looking at a file's permissions were Access Allowed ACEs . However we found that there was an exception. No Access is really an Access Denied ACE with All rights, i.e. with a mask where bit 28 is set! We were utterly surprised to learn this fact, but we were also relieved. By the time we figured this out the problem had really caused a lot of frustration.
When working with a securable object we believe that it is the
specific rights that specify a user's permissions on the object
(except when All or No Access is given). This have
not been extensively tested but seem to be the case on files, and
files are about the only securable object we work with. An exception
are directories where things seem to work a little different, but this
is discussed further in a later chapter. Nevertheless, with a complete
mapping of the bits in NT's access mask and a full understanding of
how the pre-defined rights affect the mask, we hope that our newly
acquired knowledge can be of someone else's assistance as well.
Figure:
NT bit masks for the different permissions.
user:kjenslj:default:allowedThe use of the default keyword should result in the removal of the respective user's ACE from the ACL . When using default the access type does not have to be specified, so allowed could have been omitted from the example above. The result would have been the same. It was not difficult to remove the ACEs belonging to users or groups when the mode was set to default. In the function getNTModeMask we return 0 if the mode is default. If the method is append we check the ACEs that exist in the old ACL against the list of CFACEs from the configuration file. Then we add a CFACE to a new list, unless the mode is 0 like it will be if the mode is default. Finally we add all the ACE s that are in the configuration file and not in the old ACL . The entries in the list of CFACEs from the configuration file with NTMode equal to 0, i.e. the NTMode has not been updated. There is one exception though. If the mode is default the NTMode have been updated, but it is still 0. Therefore we also test that the mode is different from default before we add them. In case the method is overwrite we just check that the mode is different from default before we call AddAccessDeniedAce or AddAccessAllowedAce.
We ran into a problem when we made access denied ACEs with all rights, i.e. with noaccess. Sometimes we were able to view the ACEs in NT's graphical user interface, but sometimes not. We suspected that it might have something to do with the order of the ACEs . We consulted the documentation on Microsoft's web site and found this to be true. All the access denied ACEs should be placed ahead of all the allowed ACEs in the ACL .
When implementing the denied ACEs a bug that caused the program to not add some ACEs occurred. For a long time we thought that this had to do with the noaccess functionality that we had just implemented. However the bug seemed to occur randomly so we suspected it could have something to do with memory. We finally found the bug. We did not allocate enough memory for the new ACL . The function we made to calculate the size only gave the size of all the ACEs and not the ACL itself. We added the size of an empty ACL , which was 8 bytes, and then everything worked fine. Finally we thought we had finished the implementation, but as we describe in the next section there was one thing we had overlooked.
The way they differ from files is that every entry for a user or group in the ACL has two sets of rights. The first set is directory access while the other is file access. The file access permissions are the permissions that will be inherited by files created in the directory. The directory access permissions says as you might assume who will have access to the directory.
We started to test how the ACLs worked for directories. First we had thought that the access masks used in the ACEs could be different, but we discovered that this was not the case. We used our test program to get a hold of the ACL of a directory and print all the ACEs . What we discovered was that every user and group had two ACEs , except for the ones where either directory access or file access weren't specified. This presented a new problem; how does NT know which ACE is directory access and which is file access? The information in the ACEs seemed to be the same so we started looking closer at the access masks. It looked like the masks specifying directory access used the specific and standard part of the mask, while the ones specifying file access used the generic and standard part. This led us to believe that the mask format told NT which ACE applied to which type of access. But when we thought about it we found that if you had two ACEs with permissions that only contained d, p or o it would not be possible to tell them apart because both masks would only have bits set in the standard part. So there had to be another way to tell them apart.
Another thing we discovered when we tried to find information about ACLs on directories was that the ACE header contained a flag that said something about inheritance of permissions. We did not know of this flag and therefore we had not preserved its value when we put the ACEs that existed in a files ACL into a new ACL . However we feel that we must blame this on the book we used because it did not say anything about this flag when describing how to add ACEs to an ACL . It is extremely relevant information and should have been mentioned. Since we found out about the flag we believe that its value can tell the system if the ACE should be directory or file access permissions. We have not had time to test this though so we cannot say for sure.
No matter how NT tells the two sets of permissions apart we have to find a way to define them in the config file. We thought of a few possibilities. For example you could have two entries for each user in the config file, but then it would be hard to use the ACL for both files and directories. We have come up with another solution, that we believe will solve this problem. The idea is to write it like this:
user:kjenslj:rwx/rx:allowed/allowedThe first set of permissions (rwx) would be the specific rights, i.e. the rights that apply to the object we are working on. The other set (rx) would be generic. If we are working with a file only the specific rights will be used, while the other (rx) will be discarded. If it is a directory the specific rights will define the directory access. The other set of permissions (rx) will define file access, i.e. the permissions inherited by files created in the directory. The first access type applies to the first set of permissions, and the second access type to the other set.
After all our work with ACLs on the Windows NT platform the feeling we are left with is one of frustration because the documentation available on the subject is far from sufficient. Perhaps the documentation exists but in that case it is very hard to find. When you work with the system at such a low level it is hard enough without having to find out how everything works by trying and failing. Perhaps our experiences and our documentation will be of help to others trying to modify ACLs .
We are aware that there is a pstat command in NT Resource Kit that could work, but since it is a commercial product we would like to avoid it. However, many NT administrators might already have this kit so they could use it.
Another thing that cfengine can control on NT is disc space. Hard discs tend to fill up quite rapidly these days and a lot of it is because of unnecessary files. With the extensive use of the Internet you could for example have cfengine delete temporary Internet files on the system. Basically you can program cfengine to keep your hard drives tidy.
Deleting certain files is not the only thing that cfengine can do. It can also copy files . There are some files that need to be present on the system. Cfengine can check if they are in their proper place, if not it can copy them from a backup. It can also make sure that home directories of the users contain the files they are supposed to.
With a new implementation of the ps command cfengine will also be able to control the CPU usage. If a process uses too much CPU time cfengine can send a signal to the process, usually a kill signal.
The best thing however, might be that through our work with NT we have ourselves seen the need for a tool like cfengine. Windows NT is a very complex system which makes it difficult to have full control of everything. We have just administered one machine and we can only imagine how it would be to control a large NT network. With cfengine, NT administrators will be able to atomise a lot of the work that needs to be done. But even more important is the fact that cfengine will look after the system and reduce the chance of errors that the administrators will have to deal with. The computer system becomes less dependent of human intervention.
$ mkdir /tmp /etc /usr
$ ln -s /bin /path/bin
$ mkdir /etc $ mkpasswd -g > /etc/passwd $ mkgroup > /etc/groupThese files are static and will not be updated when the system information changes. You will need to execute these commands again to update the files.
$ export HOME=/homedir $ export MAKE_MODE=UNIX
export CYGWIN=ntea
$ tar xfz rx-1.5.tar.gz
$ ln -s tsort.exe cat.exe
$ cd /usr $ mkdir local
$ configure $ make $ make install
You should now be able to compile cfengine successfully.
We would like the reader to note that the program itself is so well commented that it should have the ability of serving as documentation in itself. In addition to the extensive use of comments, we have been careful to use descriptive names on functions and variables to enhance the general understanding of the program. Our intentions are that an experienced programmer should easily be able to understand the code simply by looking at it.
Our code is integrated into an existing version of cfengine. As the new version should work on both UNIX and NT, whilst still being subject to further development, we appreciated the importance of clearly marking our work in the code. We did this to avoid any confusion and to clarify which parts are needed to make cfengine run on NT.
The section Description intends explain the principal doings of our code and to give the reader a basic overview and understanding of the program that we have written as an addition to the existing code in cfengine. To show the connection between the different program modules and how these interact we have tried to explain and show the structural construction of our program sequence in the section on Structure and functionality. An extensive explanation of every function that we have coded will be provided in Section [*]. We have also made some changes to functions and structures that already existed in cfengine. These changes will be described and explained in the section Other changes. Our code has also been subject to extensive testing. This is described in the section Testing the code.
The method of permission-manipulation is set in the configuration file. If the method is overwrite we create a new ACL and add an ACE for every user that exists in cfengine's configuration file, making sure that all the Access Denied ACEs comes first. If the method is append we add the existing users as well as those existing in the config file. We make sure that the rights of the users that already existed in the ACL are changed if any of those users also existed in the configuration file. Finally, no matter the method of manipulation, the new ACL is set to be a part of the file's security descriptor. The permissions that the users had on the file has now successfully been altered.
To give you an understanding of the structure of our code we have
provided you with a figure showing how our functions tie together. See
Figure [*]. The construction should be relatively simple and
easy to understand. If a deeper understanding of the code and
functionality is required, please see the description of the functions
in Section [*] and [*].
Figure:
The structure of our code. Starting with CheckNTACE.
This function is called from the functions appendNTACEs and createNTACL.
The function handles the conversion from the UNIX like mode (like rwx) to NT's access mask. The string containing the desired mode can use the plus-, minus-, comma- and equal signs to add or remove rights. This is the way cfengine worked on UNIX systems before, i.e. no functionality is lost there. The string can be any combination of the rights r,w,x,d,p, and o, as well as cfengine's defined words (default, all). As a slightly extended functionality, the function will also interpret NT's predefined words (read, change, all), but only if those words are used alone.
For simple conversion from a mode to an access mask, let old_mode be 0. Otherwise bitwise manipulation will be performed to merge the two permissions. How the permissions will be merged will depend on new_mode.
The function starts by checking for the existence of any words in new_mode. It does not test for the word noaccess because this is already taken care of when parsing the configuration file. Please see the description of the function AddACE under the section on other changes. If the word default is found, the user should be removed from the ACL and the function returns 0. This value is remembered and later checked in the function attachToNTACEs to ensure that users are removed (or rather not added) if the value is 0. If the words all, read or change is encountered the respective masks are returned. After these tests we know that the string is put together by a set of characters representing modes. Bitwise manipulation will be needed.
Whether rights are to be added or removed is decided by a flag. The flag says to add by default. A loop checks every character in the string. If a plus or a comma sign is encountered, the flag is set to add, whilst if a minus sign is encountered the flag is set to remove. The character representing a mode will decide which bits to set in (or remove from) the mask. If an equal sign is encountered, the existing mask is set to zero and the flag is set to add before continuing reading characters from the string. The loop continues until the last character is processed.
At the end of the function the mask is checked. If it has every single specific right set (rwxdpo) we return the GENERIC_ALL constant which will cause the system to show the user with All rights set.
This function is called by the function createNTACL.
The purpose of this function is to calculate the size needed to hold the ACEs that will be created if one attempts to create one for each of the CFACEs . This function will only work for CFACEs on NT, because it requires that the access type is set in the CFACE struct.
The function starts by iterating through the list of CFACE structs. Structs that has no name (i.e. no user), structs where the mode is set to default or structs where the class is to be excluded are simply skipped. After this test we attempt to find the size of the user's security identifier (SID) by calling NT's function LookupAccountName. If the size of the SID is 0, the user does not exist and the function skips that struct. If the SID actually does have a size, we store it together with the size needed to hold the ACE , which by default has one DWORD reserved for the SID. Hence the size calculated for each struct with a valid user is the size of the users SID plus the size of the ACE minus a DWORD. The accumulated size for all the structs is returned by the function.
This function is called by the function createNTACL, but only if the method of fixing the file's permission is append.
The function was created to improve the structure of the program and to make it more readable, in particular the function createNTACL. The purpose of this function is to discover whether the file specified by filename actually has an SD and an ACL . If it does, we retrieve information about the acl and update the pointer sent to the function (old_pacl). Remember to free the memory pointed to by the pointer that is returned!
This function is called by the functions appendNTACEs and createNTACL.
The purpose of this function is simple. By sending an existing list of CFACEs and the information needed to create a new CFACE struct, the function will create a new struct with the relevant information and append that struct to the end of the existing list.
Before actually creating a new struct, the functions checks that the NTMode is not 0. If it is, it has been set to 0 by an earlier call to the function getNTModeMask because the user's mode was set to default. Unless NTMode is 0, memory is allocated for the new struct, and the relevant information is copied into the struct.
This function is called by the function createNTACL.
This function has one clear goal. It should enable the appending of users to those already existing in a file's ACL . After calling this function, all existing ACEs should be added to new_aces, possibly with altered permissions if the users that already existed in the file's ACL also existed in the config file.
The function first uses oldACLSize to get the number of ACEs in the existing ACL , and then it iterates through every ACE in the ACL , using the pointer old_pacl to get each one of the ACEs . For every ACE the function must check if the user exists in the configuration file as well, because if he does the permissions must perhaps be altered.
If the user does exist in the config file and the access type given in the config file matches the access type on the existing ACE, the new access mask representing the altered permissions is retrieved by calling getNTModeMask. The mode given in the config file and the mask from the existing ACE will then be used as parameters to getNTModeMask. Then a call to the function attachToNTACEs will hook the user with the new rights to new_aces.
If the existing ACE's username and access type does not match any of the ones in the config file, no alterations on the user's permissions need to be done. A call to attachToNTACEs hooks the user up new_aces with the information already existing in the ACE. The UNIX like mode is omitted, however, and the classes is simply set to be any as we cannot retrieve this information from NT's ACEs .
After iterating through all the existing ACEs , every user that did not already exist in the config file with mode set to default should be a part of new_aces with the correct access mask set, whether the existing ACEs had their permissions altered in any way or not. After calling this function, one should only need to add the users in the config file that has not yet been accounted for to new_aces. Then one should be ready to create a new ACL with an ACE for every struct in new_aces.
This function is called by the function createNTACL.
We call this function when a new list of CFACEs containing every user that is to have their own ACE in the new ACL finally exists. This list is sent to the function as new_aces. Before calling this function, a new ACL must be initialised with the correct size. A pointer to this ACL must then be passed as the parameter new_pacl. Because the order of the ACEs is of great importance, as we have explained earlier, the preferred access type of the ACEs is passed as a parameter to the function. This way, we can call this function twice. Once to create all ACEs with access type denied, then a second time to add all ACEs that are allowed ACEs .
The function starts by iterating through the list of CFEACE structs. For each struct we retrieve the name of the user and attempt to get the size of the user's SID by calling NT's function LookupAccountName. If the SID does have a size, we know that the user exists. We then allocate memory to hold the SID and retrieve it by calling LookupAccountName a second time. We now add an ACE to the ACL pointed to by new_pacl. The ACE is added by a call to either AddAccessAllowedAce or AddAccessDeniedAce depending on the value of the parameter accessType. If the accessType is empty (i.e. not specified in the config file) AddAccessAllowedAce will be called, thus implementing Access Allowed ACE as the default type of ACE . The reasons for choosing Access Allowed ACE as the default ACE type is explained in an earlier chapter. Before moving on to check the next struct in the list we are iterating we free the memory held by the user's SID so that no memory is lost.
This function is called by the function CheckNTACE and is the main controlling function, tying the rest of the functions together.
First of all, the function creates a pointer to an empty list of CFACEs . Potentially, this list will contain every user that is to have his own ACE in the ACL . It then checks the method.
If the method is a, i.e. append, we call getNTACLInformation to get information about the file's existing ACLindexACL. We send a flag to the function, and if the flag is raised when the function returns we know that the file actually had an ACL . If this is the case we call appendNTACEs to append the file's existing ACEs to our empty list of CFACE structs. If the file failed to have an ACL and cfengine is running in verbose mode we let the user know that the file has unrestricted access.
If the method was append we should now have all the file's existing users added to our list of new CFACEs with possibly modified rights. If the method was not append, that list should still be empty. Whatever the case is, the function continues with an iteration of the CFACE list aces containing structs with the information from the config file. Users in this list with an access mask equal to zero (as initialised) will be appended to our new list of CFACE structs because they have not yet been accounted for. Users with an access mask that has a value other than zero have already been processed in the function appendNTACEs. After the iteration of aces every existing user with modified permissions as well as new users specified in the configuration file should have a struct in our new list of CFACEs .
Not all of the users in our new list of CFACEs will get an ACE . Either because they have their mode set to default or because the user is not defined as a user on NT. But our function getNTACEs_Size checks this and returns the size needed to hold all the ACEs that can in fact be created. So we call getNTACEs_Size and get the size needed to hold the ACEs . Then we add the size of the ACL itself and allocate memory for the new ACL with all it's potential ACEs . We then initialise the file's security descriptor and the new ACL .
Now everything is ready to add ACEs to the new ACL . However, as mentioned several times before, the order of the ACEs is important. We call our function addNTACEs once to add all the Access Denied ACEs to our new ACL , and then we call it again to add all the Access Allowed ACEs . Then we put the new ACL into the file's security descriptor (SD ) by calling NT's function SetSecurityDescriptorDacl. After checking that the SD is still valid we install it, in effect connecting it to the file.
At the end of the function we free all the memory that we have allocated earlier and return.
This function is called by a function that already existed in cfengine, but that we have modified to include NT. The function is called checkACLs.
This function does nothing but call createNTACL. This implies that this function is not needed, and strictly speaking it really is not necessary. We still chose to have it to follow the naming convention that seem to already exist in cfengine. So now, depending on the operating system, checkPosixACE, checkDFSACE or checkNTACE will be called from the function checkACLs.
The struct is defined in the file cf.defs.h located under cfengine's src directory.
We have added the variable nt_acltype in case NT is defined, i.e. the variable will be defined when cfengine is run under NT. Windows NT has defined two types of ACLs , system and discretionary . We have been consentrating on discretionary ACLs (DACLs ). However, sometime in the future it may be appropriate to implement the SACLs as well. The variable we have defined here is not yet in use, but is implemented for future use.
The struct is defined in the file cf.defs.h located under cfengine's src directory.
NT operates with both users and groups but they all have their own security identifier, a unique number. This makes the variable acltype redundant on NT. We do not use it in our code, and the value of it (which is specified in cfengine's configuration file) is of no importance.
We have added the variables access and NTMode in case NT is defined, i.e. the variable will be defined when cfengine is run under NT. Windows NT has defined several different types of ACEs but since we have concentrated on discretionary ACLs (DACLs ) we have only implemented the two types of ACEs used by the DACL , which are denied and allowed. Th NTMode is included to hold NT's own access mask representing a user's permission on a file. We added this variable to obtain the most effective and optimised code when implementing cfengine's append functionality on a file's permissions.
This function is coded in the file acl.c, which is located under cfengine's src directory. It is called for each of the lines in the acl definition in cfengine's configuration file, i.e. each of the lines with a colon in the example configuration file as follows:
Here comes "cfengine.conf.acl"
The line is sent as parameter string. This function then allocates memory for a CFACE struct and parses that string, storing the information in the struct. We made some additions to this function to implement NT functionality. We added a new variable and enabled parsing of an additional variable at the end of the line represented in string. The line would usually look something like:
user:kjenslj:rwx
But if cfengine is run on NT, one should be able to specify the type of ACE to be created for that user as well. This is explained in an earlier chapter. If cfengine is run on NT, the new look of the line can be something like:
user:kjenslj:rwx:allowed
The function will still parse it correctly, saving the new access type variable in an additional variable that we have created in the CFACE struct.
If NT is defined we also check if the mode is noaccess. If this is the case, the mode saved in the struct is all and the respective access type is set to be denied. This way an ACE with NT's predefined permission type No Access will be created for the respective user on the file.
This function is coded in the file acl.c, located under cfengine's src directory.
When using the files command in cfengine's configuration file, cfengine will after having parsed the configuration file and found that an acl definition is specified eventually reach this function. We have added a test in this function, checking whether or not the file system is NT, and if it is we call our function CheckNTACE.
For every new functionality that we have implemented, we have performed exhaustive testing through extensive use of cfengine's configuration file, in effect attempting every possibility and combination of input. An analysis of the respective output would repeatedly trigger further investigation of our module's performance under different conditions. Whether those conditions be faulty input from the configuration file, lack of input, unlikely combinations of input, or any other exceptional event, we would continuously work to improve the code. We believe that as of today, most combinations have been tested for an corrected in case of faulty performance. The things we have tested is listed at the end of this section.
Today there are only a few cracks that we are aware of. These flaws in our code have nothing to do with the functionality of the code, but has only an effect on the internal performance of the module, such as possibly allocating too much memory creating too many ACEs . The user will not be aware of these bugs as the program still behaves as expected. Only time has prevented us from correcting them.
Testing of the code has showed us that full manipulation of ACLs in directories not yet works as desired. However, as time has not permitted us of implementing this functionality, we have chosen to not see this as a fault in the code but as a feature yet to be implemented.
The behaviour of our module in the event of an error has also been tested extensively. We have tried to follow the existing convention on error handling, calling previously coded functions to handle error messages. The error messages themselves are also meant to follow an existing convention, producing information as in which module the error occurred, the effect it had and the result. Depending on the seriousness of the error the program will either exit or log the error to a file.
As of today, our code is not yet complete. It should include the complete functionality on file permissions on NT as well as enabling editing the registry and the implementation of UNIX' ps command. The fact that we are missing complete and fully functional code has prevented us from further testing the code. When the ACLs are fully implemented, an extensive and complete test of the functionality should of course be executed, although we hopefully have discovered and corrected all exceptions by that time.
Specifying a user that does not exists triggered an error message before cfengine continued. Nothing was done for the user that did not exist.
Omitting to specify the access type should make cfengine interpret the access type as allowed. This was also the case. Specifying an access type other than the possible values allowed or denied resulted in an error message before cfengine continued. Nothing was done for the user that had a faulty access type.
Specifying permissions that do not exist will trigger an error message before cfengine continued. Nothing was done for the user with the faulty permissions.
Specifying a method or a file system that is invalid is taken care of by existing code in cfengine.
The next two sections documents how our code responds to the two methods of working with ACLs on NT:
Denying rights was more difficult to test as NT 4.0 is unable to show denied permissions in their graphical user interface. That is except for the times when a user is denied all rights. Then that user is showed having No Access. We tried this and it worked. Denied rights can be tested much more extensively in a network, but we do not have a network with NT machines available, and neither do we have the time it would require to do it. We assume that denying specific rights also works as the code is practically identical to when granting those rights.
What was said about denied rights in the previous section Overwriting method applies here two. The only difference was that we were able to test it a little bit more. We first denied some permissions to a user on a file. We could not see the results of that operation because of NT's limitation on it's graphical user interface (GUI). However, when we ran cfengine again we denied the same user the rest of the permissions. The result was as desired. The user had been denied all rights, and this is shown by NT as No Access in the GUI. We tried this several times, and it always worked. This implies that the denied functionality works the way it is supposed to.
In the configuration file, the manual says that the type of acl, the acl_type, is to be specified as either user or group. However, we never test the value of acl_type because it does not matter. All we need to create an ACE is the name of the user/group to find the respective security identifier (SID). The value of acl_type is therefore irrelevant, but we have not tested what happens if something other than user or group is specified. This is something to be checked out.
A star/asterisk in the field for user/group would normally imply that the ACL applies to the owner of the file object. However this functionality is as of today not yet implemented. It should not be a problem to fix this, though.
In the function getNTModeMask we check if the mode is noaccess. This test may be omitted though because this is checked already when parsing the configuration file. If noaccess is the mode then all will be stored in the CFACE with the access type set to denied, thus making the test in getNTModeMask redundant.
When checking the existing ACEs from the old ACL against the users specified in the config file, we stop testing when a user with the same name is found (in appendNTACEs). Then the mask is corrected and saved. However, it is possible that several users with the same access type have been specified in the configuration file. This should cause the mask of the original ACE to be manipulated further, but this is not the case today. We should not break the loop that iterates through every user in the config file when a match is found, but continue to check the old ACE against every single user.
When the overwrite is used, or after the users that existed has been modified if append is used, there is a small bug. We now iterate through the list of the users from the config file, adding every one that has a mask=0 to the new list that specifies who should get an ACE in the new ACL. However, if several users with the same name occurs several times, that user will also be added several times. This should not happen, although NT seems to be able to handle it. We do not know the exact effect even it seems to be OK. Besides, in any case we are wasting space. We should always check the user that is to be added to our new list against every user that already exist in that new list.
Since we learned about the directories, we believe that the function returning the access mask (getNTModeMask) must be modified. Today, the mask returned will use the specific bits of the mask. However, when working with directories it is sometimes the case that the generic part of the mask should be used instead of the specific. We believe that an inheritance flag located in the ACE header decides this. If so, the flag should be sent to the function deciding which part of the mask we should use when setting the bits in it.
This manual is meant to be sufficient for you to successfully use ACL's on cfengine for NT, but will not provide you with much understanding. For a fuller comprehension we recommend that you read the previous chapters in this report.
acl: class:: { acl-alias action }Cfengine's ACL feature is a common interface for managing file system access control lists (ACLs). An access control list is an extended file permission. It allows you to open or close a file to a named list of users (without having to create a group for those users); similarly, it allows you to open or close a file for a list of groups. Several operating systems have access control lists, but each typically has a different syntax and different user interface to this facility, making it very awkward to use. This part of a cfengine configuration simplifies the management of ACLs by providing a more convenient user interface for controlling them and--as far as possible--a common syntax.
An ACL may, by its very nature, contain a lot of information. Normally you would set ACLs in a files command, or a copy command. It would be too cumbersome to repeat all of the information in every command in your configuration, so cfengine simplifies this by first associating an alias together with a complex list of ACL information. This alias is then used to represent the whole bundle of ACL entries in a files orcopy command. The form of an ACL is similar to the form of an editfiles command. It is a bundle of information concerning a file's permissions.
{ acl-alias method:overwrite/append fstype:posix/solaris/dfs/afs/hpux/nt acl_type:user/group:permissions acl_type:user/group:permissions ... }The name acl-alias can be any identifier containing alphanumeric characters and underscores. This is what you will use to refer to the ACL entries in practice. The method entry tells cfengine how to interpret the entries: should a file's ACLs be overwritten or only adjusted? Since the file systems from different developers all use different models for ACLs, you must also tell cfengine what kind of file system the file resides on. Currently only solaris and DCE/DFS ACLs are implemented.
NOTE: if you set both file permissions and ACLs the file permissions override the ACLs.
An access control list is build of any number of individual access control entries (ACEs ). The ACEs has the following general syntax:
acl_type:user/group:permissionsThe user or group is sometimes referred to as a key.
For an explanation of ACL types and their use, refer to your local manual page. However, note that for each type of file system, there are certain entries which must exist in an ACL. If you are creating a new ACL from scratch, you must specify these. For example, in solaris ACLs you must have entries for user, group and other. Under DFS you need what DFS calls a user_obj, group_obj and an other_obj, and in some cases mask_obj. In cfengine syntax these are called user:*:, @other:*: and mask:*:, as described below. If you are appending to an existing entry, you do not have to re-specify these unless you want to change them.
Cfengine can overwrite (replace) or append to one or more ACL entries.
If the new ACL exactly matches the existing ACL, the ACL is not replaced.
The individual bits in an ACE may be either added subtracted or set equal to a specified mask. The + symbol means add, the - symbol subtract and = means set equal to. Here are some examples:
acltype:id/*:mask user:mark:+rx,-w user:ds:=r user:jacobs:noaccess user:forgiven:default user:*:rw group:*:r other:*:r
The keyword noaccess means set all access bits to zero for that user, i.e. remove all permissions. The keyword default means remove the named user from the access control list altogether, so that the default permissions apply. A star/asterisk in the centre field indicates that the user or group ID is implicitly specified as of the owner of the file, or that no ID is applicable at all (as is the case for `other').
acl_type:user/group:permissions:accesstype
The actual change consists of the extra field containing the access type. A star/asterisk in the field for user/group would normally imply that the ACL applies to the owner of the file object. However this functionality is as of today not yet implemented.
In NT, the ACL type can be one of the following:
user groupBoth types require that you specify the name of a user or a group.
NT permissions are comprised of the bits 'rwxdpo', where:
r - Read privileges w - Write privileges x - Execute privileges d - Delete privileges p - Privileges to change the permissions on the file o - Privileges to take ownership of the fileIn addition to any combination of these bits, the word noaccess or default can be used as explained in the previous section. NT comes with some standard, predefined permissions. The standards are only a predefined combination of the different bits specified above and are provided with cfengine as well. You can use the standards by setting the permission to read, change or all. The bit implementation of each standard is as on NT:
read - rx change - rwxd all - rwxdpowhere the bits follow the earlier definition. The keywords mentioned above can only be used alone, and not in combination with +, -, = and/or other permission bits.
NT defines several different access types, of which only two are used in connection with the ACL type that is implemented in cfengine for NT. The access type can be one of the following:
allowed deniedIntuitively, allowed access grants the specified permissions to the user, whilst denied denies the user the specified permissions. If no access type is specified, the default is allowed. This enables cfengine's behaviour as on UNIX systems without any changes to the configuration file. If the permissions noaccess or default is used, the access type will be irrelevant.
control: actionsequence = ( files ) domain = ( iu.hioslo.no ) files: $(HOME)/tt acl=acl_alias1 action=fixall acl: { acl_alias1 method:overwrite fstype:nt user:gustafb:rwx:allowed user:mark:all:allowed user:toreo:read:allowed user:torej:default:allowed user:ds2:+rwx:allowed group:dummy:all:denied group:iu:read:allowed group:root:all:allowed group:guest:dpo:denied }
This document was generated using the LaTeX2HTML translator Version 99.2beta6 (1.42)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -html_version 2.0 -ascii_mode -split 0 cfengine-NT.tex
The translation was initiated by Paul Visscher on 2001-07-19
Return to GNU's home page.
Please send FSF & GNU inquiries & questions to gnu@gnu.org. There are also other ways to contact the FSF.
Please send comments on these web pages to webmasters@gnu.org, send other questions to gnu@gnu.org.
Copyright (C) 2001 Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA
Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.
Updated: $Date: 2002/08/22 21:26:45 $ $Author: bkuhn $