Added acl.grant and acl.revoke functions.
[py-rsbac] / rsbac / _utils.py
1 # -*- coding:utf-8 -*-
2
3 # py-rsbac - RSBAC Python bindings
4 # Copyright (C) 2006  Frederic Jolliton <pyrsbac@tuxee.net>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 import socket
21
22 from ctypes import byref, cast, POINTER
23
24 from rsbac import headers
25 from rsbac.errors import raiseIfError
26
27 #--[ ctypes ]-----------------------------------------------------------------
28
29 def aptr( o ) :
30     """Return a pointer to base type of ctypes array 'o'."""
31     t = o._type_
32     return cast( byref( o ) , POINTER( t ) )
33
34 def slowButCorrectStringAssignation( object , field , data , nul = 1 ) :
35     """Carefully store a Python string into a ctypes field.
36
37     This function is obviously a temporary solution. We should find a
38     way to correctly put a string into a c_char array field in a
39     structure (or union) with always a NUL character at the end of the
40     string. Currently, ctypes doesn't report error if the string fill
41     the array completly and if there is no room to put the NUL
42     character. Also, a NUL character is silently interpreted as the
43     end of the string!
44
45     This function take cares of these points.
46
47     """
48     if '\x00' in data :
49         raise RuntimeError , 'a string intended for ctypes cannot contains the NUL character.'
50     size = getattr( object , '_sz_' + field , None )
51     if size is None :
52         cls = object.__class__
53         for name , desc in cls._fields_ :
54             if name == field :
55                 size = desc._length_
56                 setattr( cls , '_sz_' + field , size )
57                 break
58         else :
59             raise RuntimeError , 'unknown field %r in object of class %r' % ( field , cls )
60     if len( data ) > size - nul :
61         raise ValueError , '[XXX] string too long (%d, maximum length %d)' % ( len( data ) , size - nul )
62     setattr( object , field , data )
63
64 def stringToByteArray( array , string , fill = 0 , minPadding = 1 ) :
65     """Copy a string into a ctypes array of character.
66
67     Copy the string 'string' into ctypes array 'array'. An error is
68     thrown if string is too long. Array if filled with 'fill' value if
69     string is too short.
70
71     array -- ctypes array of character
72     string -- string to store in the array
73     fill -- fill value to use if string is smaller than the array size
74     minPadding -- mininum number of fill values to store at the end of
75     the array
76
77     Returns the first parameter.
78
79     """
80     a = len( array )
81     b = len( string )
82     if b > a - minPadding :
83         raise RuntimeError , \
84             'string of length %d cannot fit in buffer on length %d (%d)' \
85             % ( b , a - minPadding , a )
86     m = min( a , b )
87     i = 0
88     while i < m :
89         array[ i ] = ord( string[ i ] )
90         i += 1
91     while i < a :
92         array[ i ] = fill
93         i += 1
94     return array
95
96 def byteArrayToString( array ) :
97     """Read a string from a ctypes array of character.
98
99     array -- ctypes array of character
100
101     Returns a string.
102
103     """
104     end = -1
105     for end in range( 0 , len( array ) ) :
106         if not array[ end ] :
107             break
108     else :
109         end += 1
110     if end >= 0 :
111         return ''.join( map( chr , array[ : end ] ) )
112     else :
113         return ''.join( map( chr , array ) )
114
115 #--[ rsbac ]------------------------------------------------------------------
116
117 # The fetch function take care of extracting all the value from a
118 # RSBAC list. Several attempts are made if the list is updated while
119 # we try to read it.
120 #
121 # Say that L = [a,b,d]. We ask for the length and we get 3, but in the
122 # meantime, L becomes [a,b,c,d]. Then if we ask for 3 elements, we get
123 # [a,b,c].. missing one item!
124 #
125 # Internall, RSBAC tools prevent this by adding a constant amount of
126 # place but it's not perfect.
127 #
128 # Note that if the list is updated concurrently, then we can't be sure
129 # that it's up-to-date. Transaction may helps though.
130 #
131 # So, the only thing that is guaranteed, is that the list is complete
132 # according to the state at the time the request was made.
133
134 # FIXME: For multiple type, allows None to don't request a particular
135 # array?
136
137 def fetch( type , fun , start = None ) :
138     """Fetch RSBAC array.
139
140     Fetch one or several RSBAC arrays of types specified by 'type', by
141     calling the function 'fun'. This function should takes several
142     arguments: the first argument receive the number of items to
143     retrieve and the remaining argument are ctypes pointer to already
144     allocated arrays. This function act as a callback called by fetch,
145     and may be called an unspecified number of times.
146
147     type -- either a single ctypes type, or a tuple of ctypes type
148     fun -- function taking at least 2 arguments
149     start -- None or an integer
150
151     Returns a single list if 'type' was a ctypes type, or a tuple of
152     list if 'type' was a tuple of ctypes type.
153
154     """
155     if start is None :
156         if not isinstance( type , ( tuple , list ) ) :
157             n = raiseIfError( fun( 0 , None ) )
158         else :
159             n = raiseIfError( fun( 0 , *( [ None ] * len( type ) ) ) )
160     else :
161         n = max( start , 1 )
162     if not isinstance( type , ( tuple , list ) ) :
163         while 1 :
164             # The only way to ensure that we got all the items is to ask
165             # for more and check if the result contains less than asked.
166             # If not, we increase the buffer size and we try again.
167             n += 1
168             arr = ( type * n )()
169             m = raiseIfError( fun( n , aptr( arr ) ) )
170             if m < n :
171                 # If no items are created while calling rsbac_rc_get_list,
172                 # then we will exit this loop at the first iteration.
173                 break
174             # Exponentially raise the size of the array.
175             n += n // 2 + 1
176         return arr[ : m ]
177     else :
178         types = type
179         while 1 :
180             # The only way to ensure that we got all the items is to ask
181             # for more and check if the result contains less than asked.
182             # If not, we increase the buffer size and we try again.
183             n += 1
184             arrs = [ ( type * n )() for type in types ]
185             m = raiseIfError( fun( n , *map( aptr , arrs ) ) )
186             if m < n :
187                 # If no items are created while calling rsbac_rc_get_list,
188                 # then we will exit this loop at the first iteration.
189                 break
190             # Exponentially raise the size of the array.
191             n += n // 2 + 1
192         return tuple( arr[ : m ] for arr in arrs )
193
194 #--[ TTL ]--------------------------------------------------------------------
195
196 undefinedTtl = False   # nothing
197 unlimitedTtl = True    # map to 0
198 unchangedTtl = None    # map to (rsbac_time_t)-1
199
200 _ttlMax = int( headers.rsbac_time_t( -1 ).value )
201 assert _ttlMax > 0
202
203 def ttlToInt( ttl ) :
204     if ttl is True :
205         ttl = 0
206     elif ttl is None :
207         ttl = _ttlMax # keep
208     else : # include False
209         if ttl >= _ttlMax :
210             raise RuntimeError , 'TTL must be below %d seconds' % _ttlMax
211         ttl = int( ttl )
212         if ttl < 1 :
213             ttl = 1
214     return ttl
215
216 def intToTtl( n ) :
217     if not n :
218         return True
219     else :
220         assert n >= 1
221         return int( n )
222
223 def ttlToTuple( ttl ) :
224     if ttl is False or ttl < 1 :
225         return False , 0
226     elif ttl is True :
227         return True , 0
228     else :
229         return True , ttl
230
231 def tupleToTtl( t ) :
232     flag , ttl = t
233     if not flag :
234         return False
235     elif ttl == 0 :
236         return True
237     else :
238         return int( ttl )
239
240 #-----------------------------------------------------------------------------
241
242 # FIXME: endianess?
243 def intToIp( n ) :
244     """Convert an IP represented as an integer to a tuple of integer.
245
246     intToIp(3232235821) => (192, 168, 1, 45)
247
248     """
249     n = socket.htonl( n )
250     return ( int( ( n >> 24 ) & 0xff ) ,
251              int( ( n >> 16 ) & 0xff ) ,
252              int( ( n >>  8 ) & 0xff ) ,
253              int(   n         & 0xff ) )
254
255 # FIXME: endianess?
256 def ipToInt( ip ) :
257     """Convert an IP represented as a tuple of integer to an integer.
258
259     ipToInt((192, 168, 1, 45)) => 3232235821
260
261     """
262     ip = socket.ntohl( ip )
263     if isinstance( ip , tuple ) :
264         return reduce( lambda a , b : ( a << 8 ) | b , ip , 0 )
265     elif isinstance( ip , ( int , long ) ) :
266         return ip & 0xffffffff
267     else :
268         return ip
269
270 def processName( pid ) :
271     """Get the name of a process.
272
273     Note: Extract the process name from /proc.
274
275     pid -- process ID (PID)
276
277     Returns the name of the process as string.
278
279     """
280     if isinstance( pid , int ) and pid > 0 :
281         try :
282             f = file( '/proc/%d/stat' % pid )
283             line = f.readline()
284             f.close()
285             return line[ line.find( '(' ) + 1 : line.rfind( ')' ) ]
286         except :
287             pass
288
289 def digits( n , base = 10 , size = None ) :
290     """Extract the digits from the integer 'n'.
291
292     Extract all digits (unless size is not None) in base 'base' from
293     integer 'n'. If 'size' is not None, then exactly 'size' digits are
294     extracted, adding heading 0s if necessary.
295
296     n -- integer
297     base -- integer (>= 2)
298     size -- None or an integer
299
300     Returns an array of integer.
301
302     digits(30, 4) => (1, 3, 2) because 30 is written 132 is base 4.
303
304     digits(30, 4, 6) => (0, 0, 0, 1, 3, 2)
305
306     digits(30, 4, 2) => (3, 2)  Note that a digit is missing, since we asked
307                                 only for 2.
308     """
309     r = []
310     if n < 0 :
311         n = -n
312     if size is None :
313         while 1 :
314             r.append( int( n % base ) )
315             n //= base
316             if not n :
317                 break
318     else :
319         for i in range( size ) :
320             r.append( int( n % base ) )
321             n //= base
322             if not n :
323                 r += ( 0 , ) * ( size - i - 1 )
324                 break
325     r.reverse()
326     return tuple( r )
327
328 # Local Variables:
329 # indent-tabs-mode: nil
330 # python-indent: 4
331 # End: