forked from netsoc/nd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaccountrequests.py
262 lines (211 loc) · 7.13 KB
/
accountrequests.py
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
import gdbm, socket, time, hashlib, os, sys, pwd
run_as_server = (__name__ == '__main__')
if run_as_server:
sys.path += ["/usr/local/nd"]
import sendmail
########### Networking
server_socket = None
def accept_network_conn():
global server_socket
c, addr = server_socket.accept()
c.settimeout(1)
return c, addr
if run_as_server:
server_socket = socket.socket()
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("0.0.0.0", 900))
server_socket.listen(5)
########### LDAP connection
import nd
def check_ldap():
# If we lose the connection to LDAP, we're going to have to restart
# since we dropped privileges and can't rebind as root
# If running as a server, the server will be restarted
def fail():
if run_as_server:
print "Not bound to LDAP as root, shutting down server"
sys.exit()
else:
raise Exception("Not bound to LDAP as root")
try:
if nd.whoami() != 'dn:cn=root,dc=tophat,dc=ie':
# ldap is bound as the wrong user
fail()
except Exception, e:
# ldap is down
fail()
check_ldap()
if run_as_server:
# drop privileges now that we have the socket and the ldap connection
os.setuid(pwd.getpwnam("daemon")[2])
check_ldap()
########### Request marshalling/unmarshalling (both network & storage)
def parse_request(s):
xs = s.split("\0")
code = xs[0]
del xs[0]
if len(xs) % 2 != 0:
raise Exception("badly formatted request")
args = {}
for i in range(0, len(xs), 2):
args[xs[i]] = xs[i+1]
return code, args
def unparse_request(code, args):
def mkstr(x):
if type(x) == unicode:
return x.encode("utf8")
else:
return str(x)
l = [(str(k), mkstr(v)) for k, v in args.items()]
l.sort()
# flatten
l = [x for pair in l for x in pair]
return code + "\0" + "\0".join(l)
########### Request database access
request_db_filename = os.path.abspath(os.path.dirname(__file__)) + "/reqs.db"
def db_lookup_code(code):
db = gdbm.open(request_db_filename, "r")
try:
p = db[code]
except KeyError:
p = None
db.close()
if p is None:
raise Exception("invalid code")
else:
return parse_request(p)[1]
def db_write(code, args):
db = gdbm.open(request_db_filename, "cs", 0600)
db[code] = unparse_request(code, args)
db.close()
def create_key(op, **args):
if type(op) != str:
op = op.__name__
assert op in operations
args['operation'] = op
code = "".join(["%02x" % ord(x) for x in open("/dev/urandom").read(16)])
db_write(code, args)
return code
def all_codes():
db = gdbm.open(request_db_filename, "r")
k = db.firstkey()
l = []
while k != None:
l.append(parse_request(db[k]))
k = db.nextkey(k)
db.close()
return l
def dump_codes():
l = all_codes()
usedcodes = [x for x in l if '*used' in x[1]]
unusedcodes = [x for x in l if '*used' not in x[1]]
def fmtreq((code, args)):
return "%s: %s" % (code, ", ".join("%s=%r" % x for x in args.items()))
print "Used codes:"
for i in usedcodes:
print fmtreq(i)
print
print "Unused codes:"
for i in unusedcodes:
print fmtreq(i)
########### Hashcodes for frontend
def create_mac(data):
hash = lambda x: hashlib.md5(x).hexdigest()
return hash("Although your world wonders me, " + hash("olololololololol" + data)) + "/" + data
def verify_mac(mac):
if "/" not in mac: return None
h, _, data = mac.partition("/")
if create_mac(data) != mac:
return None
return data
########### Making privileged URLs to send out to users
def make_signup_url(user):
assert type(user) == nd.User
if user.get_state() != "newmember":
raise Exception("%r is not a newly-created account, won't make signup URL" % user)
print "Generating single-use code for userid %d to change their account state" % user.uidNumber
k = create_key(setup_account, uidnumber = user.uidNumber)
u = create_mac(str(user.uidNumber))
return "https://signup.netsoc.tcd.ie/signup.php?code=%s&userid=%s" % (k,u)
########### Authorization logic (checking request received over network against stored parameters)
def check(condition, message):
'''asserts with a message to be passed back to the user'''
if not condition:
raise Exception(message)
operations = {}
def run_request(code, args):
props = db_lookup_code(code)
check("*used" not in props, "This code has already been used")
print props, args
for p in props:
if p in args:
check(args[p] == props[p], "Code not authorized for %s = %r" % (p, args[p]))
else:
args[p] = props[p]
if "*expiry" in props:
pass # FIXME
if "operation" not in args or args["operation"] not in operations:
raise Exception("Unknown operation")
opname = args["operation"]
logmsg = args.get("log")
op = operations[opname]
del args["operation"]
if "log" in args:
del args["log"]
check_ldap()
op(**args)
args["operation"] = opname
if logmsg:
args["log"] = logmsg
args["*used"] = time.asctime()
db_write(code, args)
def define_operation(fn):
operations[fn.__name__] = fn
return fn
########### Actual operations that can be performed
@define_operation
def change_password(username, password, **kw):
print "pw of %s -> %s" % (username, password)
@define_operation
def setup_account(uidnumber, username, name, issusername, password):
userlist = nd.User.search(uidNumber = uidnumber)
check(len(userlist) == 1, "Could not find matching account")
user = userlist[0]
check(user.get_state() == "newmember", "User account already set up. To renew an account instead, contact [email protected].")
check(nd.User.username_is_valid(username), "Invalid username")
check(len(name) > 2 and " " in name, "Please enter your full name")
check(len(issusername) > 2, "Please enter your College username")
check(len(password) >= 6, "Please enter at least an 6-character password")
user.uid = username
user.cn = name
user.tcdnetsoc_ISS_username = issusername
user.set_state("shell", password)
try:
sendmail.sendmail("account_setup", to=user.mail)
except Exception, e:
# if mail-sending fails we don't care that much
print e
########### Main loop
def run_server():
if not run_as_server:
raise Exception("Not running as a service")
print "server starting"
while 1:
addr = None
args = {}
try:
c, addr = accept_network_conn()
s = c.recv(4096)
code, args = parse_request(s)
ret = run_request(code, args)
c.send("Success")
c.close()
except Exception,e:
print str(e), " from ", str(addr), ", msg:", repr((code,args))
try:
c.sendall(str(e))
c.close()
except Exception,e:
print "error sending error code " + str(e)
if run_as_server:
run_server()