1 /* zxiduser.c - Handwritten functions for SP user local account management
2 * Copyright (c) 2012 Synergetics NV (sampo@synergetics.be), All Rights Reserved.
3 * Copyright (c) 2009-2010 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
4 * Copyright (c) 2007-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
5 * Author: Sampo Kellomaki (sampo@iki.fi)
6 * This is confidential unpublished proprietary source code of the author.
7 * NO WARRANTY, not even implied warranties. Contains trade secrets.
8 * Distribution prohibited unless authorized in writing.
9 * Licensed under Apache License 2.0, see file COPYING.
10 * $Id: zxiduser.c,v 1.18 2009-11-29 12:23:06 sampo Exp $
11 *
12 * 12.10.2007, created --Sampo
13 * 7.10.2008, added documentation --Sampo
14 * 14.11.2009, added yubikey (yubico.com) support --Sampo
15 * 23.9.2010, added delegation support --Sampo
16 * 1.9.2012, distilled the authentication backend to an independent module zxpw.c --Sampo
17 */
18
19 #include "platform.h" /* for dirent.h */
20
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <errno.h>
27
28 #ifdef USE_OPENSSL
29 #include <openssl/des.h>
30 #endif
31
32 #include "errmac.h"
33 #include "zxid.h"
34 #include "zxidutil.h"
35 #include "zxidconf.h"
36 #include "c/zx-sa-data.h"
37
38 /*() Parse a line from .mni and form a NameID, unless there is mniptr */
39
40 /* Called by: zxid_check_fed, zxid_get_user_nameid */
zxid_parse_mni(zxid_conf * cf,char * buf,char ** pmniptr)41 zxid_nid* zxid_parse_mni(zxid_conf* cf, char* buf, char** pmniptr)
42 {
43 zxid_nid* nameid;
44 char* p;
45 char* idpent = 0;
46 char* spqual = 0;
47 char* nid = 0;
48 char* mniptr = 0;
49
50 p = strchr(buf, '|');
51 if (p) {
52 *p = 0;
53 idpent = ++p;
54 p = strchr(p, '|');
55 if (p) {
56 *p = 0;
57 spqual = ++p;
58 p = strchr(p, '|');
59 if (p) {
60 *p = 0;
61 nid = ++p;
62 p = strchr(p, '|');
63 if (p) {
64 *p = 0;
65 mniptr = ++p;
66 p = strchr(p, '|');
67 if (p)
68 *p = 0;
69 }
70 }
71 }
72 }
73
74 if (mniptr && *mniptr) {
75 if (pmniptr)
76 *pmniptr = mniptr;
77 return 0;
78 }
79
80 nameid = zx_NEW_sa_NameID(cf->ctx,0);
81 if (spqual && *spqual) nameid->SPNameQualifier = zx_dup_attr(cf->ctx, &nameid->gg, zx_SPNameQualifier_ATTR, spqual);
82 if (idpent && *idpent) nameid->NameQualifier = zx_dup_attr(cf->ctx, &nameid->gg, zx_NameQualifier_ATTR, idpent);
83 if (*buf) nameid->Format = zx_dup_attr(cf->ctx, &nameid->gg, zx_Format_ATTR, buf);
84 if (nid && *nid) zx_add_content(cf->ctx, &nameid->gg, zx_dup_str(cf->ctx, nid));
85 return nameid;
86 }
87
88 /*() Formulate NameID based directory name for the user. qualif is usually
89 * the IdP Entity ID. It is important to separate between same nid
90 * issued by different IdP. The result is "returned" by modifying
91 * sha1_name buffer, which MUST be at least 28 characters long. */
92
93 /* Called by: zxid_get_user_nameid, zxid_put_user, zxid_ses_to_pool x2, zxid_user_change_nameid */
zxid_user_sha1_name(zxid_conf * cf,struct zx_str * qualif,struct zx_str * nid,char * sha1_name)94 void zxid_user_sha1_name(zxid_conf* cf, struct zx_str* qualif, struct zx_str* nid, char* sha1_name)
95 {
96 struct zx_str* ss;
97 if (!nid) {
98 ZERO(sha1_name, 28);
99 return;
100 }
101 if (qualif) {
102 ss = zx_strf(cf->ctx, "%.*s|%.*s", qualif->len, qualif->s, nid->len, nid->s);
103 sha1_safe_base64(sha1_name, ss->len, ss->s);
104 zx_str_free(cf->ctx, ss);
105 } else {
106 sha1_safe_base64(sha1_name, nid->len, nid->s);
107 }
108 sha1_name[27] = 0;
109 }
110
111 /*() Locate user file using a NameID, which may be old or current. If old,
112 * chase the MNIptr fields until current is found. Mainly used to support MNI. */
113
114 /* Called by: zxid_sp_mni_redir, zxid_sp_mni_soap, zxid_sp_slo_redir, zxid_sp_slo_soap */
zxid_get_user_nameid(zxid_conf * cf,zxid_nid * oldnid)115 zxid_nid* zxid_get_user_nameid(zxid_conf* cf, zxid_nid* oldnid)
116 {
117 char sha1_name[28];
118 char* buf;
119 char* mniptr;
120 int iter = 1000;
121 zxid_nid* nameid;
122
123 if (!cf->user_local)
124 return oldnid;
125
126 zxid_user_sha1_name(cf, &oldnid->NameQualifier->g, ZX_GET_CONTENT(oldnid), sha1_name);
127 buf = ZX_ALLOC(cf->ctx, ZXID_MAX_USER);
128 mniptr = sha1_name;
129
130 while (--iter && mniptr && *mniptr) {
131 read_all(ZXID_MAX_USER, buf, (const char*)__FUNCTION__, 1, "%s" ZXID_USER_DIR "%s/.mni", cf->cpath, mniptr);
132 nameid = zxid_parse_mni(cf, buf, &mniptr);
133 if (nameid)
134 return nameid;
135 if (!mniptr || !strcmp(mniptr, sha1_name)) {
136 ERR("Infinite loop in MNI changed NameIDs in user database mniptr(%s) iter(%d)", STRNULLCHK(mniptr), iter);
137 return 0;
138 }
139 }
140 ERR("Too many mniptr indirections for oldnid(%.*s)", ZX_GET_CONTENT_LEN(oldnid), ZX_GET_CONTENT_S(oldnid));
141 return 0;
142 }
143
144 /*() Change a NameID to newnym. Old NameID's user entry is rewritten to have mniptr */
145
146 /* Called by: zxid_mni_do */
zxid_user_change_nameid(zxid_conf * cf,zxid_nid * oldnid,struct zx_str * newnym)147 void zxid_user_change_nameid(zxid_conf* cf, zxid_nid* oldnid, struct zx_str* newnym)
148 {
149 char sha1_name[28];
150 zxid_user_sha1_name(cf, &oldnid->NameQualifier->g, newnym, sha1_name);
151 zxid_put_user(cf, &oldnid->Format->g, &oldnid->NameQualifier->g, &oldnid->SPNameQualifier->g, newnym, 0);
152 zxid_put_user(cf, &oldnid->Format->g, &oldnid->NameQualifier->g, &oldnid->SPNameQualifier->g, ZX_GET_CONTENT(oldnid), sha1_name);
153 }
154
155 /*() Create new user object in file system. Will create user diretory (but not
156 * its subdirectories).
157 * See also zxid_ses_to_pool() */
158
159 /* Called by: zxid_sp_sso_finalize, zxid_user_change_nameid x2, zxid_wsp_validate_env */
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 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)
161 {
162 char sha1_name[28];
163 char dir[ZXID_MAX_BUF];
164 char* buf;
165
166 if (!cf->user_local)
167 return 0;
168
169 if (!idpnid) {
170 ERR("Missing NameID %p", idpent);
171 return 0;
172 }
173
174 zxid_user_sha1_name(cf, idpent, idpnid, sha1_name);
175 name_from_path(dir, sizeof(dir), "%s" ZXID_USER_DIR "%s", cf->cpath, sha1_name);
176 if (MKDIR(dir, 0777) && errno != EEXIST) {
177 ERR("Creating user directory(%s) failed: %d %s; euid=%d egid=%d", dir, errno, STRERROR(errno), geteuid(), getegid());
178 return 0;
179 }
180
181 buf = ZX_ALLOC(cf->ctx, ZXID_MAX_USER);
182 write_all_path_fmt("put_user", ZXID_MAX_USER, buf,
183 "%s" ZXID_USER_DIR "%s/.mni", cf->cpath, 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 ZX_FREE(cf->ctx, buf);
191 D("PUT USER idpnid(%.*s)", idpnid->len, idpnid->s);
192 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 /*() Authenticate by a pairing code.
198 * Pairing code is generated by an external program such as idppairing.pl
199 * after user has logged in. Pairing code can be introduced in another device,
200 * such as mobile phone with limited keyboard, to authenticate the user based
201 * on original authentication to the pairing web site.
202 */
203
zxid_check_mobile_pairing(zxid_conf * cf,zxid_cgi * cgi)204 static int zxid_check_mobile_pairing(zxid_conf* cf, zxid_cgi* cgi)
205 {
206 int len, secs;
207 char* uid;
208 char buf[1024];
209 len = read_all(sizeof(buf), (char*)buf, "pairing", 0, "%s" ZXID_PCODE_DIR "%s", cf->cpath, cgi->pcode);
210 if (len <= 0) {
211 ERR("Bad pairing pcode(%s)", cgi->pcode);
212 return 0;
213 }
214 secs = atoi(buf);
215 if (secs < time(0)) {
216 ERR("Bad pairing pcode(%s) buf(%s) expired=%d, now=%d", cgi->pcode, buf, secs, (int)time(0));
217 return 0;
218 }
219 uid = strchr(buf, ' ');
220 if (!uid) {
221 ERR("Bad pairing pcode(%s) buf(%s) uid not found", cgi->pcode, buf);
222 return 0;
223 }
224 ++uid;
225 D("Pairing OK pcode(%s) buf(%s) expired=%d, now=%d uid=%s", cgi->pcode, buf, secs, (int)time(0), uid);
226 cgi->uid = zx_dup_cstr(cf->ctx, uid);
227 snprintf(buf, sizeof(buf), "%s" ZXID_PCODE_DIR "%s", cf->cpath, cgi->pcode);
228 unlink(buf);
229 return 2; /* *** what is good authentication context class for pairing? Password equivalent? */
230 }
231
232 /*() Locally authenticate user. If successful, create a session.
233 * Expects to get username and password in cgi->au and cgi->ap
234 * respectively. User authetication is done against local database or
235 * by default using /var/zxid/uid/uid/.pw file. When filesystem
236 * backend is used, for safety reasons the uid (user) component can
237 * not have certain characters, such as slash (/) or sequences like "..".
238 * See also: zxpasswd.c
239 *
240 * return:: 0 on failure and sets cgi->err; 1 on success */
241
242 /* Called by: zxid_idp_as_do, zxid_simple_idp_pw_authn, zxid_simple_idp_show_an */
zxid_pw_authn(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses)243 int zxid_pw_authn(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
244 {
245 int an_level;
246 struct zx_str* ss;
247 struct zxid_cstr_list* ac;
248
249 if (cgi->pcode) {
250 an_level = zxid_check_mobile_pairing(cf, cgi);
251 } else {
252 an_level = zx_password_authn(cf->cpath, cgi->uid, cgi->pw, cgi->pin, 0);
253 }
254 if (!an_level) {
255 cgi->err = login_failed;
256 return 0;
257 }
258
259 /* Successful login. Establish session. */
260
261 ZERO(ses, sizeof(zxid_ses));
262 ses->magic = ZXID_SES_MAGIC;
263 ses->an_instant = time(0); /* This will be later used by AuthnStatement constructor. */
264
265 for (ac = cf->issue_authnctx; ac && an_level > 0; ac = ac->n, --an_level) ;
266 if (!ac)
267 ac = cf->issue_authnctx;
268 ses->an_ctx = ac->s;
269
270 /* Master session. Each pairwise SSO has its own to avoid correlation, see zxid_mk_an_stmt() */
271 ss = zxid_mk_id(cf, "MMSES", ZXID_ID_BITS);
272 ses->sesix = ss->s;
273 ZX_FREE(cf->ctx, ss);
274 ses->sid = cgi->sid = ses->sesix;
275 ses->uid = cgi->uid;
276 zxid_put_ses(cf, ses);
277 if (cf->ses_cookie_name && *cf->ses_cookie_name) {
278 ses->setcookie = zx_alloc_sprintf(cf->ctx, 0, "%s=%s; path=/%s%s",
279 cf->ses_cookie_name, ses->sid,
280 cgi->mob?"; Max-Age=15481800":"",
281 ONE_OF_2(cf->burl[4], 's', 'S')?"; secure; HttpOnly":"; HttpOnly");
282 ses->cookie = zx_alloc_sprintf(cf->ctx, 0, "$Version=1; %s=%s",
283 cf->ses_cookie_name, ses->sid);
284 }
285 INFO("LOCAL LOGIN SUCCESSFUL. sid(%s) uid(%s)", cgi->sid, cgi->uid);
286 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "K", "INEWSES", ses->sid, "uid(%s)", ses->uid);
287 if (cf->loguser)
288 zxlogusr(cf, ses->uid, 0,0,0,0,0,0,0, "N", "K", "INEWSES", ses->sid, "uid(%s)", ses->uid);
289 return 1;
290 }
291
292 /* EOF -- zxiduser.c */
293