File: apt_proxy_conf.py

package info (click to toggle)
apt-proxy 1.9.35-0.3
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 616 kB
  • ctags: 712
  • sloc: python: 3,749; sh: 384; makefile: 63
file content (298 lines) | stat: -rw-r--r-- 10,489 bytes parent folder | download
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#
# Copyright (C) 2002 Manuel Estrada Sainz <ranty@debian.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

from apt_proxy import Backend
from misc import log
import os, sys, re
from types import StringType, NoneType
import urlparse
from ConfigParser import RawConfigParser,DEFAULTSECT
    
class ConfigError(Exception):
    def __init__(self, message):
        self.message = message
    def __str__(self):
        return repr(self.message)

class apConfigParser(RawConfigParser):
    """
    Adds 'gettime' to ConfigParser to interpret the suffixes.
    Interprets 'disabled_keyword' as disabled (None).
    """
    time_multipliers={
        's': 1,    #seconds
        'm': 60,   #minutes
        'h': 3600, #hours
        'd': 86400,#days
        }
    DISABLED_KEYWORD = 'off'
    def isOff(self, section, option):
        value = self.get(section, option)
        return value == self.DISABLED_KEYWORD

    def getint(self, section, option):
        value = self.get(section, option)
        return int(value)
    def gettime(self, section, option):
        mult = 1
        value = self.get(section, option)
        if len(value) == 0:
            raise ConfigError("Configuration parse error: [%s] %s" % (section, option))
        suffix = value[-1].lower()
        if suffix in self.time_multipliers.keys():
            mult = self.time_multipliers[suffix]
            value = value[:-1]
        return int(value)*mult
    def getstring(self, section, option):
        return self.get(section,option)
    def getstringlist(self, section, option):
        return self.get(section,option).split()
    def getproxyspec(self, section, option):
        "Get http proxy info from string"
        p = ProxyConfig(self.get(section,option))
        if p.host is not None:
            return p
        else:
            return None

class apConfig:
    """
    Configuration module for apt-proxy
    holds main configuration values in class and backend
    configs in backends[backend-name]
    """
    
    """
    Configuration items for default section
    [0] - name of config parameter and resulting class variable name
    [1] - default value
    [2] - getXXX method to use to read config.
          A method prefixed with '*' will return None if disabled
    """
    CONFIG_ITEMS = [
        ['address', '', 'string'],
        ['port', 9999, 'int'],
        ['min_refresh_delay', 30, 'time'],
        ['complete_clientless_downloads', False, 'boolean'],
        ['telnet_port', 0, 'int'],
        ['telnet_user', '', 'string'],
        ['telnet_pass', '', 'string'],
        ['timeout', 30, 'time'],
        ['cleanup_freq', 600, '*time'],
        ['cache_dir', '/var/cache/apt-proxy', 'string'],
        ['max_versions', 3, '*int'],
        ['max_age', 10, '*time'],
        ['import_dir', '/var/cache/apt-proxy/import', 'string'],
        ['passive_ftp', 'on', 'boolean'],
        ['dynamic_backends', 'on', 'boolean'],
        ['http_proxy', None , 'proxyspec'],
        ['username', 'aptproxy', 'string'],
        ['bandwidth_limit', None, '*int']
        ]

    """
    Configuration items for backends
    [0] - name of config parameter and resulting class variable name
    [1] - default value, None to use factory default
    [2] - getXXX method to use to read config.
          A method prefixed with '*' will return None if disabled
    """
    BACKEND_CONFIG_ITEMS = [
        ['timeout', None, 'time'],
        ['passive_ftp', None, 'boolean'],
        ['backends', '', 'stringlist'],
        ['http_proxy', None , 'proxyspec'],
        ['bandwidth_limit', None, '*int']
        ]

    DEFAULT_CONFIG_FILE = ['/etc/apt-proxy/apt-proxy-v2.conf',
                            '/etc/apt-proxy/apt-proxy-2.conf',
                            '/etc/apt-proxy/apt-proxy.conf']

    "Backend configurations are held here"
    backends = {}
    parser = None
    debugDomains = {}
    debug = '0'
    
    def __init__(self, config_file = None):
        """
        Read configuration from specified source, or default config
        file location if not specified

        @param config_file Filename to read, or descriptor of already-open file
        """
        self.backends = {}
        if type(config_file) is StringType or type(config_file) is NoneType:
            c = self.readFromFile(config_file)
        else:
            c = self.readFromStream(config_file)

        self.parseConfig(c)

    def readFromFile(self, config_file):
        """
        Read configuration from filename given
        @param config_file filename to read from, or None for default
        """
        conf = apConfigParser()

        if config_file is not None:
            config_files = config_file,
        else:
            config_files = self.DEFAULT_CONFIG_FILE

        for f in config_files:
            if os.path.exists(f):
                conf.read(f)
                break
        else:
            raise ConfigError("%s: Configuration file does not exist" % config_files[0])

        return conf

    def readFromStream(self, filehandle):
        "Read from open file handle, for test suite"
        conf = apConfigParser()
        conf.readfp(filehandle)
        filehandle.close()
        return conf
    
    def setDebug(self):
        "Set logger debug level"
        for domain in self.debug.split():
            #print "domain:",domain
            if domain.find(':') != -1:
                name, level = domain.split(':')
            else:
                name, level = domain, 9
            self.debugDomains[name] = int(level)
        log.setDomains(self.debugDomains)

    def parseConfig(self, config):
        "Read values from apConfigParser config"

        # debug setting
        if config.has_option(DEFAULTSECT, 'debug'):
            self.debug=config.get(DEFAULTSECT, 'debug')
        else:
            self.debug='all:3'
        self.setDebug()

        # read default values
        for name,default,getmethod in self.CONFIG_ITEMS:
            value = self.parseConfigValue(config, DEFAULTSECT, name, default, getmethod)
            setattr(self, name, value)
            if value != default and name != "telnet_pass":
                log.debug("config value %s=%s"%(name, value), 'config')

        self.address = self.address.split(" ")

        if not self.telnet_user or not self.telnet_pass:
            self.telnet_port = 0  # No server if empty username or password

        # Read backend configurations
        for backendName in config.sections():
            self.addBackend(config, backendName)

    def addBackend(self, config, backendName, backendServers=None):
        """
        Add a new backend configuration
        @param config Configuration file parser to get backend values from.  If None, backend is dynamic
        @param backendName Name of backend to create
        @param backendServers List of backend servers to use (if backend is dynamic)
        @ret newly created apBackendConfig
        """
        if backendName.find('/') != -1:
            log.msg("WARNING: backend %s contains '/' (ignored)"%(name))
            return None

        backend = apBackendConfig(backendName)
        for paramName,default,getmethod in self.BACKEND_CONFIG_ITEMS:
            if default is None:
                default = getattr(self, paramName) # Use default section's default value
            value = self.parseConfigValue(config, backendName, paramName, default, getmethod)
            setattr(backend,paramName, value)
            if value != default:
                log.debug("[backend %s] %s=%s"%(backendName, paramName, value), 'config')

        if backendServers is None:
            backend.dynamic = False
            backendServers = backend.backends
        else:
            # Dynamic backend
            backend.dynamic = True

        backend.backends = []
        for server in backendServers:
            if server[-1] == '/':
                log.msg ("Removing unnecessary '/' at the end of %s"%(server))
                server = server[0:-1]
            if urlparse.urlparse(server)[0] in ['http', 'ftp', 'rsync', 'file']:
                backend.backends.append(server)
            else:
                log.msg ("WARNING: Wrong server '%s' found in backend '%s'. It was skipped." % (server, backendName))
                return None
        if len(backend.backends) == 0:
            log.msg("WARNING: [%s] has no backend servers (ignored)"%backendName)
            return None

        self.backends[backendName] = backend
        return backend

    def parseConfigValue(self, config, section, name, default, getmethod):
        "Determine value of given config item"
        if config is None or not config.has_option(section, name):
            return default
        if getmethod[0]=='*':
            if config.isOff(section, name):
                return None
            else:
                return getattr(config, 'get'+getmethod[1:])(section, name)
        else:
                return getattr(config, 'get'+getmethod)(section, name)

class apBackendConfig:
    """
    Configuration information for an apt-proxy backend
    """
    name = "UNKNOWN"
    def __init__(self, name):
        self.name = name

class ProxyConfig:
    """
    Configuration information for backend server proxies
    """
    host = None
    port = None
    user = None
    password = None

    def __init__(self, proxyspec):
        if proxyspec=='':
            return
        m = re.match('^((?P<user>.*):(?P<password>.*)@)?(?P<host>[a-zA-Z0-9_.+=-]+):(?P<port>[a-zA-Z0-9]+)',
                     proxyspec)
        if m:
            self.host = m.group('host')
            self.port = m.group('port')
            try:
                self.port = int(self.port)
            except ValueError:
                pass
            self.user = m.group('user')
            self.password = m.group('password')