1 """Functionality common to all account classes.
3 Each subclass of Account must provide five methods: create(),
4 destroy(), configure(), start(), and stop(). In addition, it must
5 provide static member variables SHELL, which contains the unique shell
6 that it uses; and TYPE, which contains a description of the type that
7 it uses. TYPE is divided hierarchically by periods; at the moment the
8 only convention is that all sliver accounts have type that begins with
11 There are any number of race conditions that may result from the fact
12 that account names are not unique over time. Moreover, it's a bad
13 idea to perform lengthy operations while holding the database lock.
14 In order to deal with both of these problems, we use a worker thread
15 for each account name that ever exists. On 32-bit systems with large
16 numbers of accounts, this may cause the NM process to run out of
17 *virtual* memory! This problem may be remedied by decreasing the
30 # shell path -> account class association
32 # account type -> account class association
35 def register_class(acct_class):
36 """Call once for each account class. This method adds the class to the dictionaries used to look up account classes by shell and type."""
37 shell_acct_class[acct_class.SHELL] = acct_class
38 type_acct_class[acct_class.TYPE] = acct_class
41 # private account name -> worker object association and associated lock
42 name_worker_lock = threading.Lock()
46 """Return the names of all accounts on the system with recognized shells."""
47 return [pw_ent[0] for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
50 """Return the worker object for a particular username. If no such object exists, create it first."""
51 name_worker_lock.acquire()
53 if name not in name_worker: name_worker[name] = Worker(name)
54 return name_worker[name]
55 finally: name_worker_lock.release()
59 def __init__(self, rec):
60 self.name = rec['name']
65 def create(name): abstract
67 def destroy(name): abstract
69 def configure(self, rec):
70 """Write <rec['keys']> to my authorized_keys file."""
71 new_keys = rec['keys']
72 if new_keys != self.keys:
74 dot_ssh = '/home/%s/.ssh' % self.name
75 def do_installation():
76 if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
77 tools.write_file(dot_ssh + '/authorized_keys', lambda f: f.write(new_keys))
78 logger.log('%s: installing ssh keys' % self.name)
79 tools.fork_as(self.name, do_installation)
86 # these semaphores are acquired before creating/destroying an account
87 _create_sem = threading.Semaphore(1)
88 _destroy_sem = threading.Semaphore(1)
90 def __init__(self, name):
91 self.name = name # username
92 self._acct = None # the account object currently associated with this worker
94 # outsiders request operations by putting (fn, args...) tuples on _q
95 # the worker thread (created below) will perform these operations in order
96 self._q = Queue.Queue()
97 tools.as_daemon_thread(self._run)
99 def ensure_created(self, rec):
100 """Cause the account specified by <rec> to exist if it doesn't already."""
101 self._q.put((self._ensure_created, rec.copy()))
103 def _ensure_created(self, rec):
104 curr_class = self._get_class()
105 next_class = type_acct_class[rec['type']]
106 if next_class != curr_class:
107 self._destroy(curr_class)
108 self._create_sem.acquire()
109 try: next_class.create(self.name)
110 finally: self._create_sem.release()
111 if not isinstance(self._acct, next_class): self._acct = next_class(rec)
112 else: self._acct.configure(rec)
113 if next_class != curr_class: self._acct.start()
115 def ensure_destroyed(self): self._q.put((self._ensure_destroyed,))
116 def _ensure_destroyed(self): self._destroy(self._get_class())
118 def start(self): self._q.put((self._start,))
119 def _start(self): self._acct.start()
121 def stop(self): self._q.put((self._stop,))
122 def _stop(self): self._acct.stop()
124 def _destroy(self, curr_class):
127 self._destroy_sem.acquire()
128 try: curr_class.destroy(self.name)
129 finally: self._destroy_sem.release()
131 def _get_class(self):
132 try: shell = pwd.getpwnam(self.name)[6]
133 except KeyError: return None
134 return shell_acct_class[shell]
141 except: logger.log_exc()