1 """Functionality common to all account classes.
3 Each account class must provide five methods: create(), destroy(),
4 configure(), start(), and stop(). In addition, it must provide static
5 member variables SHELL, which contains the unique shell that it uses;
6 and TYPE, which contains a description of the type that it uses. TYPE
7 is divided hierarchically by periods; at the moment the only
8 convention is that all sliver accounts have type that begins with
11 Because Python does dynamic method lookup, we do not bother with a
12 boilerplate abstract superclass.
14 There are any number of race conditions that may result from the fact
15 that account names are not unique over time. Moreover, it's a bad
16 idea to perform lengthy operations while holding the database lock.
17 In order to deal with both of these problems, we use a worker thread
18 for each account name that ever exists. On 32-bit systems with large
19 numbers of accounts, this may cause the NM process to run out of
20 *virtual* memory! This problem may be remedied by decreasing the
33 # shell path -> account class association
35 # account type -> account class association
38 def register_class(acct_class):
39 """Call once for each account class. This method adds the class to the dictionaries used to look up account classes by shell and type."""
40 shell_acct_class[acct_class.SHELL] = acct_class
41 type_acct_class[acct_class.TYPE] = acct_class
44 # private account name -> worker object association and associated lock
45 _name_worker_lock = threading.Lock()
49 """Return the names of all accounts on the system with recognized shells."""
50 return [pw_ent[0] for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
53 """Return the worker object for a particular username. If no such object exists, create it first."""
54 _name_worker_lock.acquire()
56 if name not in _name_worker: _name_worker[name] = Worker(name)
57 return _name_worker[name]
58 finally: _name_worker_lock.release()
61 def install_keys(rec):
62 """Write <rec['keys']> to <rec['name']>'s authorized_keys file."""
64 dot_ssh = '/home/%s/.ssh' % name
65 def do_installation():
66 if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
67 tools.write_file(dot_ssh + '/authorized_keys', lambda thefile: thefile.write(rec['keys']))
68 logger.log('%s: installing ssh keys' % name)
69 tools.fork_as(name, do_installation)
73 # these semaphores are acquired before creating/destroying an account
74 _create_sem = threading.Semaphore(1)
75 _destroy_sem = threading.Semaphore(1)
77 def __init__(self, name):
78 self.name = name # username
79 self._acct = None # the account object currently associated with this worker
81 # outsiders request operations by putting (fn, args...) tuples on _q
82 # the worker thread (created below) will perform these operations in order
83 self._q = Queue.Queue()
84 tools.as_daemon_thread(self._run)
86 def ensure_created(self, rec):
87 """Cause the account specified by <rec> to exist if it doesn't already."""
88 self._q.put((self._ensure_created, rec.copy()))
90 def _ensure_created(self, rec):
91 curr_class = self._get_class()
92 next_class = type_acct_class[rec['account_type']]
93 if next_class != curr_class:
94 self._destroy(curr_class)
95 self._create_sem.acquire()
96 try: next_class.create(self.name)
97 finally: self._create_sem.release()
99 self._acct.configure(rec)
100 if next_class != curr_class: self._acct.start()
102 def ensure_destroyed(self): self._q.put((self._ensure_destroyed,))
103 def _ensure_destroyed(self): self._destroy(self._get_class())
105 def start(self): self._q.put((self._start,))
107 self._make_acct_obj()
110 def stop(self): self._q.put((self._stop,))
112 self._make_acct_obj()
115 def _destroy(self, curr_class):
118 self._destroy_sem.acquire()
119 try: curr_class.destroy(self.name)
120 finally: self._destroy_sem.release()
122 def _get_class(self):
123 try: shell = pwd.getpwnam(self.name)[6]
124 except KeyError: return None
125 return shell_acct_class[shell]
127 def _make_acct_obj(self):
128 curr_class = self._get_class()
129 if not isinstance(self._acct, curr_class): self._acct = curr_class(self.name)
136 except: logger.log_exc()