Odyssey
hcuAuthShared.i
1 <?php
2 /**
3  * File: hcuAuthShared.i
4  * Purpose: These functions can be used from Online Banking, Monitor, or Admin Backoffice
5  *
6  * Back-ported from Odyssey to Mammoth 4/12/18
7  *
8  * Trying to centralize some functions that were in hcuAppFeed.prg on Odyssey
9  * for shared use by Mammoth scripts OFXRequest, hcuPartner, and MoneyDesk
10  * and to make it easier for the eventual migration to Odyssey
11  *
12  */
13 
14 /**
15  * Get the user rec from cuusers. Get MFA info from cuquestselect & format
16  * as for Odyssey. This is a Mammoth replacement for Odyssey GetUserByName.
17  * Functionality is marginally different due to db schema differences. When
18  * porting to Odyssey use GetUserByName () instead.
19  *
20  *
21  * @param resource $p_dbh database handle
22  * @param string $cu hcu client code
23  * @param string $username banking member id
24  * @return array member data including record count indicator zero if none
25  */
26 function GetUserRec($p_dbh, $cu, $username) {
27  if (preg_match("/\D/", $username)) {
28  # username contains non-digits
29  $qby = 'user_alias ilike ';
30  } else {
31  $qby = 'user_name = ';
32  }
33 
34  $return_ary = array();
35  // ** QUERY THE USER INFORMATION
36  $sqluser = "SELECT trim(cuuser.user_name) as user_name, trim(cuuser.passwd) as passwd,
37  forcechange as fchange, coalesce(forceremain,0) as fremain, failedremain, pwchange as pchange,
38  trim(email) as email, confidence,
39  lastlogin as llog, failedlogin as flog, coalesce(msg_tx,0) as msg_tx, userflags & " . GetUserFlagsValue('MEM_FORCE_RESET') . "::int4 as freset,
40  userflags, coalesce(challenge_quest_id,0) as savecqid,
41  coalesce(cuadmin.flagset,0) as flagset, coalesce(cuadmin.flagset2,0) as flagset2, coalesce(cuadmin.flagset3,0) as flagset3,
42  cuadmin.livewait, trim(cuadmin.lastupdate) as lastupdate, cuadmin.min_chlng_qst as min_chlng_qst,
43  cuadmin.pname, coalesce(histdays,0) as fhdays, coalesce(gracelimit,0) as grace, trmemomaxlen, trim(cuadmin.cu) as cu
44  FROM cuusers as cuuser
45  JOIN cuadmin on cuadmin.cu = '" . prep_save($cu) . "'
46  WHERE cuuser.cu='$cu' and cuuser.$qby '" . prep_save(strtolower($username)) . "' ";
47 
48  $mbr_sth = db_query($sqluser, $p_dbh);
49  if (db_num_rows($mbr_sth) == 1) {
50  $return_ary = db_fetch_assoc($mbr_sth,0);
51  $return_ary['rowfound'] = 1;
52 
53  # set these with some other query instead...
54  $return_ary['mfaquest'] = GetMammothMFA($p_dbh, $cu, $return_ary['user_name'], $return_ary['savecqid'], $return_ary['pchange']);
55  $mbrMfaQuest = HCU_MFADecode(HCU_JsonDecode($return_ary['mfaquest']));
56  $return_ary['chcount'] = $mbrMfaQuest['mfacount'];
57  $return_ary['mfadate'] = $mbrMfaQuest['mfadate'];
58  $FORCEUPDATE = 0;
59  if ($return_ary['fchange'] == 'Y') {
60  $FORCEUPDATE += 1; #password
61  }
62 
63  if (($return_ary['msg_tx'] & GetMsgTxValue('MSGTX_FORCE_EM')) || $return_ary['email'] == '') {
64  $FORCEUPDATE += 2; # email
65  }
66  if (intval($return_ary['flagset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')) == 0 &&
67  ( $return_ary['freset'] == GetUserFlagsValue('MEM_FORCE_RESET') || $return_ary['chcount'] < $return_ary['min_chlng_qst'])
68  ) {
69  $FORCEUPDATE += 4; #challenge questions
70  }
71  if (($return_ary['flagset2'] & GetFlagsetValue('CU2_ALIAS_REQ')) && !Check_Member_UseAlias($return_ary['user_name'])) {
72  $FORCEUPDATE += 8;
73  }
74 
75 // if ( intval($return_ary['flagset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')) > 0 && $return_ary['freset'] == 2 ) {
76 // $FORCEUPDATE += 16; #phone numbers
77 // }
78  $return_ary['forceupdate'] = $FORCEUPDATE;
79 
80 
81  if ($return_ary['failedremain'] <= 0 || ( ($return_ary['forceupdate'] & 29) > 0 && $return_ary['fremain'] <= 0 )) {
82 
83  $return_ary['lockedacct'] = 1;
84  } else {
85  $return_ary['lockedacct'] = 0;
86  }
87 
88  } else {
89  $return_ary['rowfound'] = 0;
90  $return_ary['sql'] = $sqluser;
91  }
92 
93 
94 return $return_ary;
95 
96 }
97 function MakeUserkey($CU, $MEMBER) {
98  global $privkey;
99  $appexpires = time() + 900; # 15 minutes
100  $apphash = MD5($privkey . MD5(join(':', array($privkey, $appexpires, $CU, $MEMBER, $MEMBER))));
101 
102  $apptoken = urlencode("H=$apphash&E=$appexpires&A=$MEMBER");
103  return $apptoken;
104 }
105 /**
106  * MakeV94key obsolete, replaced by MakeV94Dkey which uses device cookie format
107  *
108  * @param string $CU HomeCU client code
109  * @param string $MEMBER HomeCU member number
110  * @param integer $TTL time-to-live of hash in seconds 8121600 = 94days
111  * @param string $KEY hash key to use
112  * @param string $HMETHOD hash method to use S=sha384 M=md5
113  * @param integer $PWCHANGE timestamp of last password change
114  * @return string new USERKEY
115  */
116 function MakeV94key($CU, $MEMBER, $TTL, $KEY, $HMETHOD, $PWCHANGE=0) {
117  $appexpires = time() + $TTL; # 94 days
118  if ($HMETHOD == 'S') {
119  $apphash = hash_hmac('sha384',MD5(join(':', array($KEY, $appexpires, $CU, $MEMBER, $PWCHANGE))),$KEY);
120  $apptoken = urlencode("H=$apphash&E=$appexpires&A=$MEMBER&P=$PWCHANGE");
121  } else {
122  $apphash = MD5($KEY . MD5(join(':', array($KEY, $appexpires, $CU, $MEMBER, $MEMBER))));
123  $apptoken = urlencode("H=$apphash&E=$appexpires&A=$MEMBER&C=$MEMBER");
124  }
125  return $apptoken;
126 }
127 /**
128  * MakeV94Dkey create userkey using device cookie content as the P= value
129  *
130  * @param string $CU HomeCU client code
131  * @param string $MEMBER HomeCU member number
132  * @param array $userrec HomeCU member info
133  * @param integer $TTL time-to-live of hash in seconds 8121600 = 94days
134  * @param string $KEY hash key to use
135  * @param string $HMETHOD hash method to use S=sha384 M=md5
136  * @param integer $mfaMode 0 = questions, 1 = access code
137  * calculated as $mfaMode = (intval($HB_ENV['Fset3'] & $GLOBALS['CU3_MFA_AUTHCODE']));
138  * @return string new USERKEY
139  */
140 
141 function MakeV94Dkey($CU, $MEMBER, $userrec, $TTL, $KEY, $HMETHOD) {
142  $appexpires = time() + $TTL; # 94 days
143 
144  if ($HMETHOD == 'S') {
145  # make device cookie string, use it as apptoken 'P' value
146  $mfaMode = (intval($userrec['flagset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')));
147  $mfaDate = (integer) HCU_array_key_value("mfadate", $userrec);
148  $passwd = HCU_array_key_value("passwd", $userrec);
149  $email = HCU_array_key_value("email", $userrec);
150  $confidence = HCU_array_key_value("confidence", $userrec);
151 
152  $deviceToken = hash_hmac('sha384',GetDeviceCookieContentString(),trim($passwd) . trim(strtolower($email)) . trim(strtolower($confidence)) . $mfaMode . $mfaDate);
153  $apphash = hash_hmac('sha384',MD5(join(':', array($KEY, $appexpires, $CU, $MEMBER, $deviceToken))),$KEY);
154  $apptoken = urlencode("H=$apphash&E=$appexpires&A=$MEMBER&P=$deviceToken");
155  } else {
156  $apphash = MD5($KEY . MD5(join(':', array($KEY, $appexpires, $CU, $MEMBER, $MEMBER))));
157  $apptoken = urlencode("H=$apphash&E=$appexpires&A=$MEMBER&C=$MEMBER");
158  }
159  return $apptoken;
160 }
161 
162 function CheckUserkey($CU, $USERKEY, $apptokenkey) {
163 
164  try {
165  $result = array('Status' => array('Code' =>0, 'Message' => 'Success'));
166 
167  $apptokarr = array();
168  parse_str(urldecode($USERKEY), $apptokarr);
169 
170  if ($apptokarr['E'] < time()) {
171  throw new Exception("Invalid Credentials (Expired Token) " . __LINE__,15510);
172  }
173 
174  if (is_null($apptokarr['E']) || is_null($apptokarr['A']) || is_null($apptokarr['H'])) {
175  throw new Exception("Invalid Credentials (Partial Token) " . __LINE__,15510);
176  }
177  # if no C= value, assume oldstyle userkey
178  # and set member as A= value so hash works
179  # for odyssey A & C are swapped
180  # A is Uid, C is primary account
181 
182  if (is_null($apptokarr['C'])) {
183  $CAUTH = $apptokarr['A'];
184  } else {
185  $CAUTH = $apptokarr['C'];
186  }
187  $MEMBER = $apptokarr['A'];
188  $EXPIRES = $apptokarr['E'];
189  $hash = MD5($apptokenkey .
190  MD5(join(':', array($apptokenkey, $EXPIRES, $CU, $MEMBER, $CAUTH))));
191  if ($apptokarr['H'] != $hash) {
192  throw new Exception("Invalid Credentials (Corrupted Token) " . json_encode($apptokarr) . __LINE__,15510);
193  }
194  } catch (Exception $e) {
195  $result = array('Status' => array('Code' => $e->getCode(), 'Message' => 'Failed ' . $e->getMessage()));
196  }
197  return $result;
198 } // end CheckUserkey
199 
200 function CheckV94key($CU, $USERKEY, $KEY, $HMETHOD) {
201 
202  try {
203  $result = array('Status' => array('Code' => 0, 'Message' => 'Success'));
204 
205  $apptokarr = array();
206  parse_str(urldecode($USERKEY), $apptokarr);
207 
208  if (!HCU_array_key_value('E',$apptokarr) || !HCU_array_key_value('A',$apptokarr) || !HCU_array_key_value('H',$apptokarr) ) {
209  throw new Exception("Invalid Credentials (Partial Token) ", 15510);
210  }
211 
212  if ($apptokarr['E'] < time()) {
213  throw new Exception("Invalid Credentials (Expired Token) ", 15510);
214  }
215 
216  $MEMBER = $apptokarr['A'];
217  $EXPIRES = $apptokarr['E'];
218 
219  if (HCU_array_key_exists('P',$apptokarr) ) {
220  $PWCHANGE = HCU_array_key_value('P',$apptokarr);
221  } else {
222  $PWCHANGE = 0;
223  }
224  if ($HMETHOD == 'S') {
225  $hash = hash_hmac('sha384', MD5(join(':', array($KEY, $EXPIRES, $CU, $MEMBER, $PWCHANGE))), $KEY);
226  } else {
227  $hash = MD5($KEY . MD5(join(':', array($KEY, $EXPIRES, $CU, $MEMBER, $MEMBER))));
228  }
229  if ($apptokarr['H'] != $hash) {
230  throw new Exception("Invalid Credentials (Corrupt Token) ", 15510);
231  }
232 
233  $result['data'] = $apptokarr;
234  } catch (Exception $e) {
235  $result = array('Status' => array('Code' => $e->getCode(), 'Message' => 'Failed ' . $e->getMessage()));
236  }
237  return $result;
238 }
239 
240 // end CheckV94key
241 
242 /**
243  *
244  * Create the base Banking Ticket string to be used by a cookie. All the values
245  * this function builds are in HB_ENV.
246  *
247  * @param array $pHbEnv This is the HB_ENV array for the current environment
248  * @param string $currSet The current Home Banking 'Ticket' string
249  * @param string $newSet The string of new values to set
250  * They are passed into the function in the syntax "key1=val1&key2=val2"
251  *
252  * @return string Returns the new Ticket session string value
253  */
254 //function BuildBaseSessionTicket( $pHbEnv ) {
255 //
256 // $ticket = "Cu={$pHbEnv["cu"]}&Ce={$pHbEnv['Ce']}&Clw={$pHbEnv['Clw']}"
257 // . "&Clu={$pHbEnv['Clu']}&Cauth={$pHbEnv['Cauth']}"
258 // . "&Fplog={$pHbEnv['Fplog']}&Fflog={$pHbEnv['Fflog']}"
259 // . "&Ffchg={$pHbEnv['Ffchg']}&Ffreset={$pHbEnv['Ffreset']}"
260 // . "&Ffremain={$pHbEnv['Ffremain']}&Fmsg_tx={$pHbEnv['Fmsg_tx']}"
261 // . "&Fset={$pHbEnv['Fset']}&Fset2={$pHbEnv['Fset2']}"
262 // . "&Fhdays={$pHbEnv['Fhdays']}&Fset3={$pHbEnv['Fset3']}&Ca=";
263 //
264 // // these may not exist in HB_ENV yet
265 // $userName = isset( $pHbEnv["Cn"] ) ? $pHbEnv["Cn"] : "";
266 // $ticket .= "&Cn=$userName";
267 //
268 // # Uid is an Odyssey concept - does not exist for Mammoth
269 //// $userId = isset( $pHbEnv["Uid"] ) ? $pHbEnv["Uid"] : "";
270 //// $ticket .= "&Uid=$userId";
271 //
272 // $userEmail = isset( $pHbEnv["Ml"] ) ? $pHbEnv["Ml"] : "";
273 // $ticket .= "&Ml=$userEmail";
274 //
275 // return $ticket;
276 //} // end BuildBaseSessionTicket
277 
278 /**
279  * Check the passed in session string and check the hash for validity. Return the
280  * component parts in an array for the caller to use.
281  *
282  * Returns: result = false on failure, array if successful
283  */
284 //function CheckSessionTicket( $pHBEnv, $sessionStr ) {
285 // try {
286 // $retVal = Array( "result" => array(), "resdesc" => "" );
287 //
288 // if ( empty($sessionStr) ) {
289 // // No Ticket
290 // throw new Exception( "Ticket not found {$pHBEnv['cu']}" );
291 // }
292 //
293 // $tarr = array();
294 // parse_str( $sessionStr, $tarr );
295 //
296 // $now = time();
297 // if ( $tarr['Ce'] < $now ) {
298 // // ticket has expired
299 // throw new Exception( "{$tarr['Cu']}:{$tarr['Cn']} Ticket Expired {$tarr['Ce']} < $now" );
300 // }
301 //
302 // if ( $pHBEnv['cu'] != "" &&
303 // $pHBEnv['cu'] != $tarr['Cu'] ) {
304 // // Different CU requested
305 // throw new Exception( "{$tarr['Cu']}:{$tarr['Cn']} Ticket Switch cu from {$tarr['Cu']} to {$pHBEnv['cu']}" );
306 // }
307 //
308 // // 2-Factor Ticket cookie
309 // // ** Verify ALL expected pieces exist in cookie
310 //
311 // if (is_null($tarr['Ch']) || is_null($tarr['Cn']) ||
312 // is_null($tarr['Ctime']) || is_null($tarr['Ce']) ||
313 // is_null($tarr['Cu']) || is_null($tarr['Clw']) ||
314 // is_null($tarr['Clu']) || is_null($tarr['Cauth']) ||
315 // is_null($tarr['Fplog']) || is_null($tarr['Fflog']) ||
316 // is_null($tarr['Ffchg']) || is_null($tarr['Ffremain']) ||
317 // is_null($tarr['Fmsg_tx']) || is_null($tarr['Fset']) ||
318 // is_null($tarr['Fset2']) || is_null($tarr['Fhdays']) || is_null($tarr["Ca"]) ||
319 // is_null($tarr['Fset3']) || is_null($tarr['Ml'])) {
320 // //Partial ticket, try again
321 // throw new Exception( "{$tarr['Cu']}:{$tarr['Cn']} Partial Ticket" );
322 // }
323 //
324 // $secret = $pHBEnv['secret'];
325 //
326 // // 2-Factor Ticket cookie
327 // // ** Recreate the hash from ALL the pieces and verify they are the same
328 // if ($tarr['Ch'] != MD5($secret .
329 // MD5(join (':', array($secret,
330 // $tarr['Ctime'], $tarr['Ce'], $tarr['Cu'], $tarr['Cn'],
331 // $tarr['Clw'], urlencode(trim($tarr['Clu'])), $tarr['Cauth'],
332 // urlencode(trim($tarr['Fplog'])), urlencode(trim($tarr['Fflog'])),
333 // $tarr['Ffchg'], $tarr['Ffreset'],
334 // $tarr['Ffremain'], $tarr['Fmsg_tx'], $tarr['Fset'],
335 // $tarr['Fset2'], $tarr['Fset3'], $tarr['Fhdays'],
336 // urlencode($tarr['Ml']), trim($tarr["Ca"])))))) {
337 // // hash doesn't match, someone is hacking
338 // throw new Exception( "{$tarr['Cu']}:{$tarr['Cn']} Ticket Hash Mismatch" );
339 // }
340 //
341 // $retVal["result"] = $tarr;
342 // } catch (Exception $err) {
343 // $retVal["result"] = false;
344 // $retVal["resdesc"] = $err->getMessage();
345 // }
346 //
347 // // log every occurance EXCEPT missing cookie
348 // if ( !empty($_COOKIE['Ticket']) ) {
349 // // apache_note sets variables for web server logging. Used later to split
350 // // web logfiles by credit union
351 // // ONLY add the note when we have ticket values
352 // apache_note("user_name","{$tarr['Cu']}:{$tarr['Cn']}");
353 // }
354 //
355 // return $retVal;
356 //} // end CheckSessionTicket
357 function MakeMFAKey($HB_ENV) {
358 
359  $mfaExpires = date("Ymd", time() + (94 * 86400)); # 94 days
360 
361  $mbrMfaQuest = HCU_MFADecode(HCU_JsonDecode($HB_ENV['mfaquest']));
362  $mfaMode = (intval($HB_ENV['Fset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')));
363  // if we set a date here, would have to update userrec to store it
364  // $mfadate = (empty($mbrMfaQuest['mfadate']) ? date("Ymd") : $mbrMfaQuest['mfadate']);
365  $mfadate = HCU_array_key_exists("mfadate", $mbrMfaQuest) ? $mbrMfaQuest['mfadate'] : "";
366  $MFAKey = hash_hmac('sha384',GetDeviceCookieContentString(),trim($HB_ENV['password']) . trim(strtolower($HB_ENV['savemail'])) . trim(strtolower($HB_ENV['confidence'])) . $mfaMode . $mfadate);
367 
368  return "{$mfaExpires}{$MFAKey}";
369 }
370 
371 function IsValidMFAKey($mfaKey, $userrec) {
372  $today = date("Ymd");
373 
374  $mfaExpires = substr($mfaKey,0,8);
375  $mfaHash = substr($mfaKey,8);
376 
377  $mfaMode = (intval($userrec['flagset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')));
378  $mfadate = HCU_array_key_exists("mfadate", $userrec) ? $userrec['mfadate'] : "";
379  $cookiecontent = hash_hmac('sha384',GetDeviceCookieContentString(),trim($userrec['passwd']) . trim(strtolower($userrec['email'])) . trim(strtolower($userrec['confidence']))
380  . $mfaMode . $mfadate);
381  if ($cookiecontent == $mfaHash && $mfaExpires > $today) {
382  $return_val = true;
383  } else {
384  $return_val = false;
385  }
386  return $return_val;
387 }
388 
389 function PWD_prompt($dbh, $HB_ENV) {
390  $reply_arr = array('STATUS' => array('CODE' =>3000,'SEVERITY' => 'ERROR'),
391  'DTSERVER' => date('YmdHis') );
392 
393  # needs to send
394  # 'forgot password' link
395  # confidence word
396 
397  $reply_arr['MFA']['AUTHREQ'] = 'MFP';
398  $reply_arr['MFA']['MFABUNDLE'] = $HB_ENV['mfaBundle'];
399 // $reply_arr['MFA']['PROMPT'] = $HB_ENV['MC']->msg('Login Enter Password');
400  $reply_arr['MFA']['PROMPT'] = 'Password';
401  $reply_arr['MFA']['CONFIDENCE'] = $HB_ENV['confidence'];
402 
403 // turn this off cleanly for outside vendors
404  if ($HB_ENV['flagset'] & GetFlagsetValue('CU_MEMRESET') ){
405  $reply_arr['MFA']['FORGOTLINK'] = $HB_ENV['loginpath'] . '/hcuResetPwd.prg?' . $HB_ENV['cuquery'];
406 // $reply_arr['MFA']['FORGOTLABEL'] = $HB_ENV['MC']->msg('Forgot your password');
407  $reply_arr['MFA']['FORGOTLABEL'] = 'Forgot your password';
408  }
409 
410  return $reply_arr;
411 }
412 
413 function EML_prompt($dbh, $HB_ENV) {
414  $reply_arr = array('STATUS' => array('CODE' =>3000,'SEVERITY' => 'ERROR'),
415  'DTSERVER' => date('YmdHis') );
416  $reply_arr['MFA']['AUTHREQ'] = 'EML';
417  $reply_arr['MFA']['MFABUNDLE'] = $HB_ENV['mfaBundle'];
418 // $reply_arr['MFA']['PROMPT'] = $HB_ENV['MC']->msg('Confirm Email Address');
419  $reply_arr['MFA']['PROMPT'] = 'Confirm Email Address';
420 
421  return $reply_arr;
422 }
423 function checkBundle($mode, $mfaBundle, $inPost) {
424 
425  switch ($mode) {
426  case 'TIME':
427  # special case to check the bundle age
428  $built = HCU_array_key_value('BUILDTIME', $mfaBundle);
429  if ( empty($built) || (time() - $built) > 900 ) {
430  $returnBundle = false;
431  } else {
432  $returnBundle = true;
433  }
434  break;
435  case 'EML':
436  # previous mode is MFA,
437  # bundle contains USERID
438  # Add MFA_E
439 
440  if ( HCU_array_key_value('mode', $mfaBundle) !== 'MFA' ||
441  HCU_array_key_value('USERID',$mfaBundle) !== HCU_array_key_value('USERID',$inPost) ) {
442  $returnBundle = false;
443  } else {
444  $returnBundle = true;
445  }
446  break;
447  case 'MFQ':
448  # previous mode is EML
449  # bundle contains USERID, MFA_E
450  # Add MFQ completion marker
451  if ( HCU_array_key_value('mode', $mfaBundle) !== 'EML' ||
452  HCU_array_key_value('USERID',$mfaBundle) !== HCU_array_key_value('USERID',$inPost) ||
453  !HCU_array_key_exists('MFA_E',$mfaBundle) ) {
454  $returnBundle = false;
455  } else {
456  $returnBundle = true;
457  }
458  break;
459  case 'MFP':
460  # previous mode is MFQ or EML or MFA
461  # bundle contains USERID, MFA_E,
462  # and either MFQ marker
463  if (
464  !((HCU_array_key_value('mode', $mfaBundle) === 'MFQ' && HCU_array_key_exists('haveMFQ',$mfaBundle)) ||
465  HCU_array_key_value('mode', $mfaBundle) === 'EML' || HCU_array_key_value('mode', $mfaBundle) === 'MFA')
466  || HCU_array_key_value('USERID',$mfaBundle) !== HCU_array_key_value('USERID',$inPost) ||
467  !HCU_array_key_exists('MFA_E',$mfaBundle)
468  ) {
469  $returnBundle = false;
470  } else {
471  $returnBundle = true;
472  }
473  break;
474  default:
475  # unexpected $mode - throw error
476  $returnBundle = false;
477  break;
478  }
479  return $returnBundle;
480 }
481 function createBundle($Cu, $mfaBundle) {
482  try {
483  $mfaBundle = HCU_PayloadEncode($Cu,$mfaBundle);
484  $mfaBundle = str_replace(array("+", "/", "="), array("-", "_", "."), $mfaBundle);
485  } catch (Exception $e) {
486  $mfaBundle='';
487  }
488 
489  return $mfaBundle;
490 }
491 function openBundle($Cu, $mfaBundle) {
492  try {
493 
494  $mfaBundle = str_replace(array("-", "_", "."), array("+", "/", "="), $mfaBundle);
495  $mfaBundle = HCU_PayloadDecode($Cu, $mfaBundle);
496 
497  } catch (Exception $e) {
498  $mfaBundle=array();
499  }
500  return $mfaBundle;
501 }
502 function GetMammothMFA($dbh, $CU, $MEMBER, $CQID, $PCHANGE) {
503  try {
504 
505 
506  $retMfaArray = Array(
507  "mfacount" => 0,
508  "answers" => Array(),
509  "challenge" => 0,
510  "authcode" => '',
511  "authexpires" => '',
512  "mfadate" => 0
513  );
514  if (empty($CU) || empty($MEMBER)) {
515  throw new Exception("Missing CU or MEMBER");
516  }
517 
518  $sql = "SELECT cu, accountnumber, quest_id, answer "
519  . "FROM cuquestselect WHERE cu = '$CU' "
520  . "AND accountnumber = '$MEMBER'";
521 
522  $sth = db_query($sql, $dbh);
523  $row = 0;
524  while ($drow = db_fetch_array($sth,$row)) {
525  $row++;
526  $retMfaArray['answers'][$drow['quest_id']] = $drow['answer'];
527  }
528 
529  $retMfaArray['mfacount'] = count($retMfaArray['answers']);
530  $retMfaArray['challenge'] = (empty($CQID) ? 0 : $CQID);
531  $retMfaArray['mfadate'] = (empty($PCHANGE) ? '' : strtotime($PCHANGE));
532 
533  } catch (Exception $e) {
534  // Error occurred - set the return value to default
535  $retMfaArray = Array(
536  "mfacount" => 0,
537  "answers" => Array(),
538  "challenge" => 0,
539  "authcode" => '',
540  "authexpires" => '',
541  "mfadate" => 0
542  );
543  }
544  return json_encode($retMfaArray);
545 }
546 function MFQ_defined($posted) {
547  # returns a count of MFA_* challenge question responses
548  # in (sanitized) array of posted values
549  $mfa = 0;
550  foreach (array_keys($posted) as $rkey) {
551  $m = strpos($rkey, 'MFA_');
552  if ($m !== FALSE && $m == 0) {
553  $mfa++;
554  }
555  }
556  return ($mfa);
557 }
558 /**
559  * Compare MFA challenge question responses to the values saved in the database
560  *
561  * @param resource $dbh database handle
562  * @param array $HB_ENV
563  * @param array $inPost posted responses
564  * @return array fail count, fail reason
565  * @throws Exception
566  */
567 function MFQ_response($HB_ENV, $inPost) {
568 
569  $aryMfaQuest = HCU_JsonDecode($HB_ENV['mfaquest']);
570  $mbrMfaQuest = HCU_MFADecode($aryMfaQuest); # from the db
571 
572  $fail = 0;
573  $failreason = 0;
574 
575  $dbcount = $mbrMfaQuest['mfacount']; # how many questions are in the db?
576  $mfapost = MFQ_resplist($inPost); # get list of MFA variables in the posted request (skips MFA_E)
577  # update from mammoth - gets id and answers in array using Odyssey format
578  $mfacount = count($mfapost); # how many MFA_ responses (excluding MFA_E) did we get?
579  try {
580  if ($mfacount < $dbcount && ($HB_ENV['Fset2'] & GetFlagsetValue('CU2_RANDOM_CHAL')) == 0) {
581  # expected more challenge questions than we got, so fail
582  $fail++;
583  $failreason = GetUserFlagsValue('MEM_LOGIN_FAILED_QST');
584  throw new Exception(__LINE__ . 'MFA Failed m $mfacount d $dbcount',$failreason);# expected chall ques and got none
585 
586  }
587  # make sure savemail is set in HB_ENV
588  # separate eMail check from challenge questions
589  # eMail check now using isValidEmail from cu_credentials
590 // if (strtolower($inPost['MFA_E']) !== strtolower($HB_ENV['savemail'])) {
591 // $fail++;
592 // $failreason = GetUserFlagsValue('MEM_LOGIN_FAILED_EMAIL');
593 // throw new Exception(__LINE__ . 'MFA Failed',$failreason);# email mismatch
594 // }
595  /*
596  * VALIDATE ANSWERS
597  * IF savecqid has a value and CU configured for only one answer, then ONLY validate that answer
598  *
599  * dbcount being greater than one MEANS the aryMfaQuest array contains an 'answers' key, is an array
600  * and has at least one value
601  */
602  /*
603  * mfaAnswerIdx is set to the list of questions to validate
604  * this should be:
605  * if random, and challenge is set, use challenge
606  * if random and challenge is not set, use the posted response
607  * if not random, use the db list
608  */
609  $aryMfaAnswers = $mbrMfaQuest['answers']; # stored answers
610  if (($HB_ENV['Fset2'] & GetFlagsetValue('CU2_RANDOM_CHAL'))) {
611  if ( $mbrMfaQuest['challenge'] > 0 ) {
612  // * Stuck on a challenge question
613  $mfaAnswerIdx = Array($mbrMfaQuest['challenge']);
614  } else {
615  // * Not stuck but still random, just check what they posted
616  $mfaAnswerIdx = array_keys($mfapost);
617  }
618  } else {
619  // * Not random, check all questions from db
620  $mfaAnswerIdx = array_keys($aryMfaAnswers);
621  }
622 
623  // ** NOW EVALUATE THE ANSWERS
624  # for each id in $mfaAnswerIdx
625  # intersect should be with posted answer set, not db answer set?
626  #
627  # 4/26/2018 added test to make sure there is some intersect
628  # between db list and list we will test, otherwise answering all
629  # the wrong questions still passes
630 
631  # aryMfaAnswers = db
632  # mfaAnswerIdx = ( if random, cqid if defined or posted ) else (not random, db )
633  $aryToTest = array_intersect_key($aryMfaAnswers, array_flip($mfaAnswerIdx));
634  if (count($aryToTest) == 0 ) {
635  $fail++;
636  $failreason = GetUserFlagsValue('MEM_LOGIN_FAILED_QST');
637  throw new Exception(__LINE__ . " MFA Failed Wrong Questions Answered",$failreason);# chall response mismatch
638  }
639  foreach ($aryToTest as $qid => $qanswer) {
640  if (strtolower(trim($qanswer)) != strtolower(trim(HCU_array_key_value("MFA_$qid",$inPost)))) {
641  $fail++;
642  $failreason = GetUserFlagsValue('MEM_LOGIN_FAILED_QST');
643 
644  throw new Exception(__LINE__ . " MFA $qid Failed",$failreason);# chall response mismatch
645  }
646  }
647  } catch (Exception $e) {
648  # logging handled at point of call - nothing to do here but fall through?
649  # $failreason = $e->getMessage();
650  }
651  return (array($fail, $failreason));
652 }
653 function MFQ_resplist($posted) {
654  # examines the (sanitized!) array of posted values
655  # returns list of MFA_ excluding MFA_E (email)
656  $mfalist = array();
657  foreach (array_keys($posted) as $rkey) {
658  $m = strpos($rkey, 'MFA_');
659  if ($m !== FALSE && $m == 0 && $rkey !== 'MFA_E') {
660 // $mfalist[] = array('cqid'=>substr($rkey,4),'cqanswer'=>$posted[$rkey]);
661  $cqid = substr($rkey,4);
662  $mfalist[$cqid] = $posted[$rkey];
663  }
664  }
665  return ($mfalist);
666 }
667 function Return_AllowedUpdate($CU, $MEMBER, $HB_ENV, $SENDKEY) {
668 
669 // $apptoken = MakeSessionUserkey( $HB_ENV );
670 
671  $upd_grace = $HB_ENV['Ffremain'];
672  $upd_wait = ($upd_grace == 0 ? "You must update your credentials now." :
673  "You must update your credentials within the next $upd_grace login" . ($upd_grace == 1 ? '.' : 's.') );
674 
675  $reply_arr = array('STATUS' => array('CODE' =>3110,'SEVERITY' => 'INFO'),
676  'SREQ' => $HB_ENV['forceupdate'],
677  'UREQ' => $HB_ENV['requpdate'],
678  'DTSERVER' => date('YmdHis'),
679  'MEMBER' => $MEMBER );
680 
681  if (is_array($SENDKEY)) {
682  foreach ($SENDKEY as $key => $value) {
683  $reply_arr[$key] = $value;
684  }
685  }
686 
687 
688  $reply_arr['ALLOWUPD'] = array();
689 
690  if ($HB_ENV['forceupdate'] != 0) {
691  $reply_arr['ALLOWUPD']['UPDCANWAIT'] = $upd_grace;
692  $reply_arr['ALLOWUPD']['UPDWAITPHRASE']=$upd_wait;
693  }
694  $reply_arr['ALLOWUPD']['PASSWORD'] = array(
695  'ALLOW' => (($HB_ENV['allowupdate'] & 1) ? "YES" : "NO"),
696  'REQ' => (($HB_ENV['forceupdate'] & 1) ? "YES" : "NO"));
697  $reply_arr['ALLOWUPD']['EMAIL'] = array(
698  'ALLOW' => (($HB_ENV['allowupdate'] & 2) ? "YES" : "NO"),
699  'REQ' => (($HB_ENV['forceupdate'] & 2) ? "YES" : "NO"));
700  $reply_arr['ALLOWUPD']['CHALLENGE'] = array(
701  'ALLOW' => (($HB_ENV['allowupdate'] & 4) ? "YES" : "NO"),
702  'REQ' => (($HB_ENV['forceupdate'] & 4) ? "YES" : "NO"));
703  $reply_arr['ALLOWUPD']['USERALIAS'] = array(
704  'ALLOW' => (($HB_ENV['allowupdate'] & 8) ? "YES" : "NO"),
705  'REQ' => (($HB_ENV['forceupdate'] & 8) ? "YES" : "NO"));
706  $reply_arr['ALLOWUPD']['PHONE'] = array(
707  'ALLOW' => (($HB_ENV['allowupdate'] & 16) ? "YES" : "NO"),
708  'REQ' => (($HB_ENV['forceupdate'] & 16) ? "YES" : "NO"));
709 
710  return $reply_arr;
711 }
712 
713 function Return_ReqUpdate($CU, $MEMBER, $HB_ENV, $SENDKEY) {
714  $dbh = $HB_ENV['dbh'];
715  $MC = $HB_ENV['MC'];
716 
717 // $apptoken = MakeSessionUserkey( $HB_ENV );
718 
719  # forceupdate 1 = reset password
720  # forceupdate 4 = reset security w/ challenge questions
721  # forceupdate 16 = reset security w/ access codes
722  # send updRemember if reset pwd or reset security is set
723  $upd_remember = $HB_ENV['forceupdate'] & 21;
724  $upd_grace = $HB_ENV['Ffremain'];
725  $upd_wait = ($upd_grace == 0 ? "You must update your credentials now." :
726  "You must update your credentials within the next $upd_grace login" . ($upd_grace == 1 ? '.' : 's.') );
727 
728  $reply_arr = array('STATUS' => array('CODE' =>3100,'SEVERITY' => 'INFO'),
729  'SREQ' => $HB_ENV['forceupdate'],
730  'UREQ' => $HB_ENV['requpdate'],
731  'DTSERVER' => date('YmdHis'),
732  'MEMBER' => $MEMBER );
733 
734  if (is_array($SENDKEY)) {
735  foreach ($SENDKEY as $key => $value) {
736  $reply_arr[$key] = $value;
737  }
738  }
739 
740  $reply_arr['REQUIREUPD'] = array();
741 
742  if ($HB_ENV['forceupdate'] != 0 && $HB_ENV['requpdate'] == 0) {
743  $reply_arr['REQUIREUPD']['UPDCANWAIT'] = $upd_grace;
744  $reply_arr['REQUIREUPD']['UPDWAITPHRASE']=$upd_wait;
745  }
746  if ($upd_remember) {
747  $reply_arr['REQUIREUPD']['UPDREMEMBER']='Remember This Device';
748  }
749  $upd_req = array();
750 
751  if ((($HB_ENV['forceupdate'] & 4) == 4 && $HB_ENV['requpdate'] == 0) || ($HB_ENV['requpdate'] & 4) == 4) {
752  # 2-factor and either force reset or not enough questions selected yet
753  # Security Reset: send master list of challenge questions
754  $upd_req = array('UPDPHRASEID' => 'CHALLENGE',
755  'UPDCONFLABEL' => 'This confidence word is used to identify and prevent phishing attempts when you access home banking through the web. It is not used in this app, but you are asked to set it now in case you later access your account through the web.',
756  'UPDCONFIDENCE' => htmlentities($HB_ENV['confidence'], ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE),
757  'UPDPHRASECOUNT' => $HB_ENV['cu_chgqst_count']);
758  if ($HB_ENV['cu_chgqst_count'] > 0) {
759  $upd_req['UPDPHRASELABEL'] = "Please select {$HB_ENV['cu_chgqst_count']} challenge questions";
760  $upd_req['UPDCHOICELIST'] = array();
761  $questlist = GetChallengeQuestions("DISPLAY", $dbh, $HB_ENV, $MC);
762  foreach ($questlist as $QstValue) {
763  $upd_req['UPDCHOICELIST'][]['CHOICEITEM'] = array('CQID' => $QstValue['cqid'],
764  'CQTEXT' => htmlentities($QstValue['display'], ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE));
765  }
766  if (($HB_ENV['requpdate'] & 4) == 4) {
767  # 'on-demand' update - send current selected questions/responses
768  $upd_req['CURRSELECTED'] = array();
769  foreach ($HB_ENV['MFA']['answers'] as $quest_id => $quest_resp) {
770  $upd_req['CURRSELECTED'][]['SELECTEDITEM'] = array('CQID' => $quest_id,
771  'CQRESP' => htmlentities($quest_resp, ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE));
772  }
773  }
774  }
775  $reply_arr['REQUIREUPD'][]['REQUPD'] = $upd_req;
776  }
777 
778  if ((($HB_ENV['forceupdate'] & 1) == 1 && $HB_ENV['requpdate'] == 0) || ($HB_ENV['requpdate'] & 1) == 1) {
779  # Password
780 
781  $noticesAry = Get_NoticeInfo($dbh, $HB_ENV, $HB_ENV['MC'], "P", "pwdRules", false);
782  $hasRules = false;
783 
784  if ( $noticesAry["status"]["code"] == "000" && $noticesAry["notice"][0]["notice_id"] ) {
785  $hasRules = true;
786  $helpdoc = $noticesAry["notice"][0]["notice_linktarget"];
787  }
788  $pwdRequires = Get_PwdRules($dbh, $HB_ENV);
789  $upd_req = array('UPDPHRASEID' => 'PASSWORD',
790  'UPDPHRASELABEL' => 'Please select a new password.',
791  'PWDRULESLINK' => ($hasRules ? htmlentities($helpdoc, ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE) : ''),
792  'PWDRULESLABEL' => ($hasRules ? 'I have read the Recommended Password Guidelines' : ''),
793  'PWDADVLABEL' => ($hasRules ? 'Recommended Guidelines' : ''));
794  foreach ($pwdRequires as $pwdkey => $pwdval) {
795  $pwdkey = strtoupper($pwdkey);
796  $upd_req['PWDREQUIRES'][$pwdkey] = $pwdval;
797  }
798 // if ($pwdRequires['spec'] > 0) {
799  $pwdSpecChar = Get_PwdSpecialCharacters();
800  $upd_req['PWDREQUIRES']['PWDSPECIALCHARS'] = htmlspecialchars($pwdSpecChar, ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE);
801 // }
802 
803  $reply_arr['REQUIREUPD'][]['REQUPD'] = $upd_req;
804  }
805  if ((($HB_ENV['forceupdate'] & 6) > 0 && $HB_ENV['requpdate'] == 0) || ($HB_ENV['requpdate'] & 2) == 2) {
806  # verify email is set or email is empty,
807  # or we are sending the CHALLENGE set so include email with it (apps treat email as extra challenge question)
808  $upd_req = array('UPDPHRASEID' => 'EMAIL',
809  'UPDPHRASELABEL' => 'Please provide your email address.',
810  'CURRENTEMAIL' => htmlentities($HB_ENV['Ml'], ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE),
811  'CURRENTOPTIN' => $HB_ENV['egenl_flag'],
812  'OPTIN_PHRASE' => $MC->msg('Yes Email List'));
813 
814  $reply_arr['REQUIREUPD'][]['REQUPD'] = $upd_req;
815  }
816 
817  if ((($HB_ENV['forceupdate'] & 8) == 8 && $HB_ENV['requpdate'] == 0) || ($HB_ENV['requpdate'] & 8) == 8) {
818  # user_alias
819  $maymust = (($HB_ENV['Fset2'] & GetFlagsetValue('CU2_ALIAS_REQ')) == GetFlagsetValue('CU2_ALIAS_REQ') ? 'must' : 'may');
820  $aliaslabel = $MC->combo_msg('Username Set', 0, '#MAYMUST#', "$maymust");
821  $upd_req = array('UPDPHRASEID' => 'USERALIAS',
822  'UPDPHRASEREQ' => ($HB_ENV['alias'] == 'NONE' ? 'NO' : $HB_ENV['alias']),
823  'UPDPHRASELABEL' => htmlentities($aliaslabel, ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE));
824  if (($HB_ENV['requpdate'] & 8) == 8) {
825  # 'on-demand' update - send current user alias
826  $upd_req['CURRENTALIAS'] = htmlentities($HB_ENV['useralias'], ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE);
827  }
828  $reply_arr['REQUIREUPD'][]['REQUPD'] = $upd_req;
829  }
830  if ((($HB_ENV['forceupdate'] & 16) == 16 && $HB_ENV['requpdate'] == 0) || ($HB_ENV['requpdate'] & 16) == 16) {
831 
832  $upd_req = array('UPDPHRASEID' => 'PHONES',
833  'UPDCONFLABEL' => 'This confidence word is used to identify and prevent phishing attempts when you access home banking through the web. It is not used in this app, but you are asked to set it now in case you later access your account through the web.',
834  'UPDCONFIDENCE' => htmlentities($HB_ENV['confidence'], ENT_NOQUOTES | ENT_XML1, 'UTF-8', FALSE));
835  # add array entries for phone numbers
836  $upd_req['MOBILE']['MAXLIMIT'] = MAX_PHONES;
837  $upd_req['MOBILE']['UPDMBLLABEL'] = $MC->msg('TXT Enabled');
838  $phones = GetUserPhones($CU, $UID, $HB_ENV);
839  if (is_array($phones['mobile'])) {
840  foreach ($phones['mobile'] as $ph => $phnum) {
841 // * strip the punctuation
842  $upd_req['MOBILE'][]['PHONE'] = preg_replace('/\D/', '', $phnum);
843  }
844  }
845  $reply_arr['REQUIREUPD'][]['REQUPD'] = $upd_req;
846  }
847 
848  return $reply_arr;
849 } // end Return_ReqUpdate
850 
851 function Return_ResponseOK($CU, $MEMBER, $SENDKEY, $MESSAGE) {
852 
853  $reply_arr = array('STATUS' => array('CODE' =>0,'SEVERITY' => 'INFO', 'MESSAGE'=> array('INFO' => $MESSAGE)),
854  'DTSERVER' => date('YmdHis'),
855  'MEMBER' => $MEMBER );
856 
857  if (is_array($SENDKEY)) {
858  foreach ($SENDKEY as $key => $value) {
859  $reply_arr[$key] = $value;
860  }
861  }
862 
863  return $reply_arr;
864 }
865 function GetPWChange($dbh, $ORG, $MEMBER) {
866  $sqluser = "SELECT coalesce(pwchange,'2006-01-01 00:00:00.00000-06') as pwchange
867  FROM {$ORG}user
868  WHERE user_name = '$MEMBER' ";
869 
870  $mbr_sth = db_query($sqluser, $dbh);
871  if (db_num_rows($mbr_sth) == 1) {
872  $return_ary = db_fetch_assoc($mbr_sth,0);
873  $return_ary['pchange'] = strtotime($return_ary['pwchange']);
874  $return_ary['rowfound'] = 1;
875  } else {
876  $return_ary['rowfound'] = 0;
877  }
878  return $return_ary;
879 }
880 
881 function LogFail($dbh, $HB_ENV, $inPost, $failbit) {
882  $updstat = UpdateMemberFailedLogin($dbh, $HB_ENV['cu'], $HB_ENV['user_name'], $failbit);
883  $p_meta = array('UA' => $_SERVER['HTTP_USER_AGENT']);
884  $p_hbenv = array('Cu' => $inPost['ORG'], 'Cn' => $HB_ENV['Cn'], 'user_id' => $HB_ENV['Uid']);
885  TrackUserLogin($dbh, $p_hbenv, $HB_ENV['platform'], $failbit, $_SERVER['REMOTE_ADDR'], $p_meta);
886 }
887 
888 function LogPass($dbh, &$HB_ENV){
889  # if not online, set the logtrack parameters to NOT decrement the remaining logins
890  $must = ($HB_ENV['offline'] != 'N' || ($HB_ENV['forceupdate'] & 29) == 0 ? 'N' : 'Y');
891  $tomorrow = date('Y-m-d', mktime(0, 0, 0, date("m"), date("d") + 1, date("Y")));
892  $pchange = ($HB_ENV['offline'] != 'N' ? $tomorrow : $HB_ENV['pwchange']);
893  $adjust = ($must == 'Y' ? 1 : 0);
894  # and fix the corresponding value in HB_ENV
895  $HB_ENV['Ffremain']-=$adjust;
896  // The challenge key must be reset to 0 for SUCCESSFUL LOGINS
897  // and the sac code and expiration must be reset empty for SUCCESSFUL LOGINS
898  $HB_ENV['MFA']['challenge'] = 0;
899  $HB_ENV['MFA']['authcode']='';
900  $HB_ENV['MFA']['authexpires']='';
901 
902 // $GLOBALS['HB_ENV']['SYSENV']['logger']->info("UpdateMemberLoginTrack(dbh, {$HB_ENV['cu']}, {$HB_ENV['user_name']}, $must, $pchange, {$HB_ENV['platform']}, {$HB_ENV['MFA']});");
903  $updstat = UpdateMemberLoginTrack($dbh, $HB_ENV['cu'], $HB_ENV['user_name'], $must, $pchange, $HB_ENV['platform'], $HB_ENV['MFA']);
904  $p_meta = array('UA' => $_SERVER['HTTP_USER_AGENT']);
905  TrackUserLogin($dbh, $HB_ENV, $HB_ENV['platform'], 0, $_SERVER['REMOTE_ADDR'], $p_meta);
906 
907 }
908  /**
909  *
910  * @param string $data XML string to be formatted
911  * @return string XML 'pretty' with indents
912  */
913 // function Format_AppFeed($data) {
914 //
915 // $dom = new DOMDocument();
916 //
917 // $dom->preserveWhiteSpace = false;
918 // $dom->formatOutput = true;
919 //
920 // $dom->loadXML($data);
921 // $out = $dom->saveXML();
922 //
923 // # commenting following line causes syntax error because of the ?GT sequence
924 // # hence the backslash. Remove that slash if code is reactivated
925 // $out = str_replace('<?xml version="1.0"?>','',$out);
926 //
927 // return ($out);
928 //}
929 /**
930  * Change an associative array to an XML string.
931  *
932  * @param array $ar associative array of items to be transformed to XML
933  * @param string $base root xml tag to use
934  *
935  * @return string XML result string
936  */
937 function assocArrayToXML($ar, $base='APPFEED',$attrs=array()) {
938 
939  $xml = new SimpleXMLExtended("<$base/>");
940  if ( isset($attrs) && is_array($attrs)) {
941  foreach ($attrs as $key => $value) {
942  if(preg_match("/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i", $key, $matches) && $matches[0] == $key) {
943  $xml->addAttribute($key, $value);
944  }
945  }
946  }
947  $f = create_function('$f,$c,$a','
948  foreach($a as $k=>$v) {
949  if(is_array($v)) {
950  if (is_numeric($k)) {
951  $f($f,$c,$v);
952  } else {
953  $ch=$c->addChild($k);
954  if(isset($v["@attributes"]) && is_array($v["@attributes"])) {
955  foreach($v["@attributes"] as $key => $value) {
956  if(preg_match("/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i", $key, $matches) && $matches[0] == $key) {
957  // only add the attribute if the name is valid
958  $value = $value === true ? "true" : $value;
959  $value = $value === false ? "false" : $value;
960  $ch->addAttribute($key, $value);
961  }
962  }
963  unset($v["@attributes"]); //remove the key from the array once done.
964  }
965  // check if it has a value directly stored as string
966  if(isset($v["@cdata"])) {
967  $value = $v["@cdata"];
968  $value = $value === true ? "true" : $value;
969  $value = $value === false ? "false" : $value;
970  $ch->addCData($value);
971  unset($v["@cdata"]); //remove the key from the array once done.
972  }
973 
974  $f($f,$ch,$v);
975  }
976  } else {
977  $c->addChild($k,$v);
978  }
979  }');
980  if (is_array($ar) ) {
981  $f($f,$xml,$ar);
982  }
983  $return = $xml->asXML();
984 
985  $return = str_replace('<?xml version="1.0"?>','',$return);
986 
987  return $return;
988 }
989 // http://coffeerings.posterous.com/php-simplexml-and-cdata
990 // https://stackoverflow.com/questions/6260224/how-to-write-cdata-using-simplexmlelement/6260295
991 class SimpleXMLExtended extends SimpleXMLElement {
992  public function addCData($cdata_text) {
993  $node = dom_import_simplexml($this);
994  $no = $node->ownerDocument;
995  $node->appendChild($no->createCDATASection($cdata_text));
996  }
997 }
998 
999 /**
1000  *
1001  * @param resource $dbh database connection handle
1002  * @param array $HB_ENV user values
1003  * @param object $MC dictionary
1004  * @return array
1005  */
1006 function MFQ_send_chall($dbh, $HB_ENV, $MC) {
1007 
1008 # sending all questions regardless of '1 random' setting for cu
1009 # updated 9/12 to recognize '1 random' setting
1010 
1011 // $MemberChallengeQuestions_ary=GetChallengeQuestions("CHALLENGE", $dbh, $HB_ENV, $MC, $HB_ENV['Cn']);
1012 // futzing w/HB_ENV to match what legacy mammoth function expects
1013 // switch back to odyssey function when updating to odyssey
1014  $HB_ENV['cu'] = $HB_ENV['Cu'];
1015  $HB_ENV['HCUPOST']['username'] = $HB_ENV['Cn'];
1016  $HB_ENV['username'] = $HB_ENV['Cn']; # yes, the function really uses both
1017  $HB_ENV['HCUPOST']['Flang'] = 'en_US';
1018  $HB_ENV['flagset2'] = $HB_ENV['Fset2'];
1019 
1020  $MemberChallengeQuestions_ary=GetChallengeQuestions("CHALLENGE", $dbh, $HB_ENV );
1021 
1022  $reply_arr = array('STATUS' => array('CODE' =>3000,'SEVERITY' => 'ERROR'),
1023  'DTSERVER' => date('YmdHis') );
1024 
1025 // old path $reply_arr['MFACHALLENGETRNRS']['MFACHALLENGERS'] = array();
1026  $reply_arr['MFA'] = array();
1027  $reply_arr['MFA']['AUTHREQ'] = 'MFQ';
1028  $reply_arr['MFA']['MFABUNDLE'] = $HB_ENV['mfaBundle'];
1029 
1030 //# force 'What email' as first challenge question
1031 // $itm_arr = array('MFACHALLENGE' => array(
1032 // 'MFAPHRASEID' => 'MFA_E',
1033 // 'MFAPHRASELABEL' => 'What email address is saved with this account?'));
1034 //
1035 // $reply_arr['MFA'][] = $itm_arr;
1036 
1037 # and now add mfa questions, if any were found
1038  if (count($MemberChallengeQuestions_ary)) {
1039  foreach ((array) $MemberChallengeQuestions_ary as $chakey => $mfaitem) {
1040  $itm_arr = array('MFACHALLENGE' => array(
1041  'MFAPHRASEID' => "MFA_{$mfaitem['cqid']}",
1042  'MFAPHRASELABEL' => "{$mfaitem['display']}"));
1043 
1044  $reply_arr['MFA'][] = $itm_arr;
1045 
1046  }
1047  }
1048 
1049  return $reply_arr;
1050 }