summaryrefslogtreecommitdiffstats
path: root/setacls.py
blob: c17521b61340304329b73b891f72f10b508b2f8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/python

# Copyright 2009  Jon Stanley
#
# This copyrighted material is made available to anyone wishing to use, modify,
# copy, or redistribute it subject to the terms and conditions of the GNU
# General Public License v.2.  This program is distributed in the hope that it
# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.  You should have
# received a copy of the GNU General Public License along with this program;
# if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
# Fifth Floor, Boston, MA 02110-1301, USA.
#
# Author(s): Jon Stanley <jonstanley@gmail.com>
#


import sys
import posix1e
import re
import grp
import pwd
import os
from optparse import OptionParser

CVSBASE = '/cvs/pkgs'
GROUP = re.compile('^@')
# this will become the group owner of all of the directories

scm_admin_group = 'cvsadmin'

# these groups can commit to anything, we'll just add them to the ACL like any
# other groups like provenpackager
secondary_arch_groups = ['fedora-s390', 'fedora-ia64',
        'fedora-sparc']
version = '0.1'

try:
    avail_file = open('avail.pkgs','r')
except IOError:
    sys.stderr.write('Cannot open avail file!\n')
    sys.exit(1)

pkgs = {}

def get_one(pkg):
    '''Returns applicable ACL's for a given package. Supports multiple matches,
    returns a dict of paths that we want to set and ACL's'''
    pkg_ret = {}
    item_regex = re.compile('rpms/%s.*' % pkg)
    for item in pkgs.keys():
        if item_regex.match(item):
            pkg_ret[item] = build_acl(pkgs[item])
    return pkg_ret

def get_all():
    '''Returns all packages in a dict of ACL objects. The keys of the dict are paths
    of the branches that we want to set ACL's on'''
    acls = {}
    for line in pkgs.keys():
        acl_list = pkgs[line]
        acls[line] = build_acl(acl_list)
    print acls
    for acl in acls.keys():
        print acls[acl]

def build_acl(acl_text):
    '''Builds an ACL from a line in the avail file. Expects a list of users and
    groups (groups should be prepended with an @), and returns an ACL object.
    This will also add the secondary arch groups to the directories'''

    for item in acl_text:
        if GROUP.match(item):
            try:
                groups.append(item.lstrip('@'))
            except NameError:
                groups = [item.lstrip('@')]
        else:
            try:
                people.append(item)
            except NameError:
                people = [item]
    acl = posix1e.ACL()

    user = posix1e.Entry(acl)
    user.tag_type = posix1e.ACL_USER_OBJ
    user.permset.add(posix1e.ACL_READ | posix1e.ACL_WRITE |
            posix1e.ACL_EXECUTE)

    group = posix1e.Entry(acl)
    group.copy(user)
    group.tag_type=posix1e.ACL_GROUP_OBJ

    other = posix1e.Entry(acl)
    other.tag_type=posix1e.ACL_OTHER
    other.permset.add(posix1e.ACL_READ | posix1e.ACL_EXECUTE)

    mask = posix1e.Entry(acl)
    mask.tag_type=posix1e.ACL_MASK
    mask.permset.add(posix1e.ACL_READ | posix1e.ACL_WRITE |
            posix1e.ACL_EXECUTE)

    for item in groups:
        group_acl = posix1e.Entry(acl)
        group_acl.tag_type = posix1e.ACL_GROUP
        group_acl.qualifier = grp.getgrnam(item).gr_gid
        group_acl.permset.add(posix1e.ACL_READ | posix1e.ACL_WRITE |
                posix1e.ACL_EXECUTE)

    for item in secondary_arch_groups:
        group_acl = posix1e.Entry(acl)
        group_acl.tag_type = posix1e.ACL_GROUP
        group_acl.qualifier = grp.getgrnam(item).gr_gid
        group_acl.permset.add(posix1e.ACL_READ | posix1e.ACL_WRITE |
                posix1e.ACL_EXECUTE)

    for item in people:
        people_acl = posix1e.Entry(acl)
        people_acl.tag_type = posix1e.ACL_USER
        people_acl.qualifier = pwd.getpwnam(item).pw_uid
        people_acl.permset.add(posix1e.ACL_READ | posix1e.ACL_WRITE |
                posix1e.ACL_EXECUTE)
    return acl

def apply_acls(acl_dict, dry_run=False):
    '''Applies ACL's to the filesystem. Take a dictionary of ACL's, with the
    keys being the path that you would like to ACL's on. The global variable
    CVSBASE will be prepended to the keys of the dict. Ownership of the
    directory on completion will be root:<scm_admin_group>. The ACL sets the
    directories to be group writable, so the admin group will be able to write
    to the directories, in addition to anyone specifically authorized'''

    scm_admin_gid = grp.getgrnam(scm_admin_group).gr_gid

    for dir in acl_dict.keys():
        real_dir = os.path.join(CVSBASE, dir)
        if not dry_run:
            acl_dict[dir].applyto(real_dir, posix1e.ACL_TYPE_DEFAULT)
            acl_dict[dir].applyto(real_dir, posix1e.ACL_TYPE_ACCESS)
            os.chown(real_dir, 0, scm_admin_gid)
        else:
            print 'would apply the following ACL to %s' % real_dir
            print acl_dict[dir]
        for file in os.listdir(real_dir):
            real_file = os.path.join(real_dir, file)
            if not dry_run:
                if os.path.isdir(real_file):
                    acl_dict[dir].applyto(real_file, posix1e.ACL_TYPE_DEFAULT)
                    acl_dict[dir].applyto(real_file, posix1e.ACL_TYPE_ACCESS)
                    os.chown(real_file, 0, scm_admin_gid)
                if os.path.isfile(real_file):
                    acl_dict[dir].applyto(real_file, posix1e.ACL_TYPE_ACCESS)
                    os.chown(real_file, 0, scm_admin_gid)
            else:
                print 'would apply the following ACL to %s' % real_file
                print acl_dict[dir]


def main():
    usage = '%prog [options] [pkgs...]'
    parser = OptionParser(usage, version=version)
    parser.add_option('-a', '--all-packages', action='store_true',
            dest='all', help='Operate on all packages (slow and destructive')
    parser.add_option('-D', '--debug', action='store_true', dest='debug', help=
            'Print extra debug info')
    parser.add_option('-d', '--dry-run', action='store_true', dest='dr', help=
            'Just print what would be done')
    options, args = parser.parse_args()

    # read the avail file into memory to make things faster

    for line in avail_file.readlines():
        parsed, pkg = line.strip().split(' | ')[1].split(','), \
            line.strip().split(' | ')[2]
        pkgs[pkg] = parsed

    if not options.all and not args:
        sys.stderr.write('either the -a option or some packages must be given\n')
        sys.exit(1)

    if options.all and args:
        sys.stderr.write('You cannot specify both -a and a list of packages\n')
        sys.exit(1)

    # something of a hack here - this list will only ever have one element.
    # This is for support of multiple command line arguments and making sure
    # that what's around is always a list. See below for when it can have more
    # than one

    if options.all:
        acl_list = []
        acl_list.append = get_all()

    if options.dr:
        dry_run = True
    else:
        dry_run = False

    if args:
        acl_list = []
        for arg in args:
            acl_list.append(get_one(arg))

    for acl_dict in acl_list:
        apply_acls(acl_dict, dry_run)

if __name__ == '__main__':
    main()