Branch data Line data Source code
1 : : /* zxiduser.c - Handwritten functions for SP user local account management
2 : : * Copyright (c) 2009-2010 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
3 : : * Copyright (c) 2007-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
4 : : * Author: Sampo Kellomaki (sampo@iki.fi)
5 : : * This is confidential unpublished proprietary source code of the author.
6 : : * NO WARRANTY, not even implied warranties. Contains trade secrets.
7 : : * Distribution prohibited unless authorized in writing.
8 : : * Licensed under Apache License 2.0, see file COPYING.
9 : : * $Id: zxiduser.c,v 1.18 2009-11-29 12:23:06 sampo Exp $
10 : : *
11 : : * 12.10.2007, created --Sampo
12 : : * 7.10.2008, added documentation --Sampo
13 : : * 14.11.2009, added yubikey (yubico.com) support --Sampo
14 : : * 23.9.2010, added delegation support --Sampo
15 : : */
16 : :
17 : : #include "platform.h" /* for dirent.h */
18 : :
19 : : #include <sys/types.h>
20 : : #include <sys/stat.h>
21 : : #include <fcntl.h>
22 : : #include <string.h>
23 : : #include <stdio.h>
24 : : #include <errno.h>
25 : :
26 : : #ifdef USE_OPENSSL
27 : : #include <openssl/des.h>
28 : : #endif
29 : :
30 : : #include "errmac.h"
31 : : #include "zxid.h"
32 : : #include "zxidutil.h"
33 : : #include "zxidconf.h"
34 : : #include "yubikey.h" /* from libyubikey-1.5 */
35 : : #include "c/zx-sa-data.h"
36 : :
37 : : /*() Parse a line from .mni and form a NameID, unless there is mniptr */
38 : :
39 : : /* Called by: zxid_check_fed, zxid_get_user_nameid */
40 : : zxid_nid* zxid_parse_mni(zxid_conf* cf, char* buf, char** pmniptr)
41 : 1226 : {
42 : : zxid_nid* nameid;
43 : : char* p;
44 : 1226 : char* idpent = 0;
45 : 1226 : char* spqual = 0;
46 : 1226 : char* nid = 0;
47 : 1226 : char* mniptr = 0;
48 : :
49 : 1226 : p = strchr(buf, '|');
50 [ + - ]: 1226 : if (p) {
51 : 1226 : *p = 0;
52 : 1226 : idpent = ++p;
53 : 1226 : p = strchr(p, '|');
54 [ + - ]: 1226 : if (p) {
55 : 1226 : *p = 0;
56 : 1226 : spqual = ++p;
57 : 1226 : p = strchr(p, '|');
58 [ + - ]: 1226 : if (p) {
59 : 1226 : *p = 0;
60 : 1226 : nid = ++p;
61 : 1226 : p = strchr(p, '|');
62 [ + + ]: 1226 : if (p) {
63 : 1172 : *p = 0;
64 : 1172 : mniptr = ++p;
65 : 1172 : p = strchr(p, '|');
66 [ - + ]: 1172 : if (p)
67 : 0 : *p = 0;
68 : : }
69 : : }
70 : : }
71 : : }
72 : :
73 [ + + - + ]: 1226 : if (mniptr && *mniptr) {
74 [ # # ]: 0 : if (pmniptr)
75 : 0 : *pmniptr = mniptr;
76 : 0 : return 0;
77 : : }
78 : :
79 : 1226 : nameid = zx_NEW_sa_NameID(cf->ctx,0);
80 [ + - ]: 1226 : if (*buf) nameid->Format = zx_dup_attr(cf->ctx, &nameid->gg, zx_Format_ATTR, buf);
81 [ + - + - ]: 1226 : if (idpent && *idpent) nameid->NameQualifier = zx_dup_attr(cf->ctx, &nameid->gg, zx_NameQualifier_ATTR, idpent);
82 [ + - + - ]: 1226 : if (spqual && *spqual) nameid->SPNameQualifier = zx_dup_attr(cf->ctx, &nameid->gg, zx_SPNameQualifier_ATTR, spqual);
83 [ + - + - ]: 1226 : if (nid && *nid) zx_add_content(cf->ctx, &nameid->gg, zx_dup_str(cf->ctx, nid));
84 : 1226 : return nameid;
85 : : }
86 : :
87 : : /*() Formulate NameID based directory name for the user. qualif is usually
88 : : * the IdP Entity ID. It is important to separate between same nid
89 : : * issued by different IdP. The result is "returned" by modifying
90 : : * sha1_name buffer, which MUST be at least 28 characters long. */
91 : :
92 : : /* Called by: zxid_get_user_nameid, zxid_put_user, zxid_ses_to_pool x2, zxid_user_change_nameid */
93 : : void zxid_user_sha1_name(zxid_conf* cf, struct zx_str* qualif, struct zx_str* nid, char* sha1_name)
94 : 168 : {
95 : : struct zx_str* ss;
96 [ - + ]: 168 : if (!nid) {
97 : 0 : ZERO(sha1_name, 28);
98 : 0 : return;
99 : : }
100 [ + + ]: 168 : if (qualif) {
101 : 161 : ss = zx_strf(cf->ctx, "%.*s|%.*s", qualif->len, qualif->s, nid->len, nid->s);
102 : 161 : sha1_safe_base64(sha1_name, ss->len, ss->s);
103 : 161 : zx_str_free(cf->ctx, ss);
104 : : } else {
105 : 7 : sha1_safe_base64(sha1_name, nid->len, nid->s);
106 : : }
107 : 168 : sha1_name[27] = 0;
108 : : }
109 : :
110 : : /*() Locate user file using a NameID, which may be old or current. If old,
111 : : * chase the MNIptr fields until current is found. Mainly used to support MNI. */
112 : :
113 : : /* Called by: zxid_sp_mni_redir, zxid_sp_mni_soap, zxid_sp_slo_redir, zxid_sp_slo_soap */
114 : : zxid_nid* zxid_get_user_nameid(zxid_conf* cf, zxid_nid* oldnid)
115 : 2 : {
116 : : char sha1_name[28];
117 : : char* buf;
118 : : char* mniptr;
119 : 2 : int iter = 1000;
120 : : zxid_nid* nameid;
121 : :
122 [ - + ]: 2 : if (!cf->user_local)
123 : 0 : return oldnid;
124 : :
125 [ + - + - : 2 : zxid_user_sha1_name(cf, &oldnid->NameQualifier->g, ZX_GET_CONTENT(oldnid), sha1_name);
+ - ]
126 : 2 : buf = ZX_ALLOC(cf->ctx, ZXID_MAX_USER);
127 : 2 : mniptr = sha1_name;
128 : :
129 [ + - + - : 4 : while (--iter && mniptr && *mniptr) {
+ - ]
130 : 2 : read_all(ZXID_MAX_USER, buf, (const char*)__FUNCTION__, 1, "%s" ZXID_USER_DIR "%s/.mni", cf->path, mniptr);
131 : 2 : nameid = zxid_parse_mni(cf, buf, &mniptr);
132 [ + - ]: 2 : if (nameid)
133 : 2 : return nameid;
134 [ # # # # ]: 0 : if (!mniptr || !strcmp(mniptr, sha1_name)) {
135 [ # # ]: 0 : ERR("Infinite loop in MNI changed NameIDs in user database mniptr(%s) iter(%d)", STRNULLCHK(mniptr), iter);
136 : 0 : return 0;
137 : : }
138 : : }
139 [ # # # # : 0 : ERR("Too many mniptr indirections for oldnid(%.*s)", ZX_GET_CONTENT_LEN(oldnid), ZX_GET_CONTENT_S(oldnid));
# # # # #
# # # ]
140 : 0 : return 0;
141 : : }
142 : :
143 : : /*() Change a NameID to newnym. Old NameID's user entry is rewritten to have mniptr */
144 : :
145 : : /* Called by: zxid_mni_do */
146 : : void zxid_user_change_nameid(zxid_conf* cf, zxid_nid* oldnid, struct zx_str* newnym)
147 : 1 : {
148 : : char sha1_name[28];
149 : 1 : zxid_user_sha1_name(cf, &oldnid->NameQualifier->g, newnym, sha1_name);
150 : 1 : zxid_put_user(cf, &oldnid->Format->g, &oldnid->NameQualifier->g, &oldnid->SPNameQualifier->g, newnym, 0);
151 [ + - + - : 1 : zxid_put_user(cf, &oldnid->Format->g, &oldnid->NameQualifier->g, &oldnid->SPNameQualifier->g, ZX_GET_CONTENT(oldnid), sha1_name);
+ - ]
152 : 1 : }
153 : :
154 : : /*() Create new user object in file system. Will create user diretory (but not
155 : : * its subdirectories).
156 : : * See also zxid_ses_to_pool() */
157 : :
158 : : /* Called by: zxid_sp_sso_finalize, zxid_user_change_nameid x2, zxid_wsp_validate_env */
159 : : int zxid_put_user(zxid_conf* cf, struct zx_str* nidfmt, struct zx_str* idpent, struct zx_str* spqual, struct zx_str* idpnid, char* mniptr)
160 : 43 : {
161 : : char sha1_name[28];
162 : : char dir[ZXID_MAX_BUF];
163 : : char* buf;
164 : :
165 [ - + ]: 43 : if (!cf->user_local)
166 : 0 : return 0;
167 : :
168 [ - + ]: 43 : if (!idpnid) {
169 : 0 : ERR("Missing NameID %p", idpent);
170 : 0 : return 0;
171 : : }
172 : :
173 : 43 : zxid_user_sha1_name(cf, idpent, idpnid, sha1_name);
174 : 43 : name_from_path(dir, sizeof(dir), "%s" ZXID_USER_DIR "%s", cf->path, sha1_name);
175 [ + + - + ]: 43 : if (MKDIR(dir, 0777) && errno != EEXIST) {
176 : 0 : perror("mkdir for user");
177 : 0 : ERR("Creating user directory(%s) failed", dir);
178 : 0 : return 0;
179 : : }
180 : :
181 : 43 : buf = ZX_ALLOC(cf->ctx, ZXID_MAX_USER);
182 [ + + + + : 43 : write_all_path_fmt("put_user", ZXID_MAX_USER, buf,
+ + + + +
+ + - +
- ]
183 : : "%s" ZXID_USER_DIR "%s/.mni", cf->path, sha1_name,
184 : : "%.*s|%.*s|%.*s|%.*s|%s",
185 : : nidfmt?nidfmt->len:0, nidfmt?nidfmt->s:"",
186 : : idpent?idpent->len:0, idpent?idpent->s:"",
187 : : spqual?spqual->len:0, spqual?spqual->s:"",
188 : : idpnid->len, idpnid->s,
189 : : STRNULLCHK(mniptr));
190 : 43 : ZX_FREE(cf->ctx, buf);
191 [ + + - + ]: 43 : D("PUT USER idpnid(%.*s)", idpnid->len, idpnid->s);
192 : 43 : return 1;
193 : : }
194 : :
195 : : static char* login_failed = "Login failed. Check username and password. Make sure you have an active local account. Or just try some other authentication method or another IdP.<p>";
196 : :
197 : : /*() Locally authenticate user. If successful, create a session.
198 : : * Expects to get username and password in cgi->au and cgi->ap
199 : : * respectively. User authetication is done against local database or
200 : : * by default using /var/zxid/uid/uid/.pw file. When filesystem
201 : : * backend is used, for safety reasons the uid (user) component can
202 : : * not have certain characters, such as slash (/) or sequences like "..".
203 : : * See also: zxpasswd.c
204 : : *
205 : : * return:: 0 on failure and sets cgi->err; 1 on success */
206 : :
207 : : /* Called by: zxid_idp_as_do, zxid_simple_idp_pw_authn, zxid_simple_idp_show_an */
208 : : int zxid_pw_authn(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
209 : 20 : {
210 : 20 : const char* meth = "??";
211 : : struct zx_str* ss;
212 : : unsigned char buf[ZXID_MAX_BUF];
213 : : unsigned char pw_buf[256];
214 : : unsigned char pw_hash[120];
215 : : yubikey_token_st yktok;
216 : : int len;
217 : :
218 [ + - - + ]: 20 : if (!cgi->uid || !cgi->uid[0]) {
219 : 0 : ERR("No uid (user's login name) supplied. %d", 0);
220 : 0 : cgi->err = login_failed;
221 [ # # # # : 0 : D("no user name pw(%s)", STRNULLCHK(cgi->pw));
# # ]
222 : 0 : return 0;
223 : : }
224 : :
225 : : /* Check for filesystem unsafe characters. (*** Is this list complete?) */
226 [ + - + - : 20 : if (strstr(cgi->uid, "..") || strchr(cgi->uid, '/')
+ - - + ]
227 : : || strchr(cgi->uid, '\\') || strchr(cgi->uid, '~')) {
228 : 0 : ERR("uid(%s) is not filesystem safe", cgi->uid);
229 [ # # # # : 0 : D("pw(%s)", STRNULLCHK(cgi->pw));
# # ]
230 : 0 : cgi->err = login_failed;
231 : 0 : return 0;
232 : : }
233 : :
234 : 20 : len = strlen(cgi->uid);
235 [ - + ]: 20 : if (len > 32) { /* Yubikey */
236 : 0 : meth = "yk";
237 : 0 : strcpy((char*)pw_hash, cgi->uid + len - 32);
238 : 0 : cgi->uid[len - 32] = 0;
239 [ # # # # ]: 0 : D("yubikey user(%s) ticket(%s)", cgi->uid, pw_hash);
240 : :
241 : 0 : snprintf((char*)buf, sizeof(buf)-1, "%suid/%s", cf->path, cgi->uid);
242 : 0 : buf[sizeof(buf)-1] = 0;
243 : 0 : len = read_all(sizeof(pw_buf), (char*)pw_buf, "ykspent", 1, "%s/.ykspent/%s", buf, pw_hash);
244 [ # # ]: 0 : if (len) {
245 : 0 : ERR("The One Time Password has already been spent. ticket(%s%s) buf(%.*s)", cgi->uid, pw_hash, len, pw_buf);
246 : 0 : cgi->err = login_failed;
247 : 0 : return 0;
248 : : }
249 [ # # ]: 0 : if (!write_all_path_fmt("ykspent", sizeof(pw_buf), (char*)pw_buf, "%s/.ykspent/%s", (char*)buf, (char*)pw_hash, "1"))
250 : 0 : return 0;
251 : :
252 : 0 : len = read_all(sizeof(pw_buf), (char*)pw_buf, "ykaes", 1, "%s/.yk", buf);
253 [ # # # # ]: 0 : D("buf (%s) got=%d", pw_buf, len);
254 [ # # ]: 0 : if (len < 32) {
255 : 0 : ERR("User's %s/.yk file must contain aes128 key as 32 hexadecimal characters. Too few characters %d ticket(%s)", cgi->uid, len, pw_hash);
256 : 0 : cgi->err = login_failed;
257 : 0 : return 0;
258 : : }
259 [ # # ]: 0 : if (len > 32) {
260 : 0 : INFO("User's %s/.yk file must contain aes128 key as 32 hexadecimal characters. Too many characters %d ticket(%s). Truncating.", cgi->uid, len, pw_hash);
261 : 0 : len = 32;
262 : 0 : pw_buf[len] = 0;
263 : : }
264 : 0 : zx_hexdec((char*)pw_buf, (char*)pw_buf, len, hex_trans);
265 : 0 : ZERO (&yktok, sizeof(yktok));
266 : 0 : zx_hexdec((void*)&yktok, (char*)pw_hash, 32, ykmodhex_trans);
267 : 0 : yubikey_aes_decrypt((void*)&yktok, pw_buf);
268 [ # # # # ]: 0 : D("internal uid %02x %02x %02x %02x %02x %02x counter=%d 0x%x timestamp=%d (hi=%x lo=%x) use=%d 0x%x rnd=0x%x crc=0x%x", yktok.uid[0], yktok.uid[1], yktok.uid[2], yktok.uid[3], yktok.uid[4], yktok.uid[5], yktok.ctr, yktok.ctr, (yktok.tstph << 16) | yktok.tstpl, yktok.tstph, yktok.tstpl, yktok.use, yktok.use, yktok.rnd, yktok.crc);
269 : :
270 [ # # ]: 0 : if (!yubikey_crc_ok_p((unsigned char*)&yktok)) {
271 [ # # # # ]: 0 : D("yubikey ticket validation failure %d", 0);
272 : 0 : cgi->err = login_failed;
273 : 0 : return 0;
274 : : }
275 : : } else {
276 [ + - - + ]: 20 : if (!cgi->pw || !cgi->pw[0]) {
277 : 0 : ERR("No password supplied. uid(%s)", cgi->uid);
278 : 0 : cgi->err = login_failed;
279 : 0 : return 0;
280 : : }
281 : :
282 : : /* *** Add here support for other authentication backends */
283 : :
284 : 20 : meth = "pw";
285 : :
286 : 20 : len = read_all(sizeof(pw_buf), (char*)pw_buf, "pw_authn", 1,
287 : : "%s" ZXID_UID_DIR "%s/.pw", cf->path, cgi->uid);
288 [ - + ]: 20 : if (len < 1) {
289 : 0 : ERR("No account found for uid(%s) or account does not have .pw file.", cgi->uid);
290 [ # # # # ]: 0 : D("pw(%s)", cgi->pw);
291 : 0 : cgi->err = login_failed;
292 : 0 : return 0;
293 : : }
294 : :
295 [ + - ]: 20 : if (len) {
296 [ - + ]: 20 : if (pw_buf[len-1] == '\012') --len;
297 [ - + ]: 20 : if (pw_buf[len-1] == '\015') --len;
298 : : }
299 : 20 : pw_buf[len] = 0;
300 [ + - - + ]: 20 : D("pw_buf (%s) len=%d", pw_buf, len);
301 : :
302 [ + + ]: 20 : if (!memcmp(pw_buf, "$1$", sizeof("$1$")-1)) {
303 : 9 : zx_md5_crypt(cgi->pw, (char*)pw_buf, (char*)pw_hash);
304 [ + - - + ]: 9 : D("pw_hash(%s)", pw_hash);
305 [ - + ]: 9 : if (strcmp((char*)pw_buf, (char*)pw_hash)) {
306 : 0 : ERR("Bad password. uid(%s)", cgi->uid);
307 [ # # # # ]: 0 : D("md5 pw(%s) .pw(%s) pw_hash(%s)", cgi->pw, pw_buf, pw_hash);
308 : 0 : cgi->err = login_failed;
309 : 0 : return 0;
310 : : }
311 : : #ifdef USE_OPENSSL
312 [ - + ]: 11 : } else if (!memcmp(pw_buf, "$c$", sizeof("$c$")-1)) {
313 : 0 : DES_fcrypt(cgi->pw, (char*)pw_buf+3, (char*)pw_hash);
314 [ # # # # ]: 0 : D("pw_hash(%s)", pw_hash);
315 [ # # ]: 0 : if (strcmp((char*)buf+3, (char*)pw_hash)) {
316 : 0 : ERR("Bad password for uid(%s)", cgi->uid);
317 [ # # # # ]: 0 : D("crypt pw(%s) .pw(%s) pw_hash(%s)", cgi->pw, pw_buf, pw_hash);
318 : 0 : cgi->err = login_failed;
319 : 0 : return 0;
320 : : }
321 : : #endif
322 [ + - - + ]: 11 : } else if (ONE_OF_2(pw_buf[0], '$', '_')) {
323 : 0 : ERR("Unsupported password hash. uid(%s)", cgi->uid);
324 [ # # # # ]: 0 : D("pw(%s) .pw(%s)", cgi->pw, pw_buf);
325 : 0 : cgi->err = login_failed;
326 : 0 : return 0;
327 : : } else {
328 [ + + ]: 11 : if (strcmp((char*)pw_buf, cgi->pw)) {
329 : 2 : ERR("Bad password. uid(%s)", cgi->uid);
330 [ + - - + ]: 2 : D("pw(%s) .pw(%s)", cgi->pw, pw_buf);
331 : 2 : cgi->err = login_failed;
332 : 2 : return 0;
333 : : }
334 : : }
335 : : }
336 : :
337 : : /* Successful login. Establish session. */
338 : :
339 : 18 : ZERO(ses, sizeof(zxid_ses));
340 : 18 : ses->magic = ZXID_SES_MAGIC;
341 : 18 : ses->an_instant = time(0); /* This will be later used by AuthnStatement constructor. */
342 : 18 : ses->an_ctx = cf->issue_authnctx_pw; /* *** Should also depend on how user was registered */
343 : : /* Master session. Each pairwise SSO has its own to avoid correlation, see zxid_mk_an_stmt() */
344 : 18 : ss = zxid_mk_id(cf, "MMSES", ZXID_ID_BITS);
345 : 18 : ses->sesix = ss->s;
346 : 18 : ZX_FREE(cf->ctx, ss);
347 : 18 : ses->sid = cgi->sid = ses->sesix;
348 : 18 : ses->uid = cgi->uid;
349 : 18 : zxid_put_ses(cf, ses);
350 [ + - + - ]: 18 : if (cf->ses_cookie_name && *cf->ses_cookie_name) {
351 [ + - - + ]: 18 : ses->setcookie = zx_alloc_sprintf(cf->ctx, 0, "%s=%s; path=/%s",
352 : : cf->ses_cookie_name, ses->sid,
353 : : ONE_OF_2(cf->url[4], 's', 'S')?"; secure":"");
354 : 18 : ses->cookie = zx_alloc_sprintf(cf->ctx, 0, "$Version=1; %s=%s",
355 : : cf->ses_cookie_name, ses->sid);
356 : : }
357 : 18 : INFO("LOCAL LOGIN SUCCESSFUL. sid(%s) uid(%s) %s", cgi->sid, cgi->uid, meth);
358 : 18 : zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "K", "INEWSES", ses->sid, "uid(%s) %s", ses->uid, meth);
359 [ + - ]: 18 : if (cf->loguser)
360 : 18 : zxlogusr(cf, ses->uid, 0, 0, 0, 0, 0, 0, 0, "N", "K", "INEWSES", ses->sid, "uid(%s) %s", ses->uid, meth);
361 : 18 : return 1;
362 : : }
363 : :
364 : : /* EOF -- zxiduser.c */
|