Odyssey
cu_credentials.i
1 <?php
2 /*
3  * Script: cu_credentials
4  * Purpose: This script will contain function to determine the security status
5  * of the member.
6  *
7  * Check_HomeCU_Status
8  * Check_Member_Credentials
9  * returnaddress
10  * Check_Member_Login
11  * Check_Member_UseAlias
12  */
13 
14 /* NOTE: This function is being deprecated. Use the following function, LoadCUAdmin().
15  * Function: Check_HomeCU_Status
16  * Purpose: This will check the status of the homebanking_status as well as the
17  * status of the current credit union and database connection
18  * IT will set global values of some common variables that will be used later
19  *
20  * NOTE ***
21  * This is being rewritten a little. -- This function will NOT set the loginscript
22  * to call, that needs to be done by the script calling this one
23  *
24  * Parameters:
25  * p_dbh - database handle
26  * Returns:
27  * GLOBALS SET:
28  * cver -- Cookie Version
29  * loginscript -- used for homebanking, but need to set
30  * loginpath -- path where login script will exist
31  * TicketDomain -- The cookie domain
32  * offline -- credit union offline status
33  * cu -- cu code
34  */
35 function Check_HomeCU_Status(&$p_dbh, &$p_hb_env) {
36 
37 // SHOULD BE ALREADY SET IN dbenv $GLOBALS['TicketDomain'] = ".homecu.net"; //Which domain shall we protect?
38 
39  $p_hb_env['cu'] = substr(filter_input(INPUT_GET, 'cu', FILTER_SANITIZE_STRING, array('flags' => (FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH))), 0, 12);
40  if ($p_hb_env['cu'] == '' && in_array(basename($_SERVER['SCRIPT_NAME']), array('OFXRequest.prg', 'hcuAppFeed.prg'))) {
41  $p_hb_env['cu'] = substr(filter_input(INPUT_POST, 'ORG', FILTER_SANITIZE_STRING, array('flags' => (FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH))), 0, 12);
42  $p_hb_env['no_cookies'] = true;
43  }
44  if ($p_hb_env['homebanking_status'] != "O") {
45  $p_dbh = db_pconnect($p_hb_env['SYSENV']['db']);
46  // *** SYSTEM IS SET TO BE ONLINE --- LOAD VALUES FROM DATABASE
47  // * LOAD the fields from the Database, these may be overriden later by cookie
48  // * values -- the dual naming pre-auth and coookie values is a pain..
49  // * They should be changed to be the same.
50 
51  if ($p_dbh) {
52  // ** Database Was successfully loaded
53 
54  $sql="SELECT cookie_ver, loginscript, offlinestat, offlineblurb, trmemomaxlen, min_chlng_qst,
55  livewait, trim(lastupdate), flagset, flagset2, flagset3, histdays, trmemomaxlen,
56  coalesce(retrylimit,5), coalesce(gracelimit,5), orgname, pname, rt, tz, livebatch
57  FROM cuadmin
58  WHERE cu = '{$p_hb_env['cu']}'";
59  $sth = db_query($sql, $p_dbh);
60 
61  if (db_num_rows($sth) > 0) {
62  list ($cver, $loginscript, $offline, $offlinemsg, $trmemomaxlen, $min_chlng_qst, $livewait,
63  $lastupdate, $flagset, $flagset2, $flagset3, $histdays, $trmemomaxlen,
64  $failedremain, $forceremain, $orgname, $pname, $rt, $tz, $livebatch) = db_fetch_array($sth,0);
65 
66  $p_hb_env['cver'] = ($cver == 'F' ? 'F' : 'L');
67 
68  $p_hb_env['offline'] = (trim($offline) == '' ? 'N' : trim($offline));
69  $p_hb_env['offlinemsg'] = $offlinemsg;
70  $p_hb_env['trmemomaxlen'] = intval($trmemomaxlen);
71  $p_hb_env['cu_chgqst_count'] = intval($min_chlng_qst);
72 
73  $p_hb_env['livewait'] = (is_null($livewait) ? "300" : $livewait);
74  $p_hb_env['lastupdate'] = (trim($lastupdate)=="" ? "Unknown" : urlencode(trim($lastupdate)));
75  $p_hb_env['flagset'] = (is_null($flagset) ? 0 : $flagset);
76  $p_hb_env['flagset2'] = (is_null($flagset2) ? 0 : $flagset2);
77  $p_hb_env['flagset3'] = (is_null($flagset3) ? 0 : $flagset3);
78  $p_hb_env['histdays'] = (is_null($histdays) ? 0 : $histdays);
79  $p_hb_env['trmemomaxlen'] = (is_null($trmemomaxlen) ? 0 : $trmemomaxlen);
80  $p_hb_env['failedremain'] = $failedremain;
81  $p_hb_env['forceremain'] = $forceremain;
82  $p_hb_env['orgname'] = trim($orgname);
83  $p_hb_env['pname'] = trim($pname);
84  $p_hb_env['rt'] = trim($rt);
85  $p_hb_env['chome'] = strtolower($p_hb_env['cu']);
86  $p_hb_env['live'] = (trim($livebatch) == 'L' ? 1 : 0);
87  $p_hb_env['tz'] = trim($tz) == "" ? "US/Mountain" : ((strpos($tz,"/") === false ) ? "US/" . trim($tz) : trim($tz));
88  }
89 
90  } else {
91 
92  $p_hb_env['cver'] = "F";
93  $p_hb_env['loginscript'] = "cuauth";
94  $p_hb_env['offline'] = "Y";
95  $p_hb_env['offlinemsg'] = "We are currently working on the system.";
96  }
97  } else {
98  // *** SYSTEM IS SET TO OFFLINE -- Set the variables accordingly
99  // ** Set the version to legacy and force the login script to be 'cuauth'
100  // * cuauth will then display the offline message
101 
102  $p_hb_env['cver'] = "F";
103  $p_hb_env['loginscript'] = "cuauth";
104  $p_hb_env['offline'] = "Y";
105  $p_hb_env['offlinemsg'] = "We are currently working on the system.";
106  }
107 
108  // ** SET THE GLOBAL VALUES
109  // print "BEFORE - {$p_hb_env['Cu']} :: ";
110  $p_hb_env['Flang'] = ''; // Initialize the value
111  if ( HCU_array_key_exists( "no_cookies", $p_hb_env ) ) {
112  $p_hb_env['Flang'] = 'en_US';
113  } else {
114  // ** SET the language -- FROM THE GET
115  // ** At this time, I am going to try and change this so it will only evaluate
116  // ** Flang from the cookie for Flang. It will only get value from there
117  $cookieName = $p_hb_env['cu'] . '_lang';
118  if (HCU_array_key_exists( $cookieName, $_COOKIE)) {
119  $p_hb_env['Flang'] = $_COOKIE[$cookieName];
120  }
121  }
122  // ** LOGIN PATH MAY NEED TO BE SET SOMEWHERE ELSE -- PER homecu request script
123  /**
124  * @var string currentscript - the current script running, should be able to use this in place of hard coding script name
125  */
126  $p_hb_env['currentscript'] = basename($_SERVER['SCRIPT_NAME']);
127 
128  $p_hb_env['remoteIp'] = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_STRING);
129 /*
130  * Values set in HB_ENV INSTEAD
131 
132  $GLOBALS['cver'] = $cver;
133  $GLOBALS['loginscript'] = $loginscript;
134  $GLOBALS['offline'] = $offline;
135  $GLOBALS['offlinemsg'] = $offline;
136  $GLOBALS['cu'] = $cu;
137  */
138  return ($offline == 'N' ? FALSE : TRUE);
139 }
140 
141 /*
142  * Function: LoadCUAdmin
143  * Purpose: This will check the status of the homebanking_status as well as the
144  * status of the given credit union and database connection
145  * It will set global values of some credit union specific variables that will be used later.
146  *
147  * NOTE ***
148  * This is being rewritten a little. -- This function will NOT set the loginscript
149  * to call, that needs to be done by the script calling this one
150  * Caller also needs to pass in the CU and database handle.
151  *
152  * Parameters:
153  * pDbh - database handle
154  * pCU - Credit Union code
155  * pEnv - reference to Environment structure
156  * Returns:
157  * GLOBALS SET:
158  * cver -- Cookie Version
159  * loginscript -- used for homebanking, but need to set
160  * loginpath -- path where login script will exist
161  * TicketDomain -- The cookie domain
162  * offline -- credit union offline status
163  */
164 function LoadCUAdmin( $pDbh, $pCU, &$pEnv ) {
165  $isOffline = true; // assume the worst
166  $isValidCU = false;
167 
168 // SHOULD BE ALREADY SET IN dbenv $GLOBALS['TicketDomain'] = ".homecu.net"; //Which domain shall we protect?
169  if ($pEnv['homebanking_status'] != "O") {
170  // *** SYSTEM IS SET TO BE ONLINE --- LOAD VALUES FROM DATABASE
171  // * LOAD the fields from the Database, these may be overriden later by cookie
172  // * values -- the dual naming pre-auth and coookie values is a pain..
173  // * They should be changed to be the same.
174 
175  # Check $pCU before using it, since it is not escaped in the sql below
176  # ctype_alnum ensures not empty / null, and contains only letters / digits
177  if ( $pDbh && ctype_alnum($pCU) ) {
178  // ** Database Was successfully loaded & cu value appears reasonable
179 
180  $sql="SELECT cu, cookie_ver, loginscript, offlinestat, offlineblurb, trmemomaxlen, min_chlng_qst,
181  livewait, trim(lastupdate), flagset, flagset2, flagset3, histdays, trmemomaxlen,
182  coalesce(retrylimit,5), coalesce(gracelimit,5), orgname, pname, rt, tz, livebatch
183  FROM cuadmin
184  WHERE cu = '{$pCU}'";
185  $sth = db_query($sql, $pDbh);
186 
187  if (db_num_rows($sth) > 0) {
188  list ($cu, $cver, $loginscript, $offline, $offlinemsg, $trmemomaxlen, $min_chlng_qst, $livewait,
189  $lastupdate, $flagset, $flagset2, $flagset3, $histdays, $trmemomaxlen,
190  $failedremain, $forceremain, $orgname, $pname, $rt, $tz, $livebatch) = db_fetch_array($sth,0);
191 
192  $pEnv['cver'] = ($cver == 'F' ? 'F' : 'L');
193 
194  $pEnv['offline'] = (trim($offline) == '' ? 'N' : trim($offline));
195  $pEnv['offlinemsg'] = $offlinemsg;
196  $pEnv['trmemomaxlen'] = intval($trmemomaxlen);
197  $pEnv['cu_chgqst_count'] = intval($min_chlng_qst);
198 
199  $pEnv['livewait'] = ((is_null($livewait) || $livewait == "0") ? "300" : $livewait);
200  $pEnv['lastupdate'] = (trim($lastupdate)=="" ? "Unknown" : urlencode(trim($lastupdate)));
201  $pEnv['flagset'] = (is_null($flagset) ? 0 : $flagset);
202  $pEnv['flagset2'] = (is_null($flagset2) ? 0 : $flagset2);
203  $pEnv['flagset3'] = (is_null($flagset3) ? 0 : $flagset3);
204  $pEnv['histdays'] = (is_null($histdays) ? 0 : $histdays);
205  $pEnv['trmemomaxlen'] = (is_null($trmemomaxlen) ? 0 : $trmemomaxlen);
206  $pEnv['failedremain'] = $failedremain;
207  $pEnv['forceremain'] = $forceremain;
208  $pEnv['orgname'] = trim($orgname);
209  $pEnv['pname'] = trim($pname);
210  $pEnv['rt'] = trim($rt);
211  $pEnv['chome'] = trim(strtolower($cu));
212  $pEnv['live'] = (trim($livebatch) == 'L' ? 1 : 0);
213  $pEnv['tz'] = trim($tz) == "" ? "US/Mountain" : ((strpos($tz,"/") === false ) ? "US/" . trim($tz) : trim($tz));
214 
215  $isOffline = !( $pEnv['offline'] == "N" );
216  $isValidCU = true;
217  }
218  }
219  }
220 
221  if ( $isOffline ) {
222  // *** SYSTEM IS SET TO OFFLINE -- Set the variables accordingly
223  // ** Set the version to legacy and force the login script to be 'cuauth'
224  // * cuauth will then display the offline message
225  $pEnv['cver'] = "F";
226  $pEnv['loginscript'] = "cuauth";
227  $pEnv['offline'] = "Y";
228  $pEnv["offlinemsg"] = HCU_array_key_exists("offlinemsg", $pEnv) ? trim($pEnv["offlinemsg"]) : "";
229  $pEnv['offlinemsg'] = $pEnv["offlinemsg"] == "" ? "We are currently working on the system." : $pEnv["offlinemsg"];
230  }
231 
232 /*
233  * Values set in HB_ENV INSTEAD
234 
235  $GLOBALS['cver'] = $cver;
236  $GLOBALS['loginscript'] = $loginscript;
237  $GLOBALS['offline'] = $offline;
238  $GLOBALS['offlinemsg'] = $offline;
239  $GLOBALS['cu'] = $cu;
240  */
241  return $isValidCU;
242 } // end LoadCUAdmin
243 
244 /**
245  * This function needs to be called by scripts at the top of their page.
246  * It will check to see if the user needs to be redirected to any of
247  * the settings pages. This is a change from the class HomeCU where
248  * redirect is handled by the login script. This will check in ONE
249  * place and always handle the same. Possible bringin up an in between
250  * screen that has a list of items that will need to be set
251  *
252  * @param integer $p_dbh This is the dB handle, $dbh
253  * @param array $p_hb_env This is the value fo the $HB_ENV array for the Home Banking Environment
254  * @param class $p_mc This is the class value for the
255  *
256  * @return array Array ([code] => "", [severity] => "", [errors] => "")
257  *
258  * 000 -- everything was OK, no changes
259  * -- If 000 is not set, then the code returned will determine
260  * -- the security item to process first in priority
261  * 010 -- Redirect member to the SET ALL settings, however, they MAY NOT SKIP
262  * 011 -- Redirect member to the SET ALL settings, similar to OLD MbrSetCfg
263  * 012 -- Redirect member to the password settings
264  * 013 -- Redirect member to the email settings
265  * 014 -- Redirect member to the user alias settings
266  * severity {ERROR, SUCCESS}
267  */
268 function Check_Member_Settings($p_dbh, $p_hb_env, $p_mc) {
269 
270  $retAry = Array("code" => "000", "severity" => "SUCCESS", "errors" => Array());
271  // * Also check the value of sC, expected values are {1 -- In process of setting, 2 -- Skip until later}
272  // * it was discussed with miki on whether to use an alternate flag on end of cookie
273  // * or use msg_tx. At the time this was decided msg_tx only had one remaining bit left
274  // * and I needed two. Changes to cuusers is of course time sensitive
275  // * sC is NOT in the hash of the cookie. So I want to check to make sure they
276  // * don't somehow change the value to 2 allowing them to skip while the forceremain is 0
277 
278  // * it is possible I may want this script to update the fremain..
279 
280  // If sC is NOT '2' (they pressed wait on the change security option ||
281  // forceremain is 0, we will force the issue if they have no more secuirty graces
282 /*
283  $mbrInfo = GetUserInfo($p_dbh, $p_hb_env, $p_mc, Array("user_name_alias" => $p_hb_env['Cn'], "cu" => $p_hb_env['Cu']));
284  // ** if we were able to retrieve the member data
285  $freset = 0;
286  if ($mbrInfo['status']['code'] == '000') {
287  $freset = $mbrInfo['data']['cuusers_forcereset'];
288  }
289 */
290 
291  // ** First Check to see if we are offline -- security questions are NOT allowed
292  // * as readonly -- So I will always set the flag to false in case the calling
293  // * script has it set to true.
294  // * If the system is offline, I simply want to return A success -- when
295  // * the system comes back online - the problem will be corrected
296  $p_hb_env['allowReadonly'] = false;
297 
298  if (!hcu_checkOffline($p_dbh, $p_hb_env)) {
299  return $retAry;
300  }
301 
302 
303  // ** Determine if the member needs to set Security Questions -- 2 factor only
304  // * TRUE - if freset is SET
305  // * OR
306  // * if NO challenge questions are set, then enter here as well
307  $aryMfaQuest = GetChallengeQuestions('CURRENT', $p_dbh, $p_hb_env, $p_mc, $p_hb_env['Cn']);
308 
309  // ** HANDLE NO Challenge questions separately FROM RESET CHALLENGE
310  // * do NOT allow the user to bypass the settings if they have NO challenge questions
311  // ** ONLY do one or the other of the challenge question errors
312  // ** ONLY when CU is NOT setup to use MFA Secure Access Codes 'CU3_MFA_AUTHCODE'
313  if (($p_hb_env['cver'] == "F") && ($aryMfaQuest['mfacount'] == 0 && intval($p_hb_env['cu_chgqst_count']) > 0) && !($p_hb_env['Fset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE'))) {
314 // // ** IF fremain is counted down to zero, then they may NOT skip
315  // * THEY MAY NOT SKIP THIS OPTION
316  // ** Prioritize this security option
317  $retAry['code'] = ($retAry['code'] == '000' ? "010" : $retAry['code']);
318  $retAry['severity'] = "ERROR";
319  $retAry['errors'][] = $p_mc->msg("Need challenge questions");
320  } else {
321  if (($p_hb_env['cver'] == "F") && ($p_hb_env['Ffreset'])) {
322  // // ** IF fremain is counted down to zero, then they may NOT skip
323  if (intval(HCU_array_key_value('sC', $p_hb_env)) != '2' || $p_hb_env['Ffremain'] == 0) {
324  // ** Prioritize this security option
325  $retAry['code'] = ($retAry['code'] == '000' ? "011" : $retAry['code']);
326  $retAry['severity'] = "ERROR";
327  $retAry['errors'][] = $p_mc->msg("Need challenge questions");
328  }
329  }
330  }
331 
332  // ** Determine if the member needs to reset their password
333  // * TRUE if Ffchg has been set other than 0
334 
335  if ($p_hb_env['Ffchg'] == 'Y') {
336  if (intval(HCU_array_key_value('sC', $p_hb_env)) != '2') {
337  $retAry['code'] = ($retAry['code'] == '000' ? "012" : $retAry['code']);
338  $retAry['severity'] = "ERROR";
339  $retAry['errors'][] = $p_mc->msg("Password flagged reset");
340  }
341  }
342 
343  // ** Is the member required to verify the email?
344  if (($p_hb_env['Fmsg_tx'] & GetMsgTxValue('MSGTX_FORCE_EM')) !== 0) {
345  if (intval(HCU_array_key_value('sC', $p_hb_env)) != '2') {
346  $retAry['code'] = ($retAry['code'] == '000' ? "013" : $retAry['code']);
347  $retAry['severity'] = "ERROR";
348  $retAry['errors'][] = $p_mc->msg("Bad Email Flag");
349  //"Bad Email Flag"
350  }
351  }
352 
353  // ** Is the member required to validate/enter their user alias?
354  if ( ($p_hb_env['Fset2'] & $GLOBALS['CU2_ALIAS_REQ']) == $GLOBALS['CU2_ALIAS_REQ']) {
355  // CURRENTLY the cookie ONLY contains the ACTUAL member number, IT does not
356  // contain the useralias.
357  // A solution for now is to retrieve the mbrInfo record from cuusers.
358  // Evaluate the actual alias field,
359 
360  // ** LONG TERM SOLUTION
361  // * Currently this will only retrieve the record for credit union REQUIRING the MemberInfo
362  // * Need to add the login UserAlias to the cookie, such as Ca (Credit Union Alias).
363 
364  $mbrInfo = GetUserInfo($p_dbh, $p_hb_env, $p_mc, Array("user_id" => $p_hb_env['Uid'], "cu" => $p_hb_env['Cu']));
365  // ** if we were able to retrieve the member data
366  if ($mbrInfo['status']['code'] == '000') {
367  if (!Check_Member_UseAlias($mbrInfo['data']['cuusers_user_name'])) {
368  if (intval(HCU_array_key_value('sC', $p_hb_env)) != '2') {
369  $retAry['code'] = ($retAry['code'] == '000' ? "014" : $retAry['code']);
370  $retAry['severity'] = "ERROR";
371  $retAry['errors'][] = $p_mc->msg("Alias required");
372  }
373  }
374  }
375  }
376  // ** NOTHING HERE - SKIP
377 
378 
379  return $retAry;
380 }
381 /**
382  * This function will evaluate the username that is passed to it
383  * and determine if we compare the username or useralias in cuusers
384  * @param string p_username username to evaluate
385  * @return bool {True} -- The username will query useralias in the database {False} -- The username will query username in the cuusers tables (STANDARD)
386  *
387  * # NOT NEEDED FOR ODYSSEY - Everybody logs in with a username for Odyssey
388  */
389 function Check_Member_UseAlias($p_username) {
390  return preg_match("/\D/",$p_username);
391 }
392 
393 /**
394  * This verifies the security credentials of the 'device' and 'member' cookies.
395  * It will also set some of the GLOBAL values that are used throughout home banking,
396  *
397  * 4/17/17 Function validates the Ticket cookie and, if the Ticket passes,
398  * uses ticket values to add / updates values in hb_env array
399  * Nothing addresses the device cookie here - see hcuLogin for that?
400  *
401  * @param $hb_env array Reference to HB_ENV to allow easy changes to values
402  * @param $ticket string The session string from the ticket cookie or appcode
403  * @return array Associative array of results:
404  * [
405  * 'result'=>value, // 1 = ticket is valid, 0 = ticket failed
406  * 'resdesc'=>value, // failure reason or '' if ticket is valid
407  * ]
408  */
409 function Check_Member_Credentials (&$hb_env, $ticket) {
410  $retVal = Array("result"=>"", "resdesc"=>"");
411 
412  $debug = 0; // * when set, the retdesc will be logged
413 
414  $retVal['result'] = 1; // ** ASSUME SUCCESS --- PROVE FAILURE
415  // unset $chome so failed cookies will leave it blank.
416 
417  $returnArray = CheckSessionTicket($hb_env, $ticket);
418  // returns Array("result"=>array(), "resdesc"=>"");
419 
420  if ( !is_array( $returnArray['result'] ) ) {
421  // Pull username from ticket because we don't have it
422  $username = '';
423  if ($ticket) {
424  $matches = [];
425  if (preg_match('/Cn=(.+?)\b/', $ticket, $matches)) {
426  $username = $matches[1];
427  }
428  }
429  $config = FeatureGateConfig::GetInstance($hb_env['dbh']);
430  $logTicketCheckGate = new CreditUnionGate(CreditUnionGate::LOG_TICKET_CHECK_FEATURE, $config);
431  if ($logTicketCheckGate->WillPass($hb_env['cu'], ['username' => $username])) {
432  $hb_env['SYSENV']['logger']->info("[CheckCredentialsFailure] ticket=$ticket; resdesc={$returnArray['resdesc']}");
433  }
434  // THE SECURITY VERIFICATION FAILED!
435  $retVal['result'] = 0;
436  $retVal["resdesc"] = $returnArray["resdesc"];
437 
438  // do NOT set the GLOBAL VALUES FROM the cookie
439  // They will be REQUIRED TO LOGIN
440  if ($debug) {
441  syslog(LOG_ERR, $retVal['resdesc']);
442  }
443 
444  } else {
445  // ** VERIFICATION PASSED
446  // ** SET GLOBAL VALUES
447  $allowENV=array('Ctime'=>'Time','Ce'=>'Expires','Cu'=>'CU',
448  'Cn'=>'Login Member','Uid'=>'Login User ID','Clw'=>'Wait','Clu'=>'Last Update',
449  'Fplog'=>'Prior Login','Fflog'=>'Last Failed',
450  'Ffchg'=>'Force Change','Ffreset'=>'Force Reset','Ffremain'=>'Fails Remaining',
451  'Fmsg_tx'=>'Msg Status','Fset'=>'Flagset 1','Fset2'=>'Flagset 2',
452  'Fset3'=>'Flagset 3', 'Ca' => "Admin user (user hub member account list)",
453  'Fhdays'=>'History Days','Ml'=>'Email Address','platform'=>'Client platform',
454  'sC'=>'Security Challenge Status', 'testmenu'=>'Show Test Menu', 'sid'=>'Session Id');
455 
456  // ** SET THE VALUES IN THE HB_ENV array --
457  # $tarr is built (above) from ticket cookie; then
458  # values from $tarr which have keys present in $allowENV
459  # are used to add/update values in $p_hb_env
460  $hb_env = array_replace( $hb_env, array_intersect_key( $returnArray["result"], $allowENV ) );
461 
462  // ** For some values, there is a Cookie fieldname and there there is the actual
463  // * fieldname
464  // ['Clu'] == ['lastupdate']
465  $hb_env['lastupdate'] = $hb_env['Clu'];
466  }
467 
468  return $retVal;
469 }
470 
471 /**
472  * Check the passed in session string and check the hash for validity. Return the
473  * component parts in an array for the caller to use.
474  *
475  * Returns: result = false on failure, array if successful
476  */
477 function CheckSessionTicket( $pHBEnv, $sessionStr ) {
478  try {
479  $retVal = Array( "result" => array(), "resdesc" => "" );
480 
481  if ( empty($sessionStr) ) {
482  // No Ticket
483  throw new Exception( "Ticket not found {$pHBEnv['cu']}" );
484  }
485 
486  $tarr = array();
487  parse_str( $sessionStr, $tarr );
488 
489  $now = time();
490  if ( $tarr['Ce'] < $now ) {
491  // ticket has expired
492  throw new Exception( "{$tarr['Cu']}:{$tarr['Uid']} Ticket Expired {$tarr['Ce']} < $now" );
493  }
494 
495  if ( $pHBEnv['cu'] != "" &&
496  $pHBEnv['cu'] != $tarr['Cu'] ) {
497  // Different CU requested
498  throw new Exception( "{$tarr['Cu']}:{$tarr['Uid']} Ticket Switch cu from {$tarr['Cu']} to {$pHBEnv['cu']}" );
499  }
500 
501  // 2-Factor Ticket cookie
502  // ** Verify ALL expected pieces exist in cookie
503 
504  if (is_null($tarr['Ch']) || is_null($tarr['Cn']) || is_null($tarr['Uid']) ||
505  is_null($tarr['Ctime']) || is_null($tarr['Ce']) ||
506  is_null($tarr['Cu']) || is_null($tarr['Clw']) ||
507  is_null($tarr['Clu']) || is_null($tarr['Fplog']) || is_null($tarr['Fflog']) ||
508  is_null($tarr['Ffchg']) || is_null($tarr['Ffremain']) ||
509  is_null($tarr['Fmsg_tx']) || is_null($tarr['Fset']) ||
510  is_null($tarr['Fset2']) || is_null($tarr['Fhdays']) || is_null($tarr["Ca"]) ||
511  is_null($tarr['Fset3']) || is_null($tarr['Ml']) || is_null($tarr["platform"]) || is_null($tarr["sid"])) {
512  //Partial ticket, try again
513  throw new Exception( "{$tarr['Cu']}:{$tarr['Cn']} Partial Ticket" );
514  }
515 
516  $secret = $pHBEnv['secret'];
517 
518  // 2-Factor Ticket cookie
519  // ** Recreate the hash from ALL the pieces and verify they are the same
520  if ($tarr['Ch'] != MD5($secret .
521  MD5(join(':',
522  [
523  $secret,
524  $tarr['Ctime'], $tarr['Ce'], $tarr['Cu'], $tarr['Cn'], $tarr['Uid'],
525  $tarr['Clw'], urlencode(trim($tarr['Clu'])), urlencode(trim($tarr['Fplog'])), urlencode(trim($tarr['Fflog'])),
526  $tarr['Ffchg'], $tarr['Ffreset'],
527  $tarr['Ffremain'], $tarr['Fmsg_tx'], $tarr['Fset'],
528  $tarr['Fset2'], $tarr['Fset3'], $tarr['Fhdays'],
529  urlencode($tarr['Ml']), trim($tarr["Ca"]), trim($tarr["platform"]), $tarr["sid"]
530  ]
531  )))) {
532  // hash doesn't match, someone is hacking
533  throw new Exception( "{$tarr['Cu']}:{$tarr['Uid']} Ticket Hash Mismatch" );
534  }
535 
536  $retVal["result"] = $tarr;
537  } catch (Exception $err) {
538  $retVal["result"] = false;
539  $retVal["resdesc"] = $err->getMessage();
540  }
541 
542  // log every occurance EXCEPT missing cookie
543  if ( !empty($_COOKIE['Ticket']) ) {
544  // apache_note sets variables for web server logging. Used later to split
545  // web logfiles by credit union
546  // ONLY add the note when we have ticket values
547  apache_note("user_name","{$tarr['Cu']}:{$tarr['Uid']}");
548  }
549 
550  return $retVal;
551 } // end CheckSessionTicket
552 
553 /**
554  * MWS 7/14/2016
555  * Removed the SetReturnAddress Function
556  * Changed GetReturnAddress to ALWAYS return main_url, but removed the setcookie portion
557  * Always redirects to main login page - v2 has NEVER respected the return address cookie
558  */
559 function GetReturnAddress ($p_hb_env, $p_bolforce_setcookie = 0) {
560 
561  $return_address = $p_hb_env['loginpath'] . "/{$p_hb_env['defaultScript']}?{$p_hb_env['cuquery']}";
562 
563  return $return_address;
564 }
565 
566 function GetUserbyName($p_dbh, $cu, $username) {
567  $return_ary = array();
568  // ** QUERY THE USER INFORMATION
569  $sqluser = "SELECT cuuser.user_id as user_id, trim(cuuser.user_name) as user_name, trim(cuuser.passwd) as passwd,
570  forcechange as fchange, coalesce(forceremain,0) as fremain, failedremain, pwchange as pchange,
571  trim(email) as email, confidence, cuuser.group_id as cuuser_group_id,
572  lastlogin as llog, failedlogin as flog, coalesce(msg_tx,0) as msg_tx, userflags & " . GetUserFlagsValue('MEM_FORCE_RESET') . "::int4 as freset,
573  userflags, coalesce(challenge_quest_id,0) as savecqid,
574  coalesce(cuadmin.flagset,0) as flagset, coalesce(cuadmin.flagset2,0) as flagset2, coalesce(cuadmin.flagset3,0) as flagset3,
575  cuadmin.livewait, trim(cuadmin.lastupdate) as lastupdate, cuadmin.min_chlng_qst as min_chlng_qst,
576  cuadmin.pname, coalesce(histdays,0) as fhdays, coalesce(gracelimit,0) as grace, trmemomaxlen, mfaquest, primary_account, trim(cuadmin.cu) as cu
577 
578  FROM {$cu}user as cuuser
579  JOIN cuadmin on cuadmin.cu = '" . prep_save($cu) . "'
580  WHERE lower(cuuser.user_name) = '" . prep_save(strtolower($username)) . "' ";
581 
582  $mbr_sth = db_query($sqluser, $p_dbh);
583  if (db_num_rows($mbr_sth) == 1) {
584  $return_ary = db_fetch_assoc($mbr_sth,0);
585  $return_ary['rowfound'] = 1;
586  $mbrMfaQuest = HCU_MFADecode(HCU_JsonDecode($return_ary['mfaquest']));
587  $return_ary['savecqid'] = $mbrMfaQuest['challenge'];
588  $return_ary['chcount'] = $mbrMfaQuest['mfacount'];
589  $return_ary['authcode'] = $mbrMfaQuest['authcode'];
590  $return_ary['authexpires'] = $mbrMfaQuest['authexpires'];
591  $return_ary['mfadate'] = $mbrMfaQuest['mfadate'];
592 
593  $FORCEUPDATE = 0;
594  if ($return_ary['fchange'] == 'Y') {
595  $FORCEUPDATE += 1; #password
596  }
597 
598  if (($return_ary['msg_tx'] & GetMsgTxValue('MSGTX_FORCE_EM')) || $return_ary['email'] == '') {
599  $FORCEUPDATE += 2; # email
600  }
601  if (intval($return_ary['flagset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')) == 0 &&
602  ( $return_ary['freset'] == GetUserFlagsValue('MEM_FORCE_RESET') || $return_ary['chcount'] < $return_ary['min_chlng_qst'])
603  ) {
604  $FORCEUPDATE += 4; #challenge questions
605  }
606  if (($return_ary['flagset2'] & GetFlagsetValue('CU2_ALIAS_REQ')) && !Check_Member_UseAlias($return_ary['user_name'])) {
607  $FORCEUPDATE += 8;
608  }
609 
610 // if ( intval($return_ary['flagset3'] & $GLOBALS['CU3_MFA_AUTHCODE']) > 0 && $return_ary['freset'] == 2 ) {
611 // $FORCEUPDATE += 16; #phone numbers
612 // }
613  $return_ary['forceupdate'] = $FORCEUPDATE;
614 
615 
616  if ($return_ary['failedremain'] <= 0 || ( ($return_ary['forceupdate'] & 29) > 0 && $return_ary['fremain'] <= 0 )) {
617 
618  $return_ary['lockedacct'] = 1;
619  } else {
620  $return_ary['lockedacct'] = 0;
621  }
622 
623  } else {
624  $return_ary['rowfound'] = 0;
625  }
626 
627 
628 return $return_ary;
629 
630 }
631 
632 /*
633  * Function: BankingVerifyCredentials
634  *
635  * MWS 10/15/2015 -- New function for verifying credentials.
636  * Started from Check_Member_Login
637  * LEGACY VERIFCATION REMOVED
638  *
639  * Purpose: To check all components of a member login and return the status.
640  * this will leave it up to the calling script to correctly identify
641  * the problem and work appropriately.
642  * Parameters: Need to pass in the information that I have for the member..
643  * p_dbh - current database handle
644  * p_mode - This is the mode we are trying to evaluate -- This will
645  * help determine the proper message to return
646  * {
647  * ChkLegacy - Legacy evaluation, only username and password required
648  * ChkMember -
649  * ChkPass - Primarily Checking for Password
650  * ChkChallenge - Primarily Checking for Challenge Question
651  * ChkEmail - Primarily Checking For Email
652  * }
653  * NOTE p_hb_env are passed BY REFERENCE .. CHANGES WILL STICK
654  * p_hb_env - this is the GLOBAL HomeBanking Environment Array
655  * p_mc - This is the current selected language class
656  * pLoginType - type of platform logging in: DSK = desktop, MBL (default) = Mobile
657  *
658  * Returns: This will return a standard array with information about how the information
659  * checked out.
660  * ReturnArray
661  * retVal['status'] - Values
662  * 000 -- Authentication Complete, the user may be authenticated
663  * 010 -- username complete
664  * 020 -- email complete
665  * 030 -- challenge complete
666  * 040 -- password complete becomes 000
667  *
668  * ERRORS
669  * 100 -- UNEXPECTED ERROR -- FAIL ALTOGETEHR
670  * 110 -- username NOT entered
671  * 111 -- username failed
672  * 112 -- Account is LOCKED
673  * 120 -- email failed
674  * 130 -- challenge failed
675  * 135 -- authcode failed
676  * 140 -- Password Failed
677  *
678  * retVal['dispmsg'][] -- This will be an ARRAY of messages to display back to the user
679  * it will already be taking into account the current
680  * language requirements.
681  *
682  * retVal['nextstep'] -- This will direct the script on what is needed
683  * next. If the script can be setup directly , it will hopefully
684  * feel easier to identify problems.
685  * {
686  * StepLegacy -- We need the username / password screen, legacy DISPLAY
687  * StepMember -- We need to show the username screen 2Factor
688  * StepEmail -- We need the email
689  * StepChallenge -- We need to show challenge questions
690  * StepSecureAccess -- We need to send a secure access code
691  * StepPass -- We need the Password, which may also show the secret word
692  * StepNone -- This is used when we need to stop processing..
693  * StepFWDEmail -- Redirect to EMAIL VERIFICATION
694  * StepFWDPWD -- Redirect to Password Change
695  * StepFWDAlias -- Redirect to UserAlias, which is required
696  * StepFWDCHG -- Redirect to Challenge Questions
697  * }
698  */
699 function BankingVerifyCredentials($p_dbh, $p_mode, &$p_hb_env, $p_mc, $pLoginType = "MBL") {
700  /*
701  * If username is not entered and posted here, then this function returned an
702  * empty nextstep. Causing the upgrade home banking to display NO form.
703  * Defaulting the return to array to be on the Member screen should prevent that
704  * problem. Note foreseeing a problem with mobile
705  */
706  $retVal = Array("status" => "100", "dispmsg" => Array(), "nextstep" => "StepMember");
707 
708 
709  /*
710  * Process -- 2Factor
711  *
712  * 2Factor -- Many steps.. more than 2!
713  * -- Hoping a building block approach works
714  * 1 - Check username
715  * 2 - Check for device cookie
716  * a FOUND -- set the email, challenge questions? OR TRUST??
717  * b NOT FOUND
718  * 1 - Check for email
719  * 2 - Check for challenge questions
720  * 3 - Check for password
721  *
722  */
723 
724  // ** upd_mbr_force -- This value will be set WHEN the member is able to bypass
725  // challenge questions, upon force reset OR FIRST TIME LOGIN - THIS WILL
726  // Ensure they are sent to the set challenge screen.
727  // *
728  $upd_mbr_force = 0;
729 
730  // ** 2FACTOR VALIDATION
731  // ** First thing is First Check the username
732  $username = trim($p_hb_env['username']);
733  $cu = trim($p_hb_env['cu']);
734  // * check for no invalid characters and not empty
735  if (hcuCheckUsername($username)) {
736  // get the userrec and status info from the db
737  $userrec = GetUserbyName($p_dbh, $cu, $username);
738  if ( $userrec['rowfound'] != 1) {
739 
740  /*
741  * username was NOT FOUND
742  *
743  * For an invalid username the flow is
744  *
745  * ____________________________
746  * | |
747  * | ENTER USER NAME |
748  * |-----------\/--------------|
749  * | |
750  * | ENTER PASSWORD |
751  * | |
752  * |-----------\/--------------|
753  * | |
754  * | USER LOGIN W/ ERROR |
755  * |___________________________|
756  *
757  */
758 
759  // ** To be here we have a username -- so we first set the next step to 'StepPass'
760  $retVal['status'] = "030";
761  $retVal['dispmsg'][] = '';
762  $retVal['nextstep'] = 'StepPass';
763  // ** Next check to see if we have the password
764  if (strlen($p_hb_env['password']) > 0) {
765  // ** We have the password -- we assume they just entered password -
766  // -- invalid user so there is no need to query the database
767  // *** SEND BACK TO MEMBER SCREEN WITH ERROR ****
768 
769  // ** I don't see why we would track bogus username entries at this point
770 
771  $retVal['status'] = "140";
772  $retVal['dispmsg'][] = $p_mc->msg("Invalid Login Password");
773  $retVal['nextstep'] = "StepMember";
774 
775  }
776 
777  } else {
778  // make sure we got a row
779  // * SET KNOWN VALUES HERE ---
780  // * OTHERS WILL BE SET ON PASSWORD AUTHENTICATION
781  $p_hb_env['confidence'] = $userrec['confidence'];
782 
783  // Locked account? value set in GetUserbyName
784  if ($userrec['lockedacct']) {
785  apache_note("user_name", "{$cu}:_Fl_{$userrec['user_name']}/{$username}"); # this will capture same value twice - use UID or Primary Acct instead?
786 
787  $userMemReset = $userrec['flagset'] & GetFlagsetValue('CU_MEMRESET');
788  $userForceReset = $userrec['userflags'] & GetUserFlagsValue('MEM_FORCE_RESET');
789  $userFailedRemain = $userrec['failedremain'] >= 0;
790 
791  if ($userMemReset && !$userForceReset && $userFailedRemain) {
792  if ($pLoginType == "MBL") {
793  $resetlink = $p_hb_env['loginpath'] . "/resetpwd.prg?cu={$p_hb_env['cu']}&Flang={$p_hb_env['Flang']}";
794  } elseif ($pLoginType == "DSK") { // DSK
795  $resetlink = $p_hb_env['loginpath'] . "/hcuResetPwd.prg?cu={$p_hb_env['cu']}";
796  } else { // Apps "APP" iPhone or "ADA" android
797  ($resetlink = '');
798  }
799  $retVal['status'] = "112";
800  $retVal['dispmsg'][] = $p_mc->combo_msg('Account is Locked Reset', 0, '#link#', "$resetlink");
801  // ** what if I leave it up to the requesting script to replace the link??
802  $retVal['nextstep'] = "StepMember";
803  } else {
804  $retVal['status'] = "112";
805  $retVal['dispmsg'][] = $p_mc->msg('Account is Locked');
806  $retVal['nextstep'] = "StepMember";
807  // $err_string=$MC->msg('Account is Locked');
808  }
809  } else {
810  // check if 2factor cookie (device cookie) exists
811  // and content matches and force reset is not set
812  // * Match the 2 Factor Cookie
813  if (isValidDeviceCookie($cu, $userrec) || IsValidMammothDeviceCookie($p_hb_env, $cu, $userrec)) {
814  // ** USERNAME -- AUTHENTICATED
815  // ** 2 FACTOR COOKIE PRE ESTABLISHED -- AUTHENTICATED
816  // ** Need the Password Screen
817  // ** RESET the username here -- Above we may strip ZEROS from the beginning
818  // * or other characters. So reset here so the remaining screens have the
819  // * correct username value
820  $p_hb_env['username'] = $username;
821 
822  $retVal['status'] = "030";
823  $retVal['dispmsg'][] = ''; //$p_mc->msg('Please Enter Password');
824  $retVal['nextstep'] = "StepPass";
825 
826  // * ALSO -- At this time, we know email is okay. (because we matched the 2-factor cookie
827  // SO set the email to what is saved. member will NOT be reentering.
828  $p_hb_env['email'] = $userrec['email'];
829  } else {
830  // ** 2 Factor Challenge FAILED -- OR was FORCED TO RESET -- DO THAT HERE
831  // ** RESET the username here -- Above we may strip ZEROS from the beginning
832  // * or other characters. So reset here so the remaining screens have the
833  // * correct username value
834  $p_hb_env['username'] = $username;
835 
836  $retVal['status'] = "010";
837  $retVal['dispmsg'][] = ""; //$p_mc->msg('Additional Authentication');
838  $retVal['nextstep'] = "StepEmail";
839  }
840 
841  // ** IF Next Step is StepEmail.. BUT IF HAVE AN EMAIL, then I want to
842  // * verify that now and move forward to NEXT
843  if ($retVal['nextstep'] == 'StepEmail' && trim($p_hb_env['email']) != '') {
844 
845  // ** HAVE VALUES FROM ABOVE
846  // If there's a saved email and it doesn't match
847  $email = trim($p_hb_env['email']);
848  if (!isValidEmail($email, $userrec)) {
849  // Track Failure
850  // email failed
851  apache_note("user_name", "{$cu}:_Fm_{$userrec['user_name']}/{$username}");
852  // ** OLD $sth = db_query("select fail2factor('{$p_hb_env['cu']}','$saveuser',{$GLOBALS['MEM_LOGIN_FAILED_EMAIL']})", $p_dbh);
853  $sth = UpdateMemberFailedLogin($p_dbh, $p_hb_env['cu'], $userrec['user_name'], $GLOBALS['MEM_LOGIN_FAILED_EMAIL']);
854  TrackUserLogin($p_dbh, array('Cu' => $cu, 'Uid' => $userrec['user_id'], 'user_name' => $userrec['user_name']), $pLoginType, $GLOBALS['MEM_LOGIN_FAILED_EMAIL'], $_SERVER['REMOTE_ADDR'], array('UA' => $_SERVER['HTTP_USER_AGENT']));
855 
856  $retVal['status'] = "120";
857  // ** IF WE are here -- I want to ignore any previous messages. cleare out the dispmsg first WHY?
858  $retVal['dispmsg'] = Array();
859  $retVal['dispmsg'][] = $p_mc->msg('Invalid Login Email');
860  $retVal['nextstep'] = "StepMember";
861  } else {
862  $retVal['status'] = "020";
863  $retVal['dispmsg'][] = "";
864 // $p_hb_env['SYSENV']['logger'] -> info(__LINE__ . " userrec {$userrec['flagset3']} p_hb_env flagset3 {$p_hb_env['flagset3']} GLOBAL {$GLOBALS['CU3_MFA_AUTHCODE']}");
865 
866  if (intval($p_hb_env['flagset3'] & $GLOBALS['CU3_MFA_AUTHCODE'])) {
867  $retVal['nextstep'] = "StepSecureAccess";
868  } else {
869  $retVal['nextstep'] = "StepChallenge";
870  }
871  }
872  } elseif ($retVal['nextstep'] == 'StepEmail' && trim($p_hb_env['email']) == '') {
873  // ** EMAIL HAS NOT BEEN ENTERED -- INFORM THEM
874  $retVal['dispmsg'][] = $p_mc->msg('Additional Authentication');
875  }
876  // ** If Next Step is StepChallenge and I have challenge questions answered
877  //** Then answer that
878  // When the process has required Challenge, then this will have to be passed
879  // ** along no matter what. So what will happen is when the member
880  // ** finally logs in, it actually logs in everything
881 
882  if ($retVal['nextstep'] == "StepChallenge") {
883  if ($userrec['freset'] == 0 && $userrec['chcount'] > 0) {
884  if (HCU_array_key_exists("challengeresponses", $p_hb_env) && count($p_hb_env['challengeresponses']) > 0) {
885  if (isValidChallenge($userrec, $p_hb_env['challengeresponses'])) {
886  $retVal['status'] = "030";
887  $retVal['dispmsg'][] = ''; //$p_mc->msg('Please Enter Password');
888  $retVal['nextstep'] = "StepPass";
889  } else {
890  apache_note("user_name", "{$cu}:_Fq_{$userrec['user_name']}/{$username}");
891  $sth = UpdateMemberFailedLogin($p_dbh, $cu, $userrec['user_name'], $GLOBALS['MEM_LOGIN_FAILED_QST']);
892  TrackUserLogin($p_dbh, array('Cu' => $cu, 'Uid' => $userrec['user_id'], 'user_name' => $userrec['user_name']), $pLoginType, $GLOBALS['MEM_LOGIN_FAILED_QST'], $_SERVER['REMOTE_ADDR'], array('UA' => $_SERVER['HTTP_USER_AGENT']));
893 
894  $retVal['status'] = "130";
895  $retVal['dispmsg'][] = $p_mc->msg('Invalid Login Challenge');
896  $retVal['nextstep'] = "StepMember";
897  }
898  }
899  } elseif ($userrec['chcount'] == 0 || $userrec['freset'] == GetUserFlagsValue('MEM_FORCE_RESET')) {
900  // ** IF NO QUESTIONS WERE FOUND SKIP StepChallenege
901  // * WE WILL SKIP CHALLENGE QUESTIONS -- SEND THEM TO PASSWORD
902  $retVal['nextstep'] = "StepPass";
903  $upd_mbr_force = 1;
904  }
905  }
906  if ($retVal['nextstep'] == "StepSecureAccess") {
907  if ($userrec['freset'] == GetUserFlagsValue('MEM_FORCE_RESET')) {
908  /*
909  * IF Force Security Reset is then skip to password screen.
910  */
911  $retVal['nextstep'] = "StepPass";
912  } else {
913  if (!empty($p_hb_env['authcode'])) {
914  if (isValidAuthcode($userrec, $p_hb_env['authcode'])) {
915  $retVal['status'] = "030";
916  $retVal['dispmsg'][] = ''; //$p_mc->msg('Please Enter Password');
917  $retVal['nextstep'] = "StepPass";
918  } else {
919  apache_note("user_name", "{$cu}:_Fq_{$userrec['user_name']}/{$username}");
920  $sth = UpdateMemberFailedLogin($p_dbh, $cu, $userrec['user_name'], $GLOBALS['MEM_LOGIN_FAILED_SAC']);
921  TrackUserLogin($p_dbh, array('Cu' => $cu, 'Uid' => $userrec['user_id'], 'user_name' => $userrec['user_name']), $pLoginType, $GLOBALS['MEM_LOGIN_FAILED_SAC'], $_SERVER['REMOTE_ADDR'], array('UA' => $_SERVER['HTTP_USER_AGENT']));
922 
923  $retVal['status'] = "135";
924  $retVal['dispmsg'][] = $p_mc->msg('Invalid Login Challenge');
925  $retVal['nextstep'] = "StepMember";
926  }
927  }
928  }
929  }
930 
931  // ** If Next Step is StepPass and I have a password,
932  // ** Validate the password
933  if (strlen($p_hb_env['password']) > 0 && $retVal['nextstep'] == 'StepPass') {
934  /*
935  * Mammoth,
936  * if password len at least 4 char, and live, and username all digits, and
937  * (userrec['rowfound']=0 or userrec['passwd']=='NULL PASSWORD')
938  * try to get a member record from the core
939  *
940  * Odyssey
941  * login will only be possible for existing user / member records
942  * Auto-activating from the core by matching pin will still be
943  * offered, but handled separately, & may be controlled by monitor
944  * settings (activate by MIR, activate by PIN, no auto activate, etc)
945  */
946 # if (isValidPass($userrec,$p_hb_env['password'])) {
947  $minLenPassword = (strlen(trim($p_hb_env['password'])) < 4 ? false : true);
948 
949  if ($minLenPassword && $userrec['rowfound'] > 0) {
950 
951  $mbrMfaQuest = HCU_MFADecode(HCU_JsonDecode($userrec['mfaquest']));
952 
953  # why are we matching email here?
954  if ((strtolower(trim($userrec['email'])) == strtolower(trim($p_hb_env['email'])) || trim($userrec['email']) == '') && password_verify($p_hb_env['password'], $userrec['passwd'])) {
955  // SUCCESSFUL PASSWORD MATCH
956  //
957 
958  $freset = (is_null($userrec['freset']) ? 0 : $userrec['freset']);
959 // $pchange = (is_null($userrec['pchange']) ? date('Ymd') : $userrec['pchange']);
960  // ** SET THE CORRECT VALUES FOR MEMBER ENVIRONMENT
961  $p_hb_env['Uid'] = $userrec['user_id'];
962  $p_hb_env['Cn'] = $userrec['user_name'];
963  $p_hb_env['Ce'] = time() + $p_hb_env['SYSENV']['ticket']['expires'];
964  $p_hb_env['Clw'] = $userrec['livewait'];
965  $p_hb_env['Clu'] = (empty($userrec['lastupdate']) ? $p_mc->msg("Unknown") : urlencode(trim($userrec['lastupdate'])));
966  $p_hb_env['lastupdate'] = (empty($userrec['lastupdate']) ? "Unknown" : urlencode(trim($userrec['lastupdate'])));
967  $p_hb_env['Fplog'] = (empty($userrec['llog']) ? $p_mc->msg("None") : urlencode(trim($userrec['llog'])));
968  $p_hb_env['Fflog'] = (empty($userrec['flog']) ? $p_mc->msg("None") : urlencode(trim($userrec['flog'])));
969  $p_hb_env['Ffchg'] = (is_null($userrec['fchange']) ? 'N' : $userrec['fchange']);
970  $p_hb_env['Ffremain'] = (is_null($userrec['fremain']) || $userrec['fremain'] == 0 ? $userrec['grace'] : $userrec['fremain']);
971  $p_hb_env['Fmsg_tx'] = $userrec['msg_tx'];
972  $p_hb_env['Fset'] = $userrec['flagset'];
973  $p_hb_env['Fset2'] = $userrec['flagset2'];
974  $p_hb_env['Fset3'] = $userrec['flagset3'];
975  $p_hb_env['Fhdays'] = $userrec['fhdays'];
976  $p_hb_env['Ml'] = $userrec['email'];
977  $p_hb_env['Ffreset'] = $userrec['freset'];
978 
979  // * Add this for use later possibly in 2Factor cookie
980  $p_hb_env['savepass'] = $userrec['passwd'];
981  $p_hb_env['savemail'] = $userrec['email'];
982 
983  // Set the sid at this point. It is not updated from this point.
984  $p_hb_env["sid"] = strval(time());
985 
986  // make sure the force security is set so member can't avoid 2-factor forever
987 
988  $f2sql = '';
989  /*
990  * UPDATE USERS FLAGS / EMAIL
991  *
992  * IF upd_mbr_force is set then set the forceremain to the Credit Union Value
993  * ELSE
994  * SET EMAIL - if it was blank and we now have a value
995  * - Why update the email if it's already set? In upgrade it always set the email
996  * - mws 3/16/2016 - if they got here, they have a successful login if email isn't
997  * blank then they must have already matched the email -- so why save
998  *
999  * Record Audit trail ONLY if email was changed FROM blank
1000  *
1001  */
1002 
1003  // ** Setup the user update array if Member was Forced to Challenge OR email was updated
1004 
1005  $updateEmail = (trim($userrec['email']) == '' && trim($p_hb_env['email']) > '');
1006 
1007  if ($upd_mbr_force || $updateEmail) {
1008  $updTable = array('user' => array(
1009  array(
1010  "_action" => "update",
1011  "user_id" => $p_hb_env['Uid']
1012  )
1013  )
1014  );
1015  if ($upd_mbr_force) {
1016  // * SET the MEM_FORCE_RESET bit in userflags
1017  // * If value is NULL then set to MEM_FORCE_RESET
1018  $valueUserFlag = ($userrec['userflags'] == '' ? $GLOBALS['MEM_FORCE_RESET'] : ($userrec['userflags'] | $GLOBALS['MEM_FORCE_RESET']));
1019  $updTable['user'][0]['userflags'] = $valueUserFlag;
1020  $updTable['user'][0]['forceremain'] = $userrec['fremain'];
1021  $freset |= $GLOBALS['MEM_FORCE_RESET'];
1022  }
1023  if ($updateEmail) {
1024  $updTable['user'][0]['email'] = trim($p_hb_env['email']);
1025  }
1026  // * Update the records -- ONLY SAVE AUDIT RECORD IF UPDATING EMAIL
1027  $updateResults = DataUserTableUpdate($p_dbh, $p_hb_env, $p_mc, $updTable, $userrec['user_id'], 'U_UPD', $pLoginType, $p_hb_env['currentscript'], 'U', ($upd_mbr_force ? "Force Mbr Update / " : "") . ($updateEmail ? "Set Email" : ""), $userrec['user_name'], $p_hb_env['email'], $p_hb_env['remoteIp'], !$updateEmail);
1028 // $userupd = GetUserbyName($p_dbh, $cu, $username);
1029 // $p_hb_env['SYSENV']['logger'] -> info(__LINE__ . " userrec " . print_r($userupd,true));
1030  }
1031  $p_hb_env['Ffreset'] = $userrec['freset'];
1032 
1033  // SET THE MEMBER COOKIE
1034  $now = time();
1035 
1036  apache_note("user_name", "{$cu}:{$username}");
1037 
1038  $baseCookie = BuildBaseSessionTicket( $p_hb_env );
1039  $mycookie = "Ctime=$now&Cn={$userrec['user_name']}&Uid={$userrec['user_id']}&Ml=" . urlencode($userrec['email']) . "&Ca=";
1040 
1041  // ** Check for testmenu param -- If set to 1, then I want to add it to cookie
1042  if (intval(HCU_array_key_value('testmenu', $p_hb_env['HCUPOST'])) == 1) {
1043  $mycookie .= "&testmenu=1";
1044  }
1045  // ** SET THE COOKIE
1046  // * *OLD FAILS SetTicket($p_hb_env, "", $mycookie);
1047  SetTicket($p_hb_env, $baseCookie, $mycookie);
1048 
1049  // ** Log success
1050 // $must = ($userrec['freset'] == 2 ? 'Y' : $p_hb_env['Ffchg']);
1051  // ** BY BEING HERE -- I know that if the CU is set to ReadOnly
1052  // ** and the script allows ReadOnly access, then at this point
1053  // ** I need to check real offline status -- So I will set a temp
1054  // ** flag to capture script flag setting for allowReadonly, and
1055  // ** reset the allowReadonly to false
1056  // ** Then call hcu_checkoffilne -- If it comes back true, then
1057  // ** based on the previous hcu_offline being true and this offline
1058  // ** being false, that can only happen if the difference is the
1059  // ** readonly status -- AND such, I do NOT want to update the
1060  // ** forceremain or pwchange in logtrack. So I will set values
1061  // ** to prevent those flags from being set
1062  // ** fchange/pchange affect the logtrack function
1063  // * if fchange is Y - then the forcremain is decremented
1064  // * if fchange is N - and the CU require members to update stale pwd's
1065  // * the forcechange is set
1066  // ** I propose, if the system is in READONLY mode
1067  // ** To set the fchange to 'N'
1068  // ** set the pchange to now()
1069  // ** This will prevent the two methods from being true in the log track function
1070  // ** and the forceremain will not be decremented or activated
1071  // ** HB_ENV['allowReadonly'] is set in the previous script, and would have stopped
1072  // ** if it was NOT readonly
1073  $tempReadonly = $p_hb_env['allowReadonly'];
1074  $p_hb_env['allowReadonly'] = false;
1075 
1076  if (!hcu_checkOffline($p_dbh, $p_hb_env)) {
1077  $funcFchange = "N";
1078  // ** By passing in tomorrows date, it should prevent the password stale comparison
1079  // * from returning true. Sending the log track into the "final" else statement
1080  $funcPchange = date('Y-m-d', mktime(0, 0, 0, date("m"), date("d") + 1, date("Y")));
1081  } else {
1082  $funcFchange = ($userrec['freset'] == 2 ? 'Y' : $p_hb_env['Ffchg']);
1083  $funcPchange = (is_null($userrec['pchange']) ? date('Ymd') : $userrec['pchange']);
1084  }
1085  $p_hb_env['allowReadonly'] = $tempReadonly;
1086 
1087  // ** PREPARE THE mfaquest --
1088  // The challenge key must be reset to 0 for SUCCESSFUL LOGINS
1089  // and the sac code and expiration must be reset empty for SUCCESSFUL LOGINS
1090  $mbrMfaQuest['challenge'] = 0;
1091  $mbrMfaQuest['authcode']='';
1092  $mbrMfaQuest['authexpires']='';
1093 
1094  // $sth = db_query("select hcumbrlogintrack('{$p_hb_env['cu']}','" . prep_save($saveuser) . "','$funcFchange','$funcPchange','{$pLoginType}')", $p_dbh);
1095 // $p_hb_env['SYSENV']['logger'] -> info(__LINE__ . " UpdateMemberLoginTrack (dbh, {$p_hb_env['cu']}, {$username}, {$funcFchange}, {$funcPchange}, {$pLoginType}, {$mbrMfaQuest})");
1096  $sth = UpdateMemberLoginTrack($p_dbh, $p_hb_env['cu'], $username, $funcFchange, $funcPchange, $pLoginType, $mbrMfaQuest);
1097  TrackUserLogin($p_dbh, array('Cu' => $p_hb_env['cu'], 'Uid' => $userrec['user_id'], 'user_name' => $username), $pLoginType, 0, $_SERVER['REMOTE_ADDR'], array('UA' => $_SERVER['HTTP_USER_AGENT']));
1098 
1099  // ** If chkSecure is selected -- Then
1100  if ($p_hb_env['HCUPOST']['chksecure'] == 'Y') {
1101  SetDeviceCookie($p_hb_env, $userrec);
1102  }
1103 
1104  // ** Always force to the next screen, this will handle any redirection
1105  $retVal['status'] = "000";
1106  $retVal['dispmsg'][] = "";
1107  $retVal['nextstep'] = "StepNone";
1108  } else {
1109  // * Track failure
1110  // ** password failed
1111  apache_note("user_name", "{$cu}:_Fp_{$username}");
1112  $sth = UpdateMemberFailedLogin($p_dbh, $cu, $userrec['user_name'], $GLOBALS['MEM_LOGIN_FAILED_PWD']);
1113  TrackUserLogin($p_dbh, array('Cu' => $cu, 'Uid' => $userrec['user_id'], 'user_name' => $userrec['user_name']), $pLoginType, $GLOBALS['MEM_LOGIN_FAILED_PWD'], $_SERVER['REMOTE_ADDR'], array('UA' => $_SERVER['HTTP_USER_AGENT']));
1114  $retVal['status'] = "140";
1115  $retVal['dispmsg'][] = $p_mc->msg("Invalid Login Password");
1116  $retVal['nextstep'] = "StepMember";
1117  }
1118  } else {
1119  // * Track failure (no user record or min password length fail)
1120  $logFlag = $minLenPassword ? "Fn" : "Fp";
1121  $logUID = $minLenPassword ? 0 : $userrec['user_id'];
1122  # need a valid code for invalid user name?
1123  $logCode = $minLenPassword ? $GLOBALS['MEM_LOGIN_FAILED_PWD'] : 0;
1124  apache_note("user_name", "{$cu}:_{$logFlag}_{$username}");
1125  $sth = UpdateMemberFailedLogin($p_dbh, $cu, $username, $logCode);
1126  TrackUserLogin($p_dbh, array('Cu' => $cu, 'uid' => $logUID, 'user_name' => $username), $pLoginType, $logCode, $_SERVER['REMOTE_ADDR'], array('UA' => $_SERVER['HTTP_USER_AGENT']));
1127  // $err_string = $MC->msg("Invalid Login Password");
1128  $retVal['status'] = "140";
1129  $retVal['dispmsg'][] = $p_mc->msg("Invalid Login Password");
1130  $retVal['nextstep'] = "StepMember";
1131  }
1132  }
1133  }
1134  } // rowfound
1135  } else {
1136  /* INVALID USER NAME ENTERED -- most likely a space was entered, but includes other characters like ; or , refer to hcuCheckUsername*/
1137  // ** To be here we have a username -- so we first set the next step to 'StepPass'
1138  $retVal['status'] = "030";
1139  $retVal['dispmsg'][] = '';
1140  $retVal['nextstep'] = 'StepPass';
1141  // ** Next check to see if we have the password
1142  if (strlen($p_hb_env['password']) > 0) {
1143  // ** We have the password -- we assume they just entered password -
1144  // -- invalid user so there is no need to query the database
1145  // *** SEND BACK TO MEMBER SCREEN WITH ERROR ****
1146 
1147  // ** I don't see why we would track bogus username entries at this point
1148 
1149  $retVal['status'] = "140";
1150  $retVal['dispmsg'][] = $p_mc->msg("Invalid Login Password");
1151  $retVal['nextstep'] = "StepMember";
1152 
1153  }
1154  } // hcuCheckUserName
1155  return $retVal;
1156 }
1157 
1158 /**
1159  *
1160  * @param string $email email entered by member
1161  * @param array $userrec result of GetUserbyName or similar
1162  * @return boolean
1163  *
1164  * returns true if email is set in db and entered email matches,
1165  * or if email in db is empty and entered email has valid format
1166  */
1167 function isValidEmail($email, $userrec) {
1168  if ((trim($userrec['email']) > '' && strtolower(trim($userrec['email'])) == strtolower(trim($email))) ||
1169  (trim($userrec['email']) == '' && validateEmail($email))) {
1170  $return_val = true;
1171  } else {
1172  $return_val = false;
1173  }
1174  return $return_val;
1175 }
1176 function activateWithCorePin($userrec, $p_hb_env) {
1177  $insflag = ($userrec['rowfound'] == 0 ? 1 : 0);
1178 // password given and username is all digits and
1179 // no cuuser record or NULL PASSWORD rec
1180 // try to get user info from the core cu
1181 // run the query again if the fetch was successful
1182 
1183 /*
1184  * check to see if the username is all digits - indicating member account number
1185  * check to see if that account is already a primaryAccout - if so, not eligible to activate
1186  * add default group records, username records, etc. as needed
1187  */
1188  list ($status, $asofdate, $reason) = fetch_user($cu, $username, $p_hb_env['password'], $insflag);
1189  if ($status == "100" || $status == "101") {
1190  // ** Re Retrieve the user record
1191  $userrec = GetUserbyName($p_dbh, $cu, $username);
1192 // $p_hb_env['SYSENV']['logger'] -> info(__LINE__ . "GetUserbyName returns: " . print_r($userrec,true));
1193  } elseif ($status == "200" || $status == "201" || $status == "202") {
1194  $retVal['status'] = "140";
1195  $retVal['dispmsg'][] = $p_mc->msg("CU System Unavailable");
1196  $retVal['nextstep'] = "StepMember";
1197  } else {
1198  // don't return the core status here
1199  $retVal['status'] = "140";
1200  $retVal['nextstep'] = "StepMember";
1201  }
1202  return $retVal;
1203 }
1204 
1205 /**
1206  *
1207  * @param array $userrec user info from db as returned by GetUserbyName
1208  * @param array $challengeresponses posted responses from the member
1209  * @return boolean returns true if posted responses match db
1210  * will also return true if force reset is on
1211  * or if member has zero challenge questions defined
1212  */
1213 function isValidChallenge($userrec, $challengeresponses) {
1214 
1215  // * Create the MFA Quest Array
1216  $aryMfaQuest = HCU_JsonDecode($userrec['mfaquest']);
1217  $mbrMfaQuest = HCU_MFADecode($aryMfaQuest);
1218  $savecqid = $mbrMfaQuest['challenge'];
1219  $chcount = $mbrMfaQuest['mfacount'];
1220  // If force reset is NOT set and we HAVE some challenge questions
1221  // ** having problems with this statement...
1222  // freset is set then we will be LATER setting challenge questions,
1223  // * no need to set right now
1224  $fail = 0;
1225 
1226  // ** VALIDATE ANSWERS IF THERE ARE ANY
1227  if (count($challengeresponses) > 0) {
1228 
1229  /*
1230  * VALIDATE ANSWERS
1231  * IF savecqid has a value and CU configured for only one answer,
1232  * then ONLY validate that answer
1233  *
1234  * chcount being greater than one MEANS the aryMfaQuest array contains
1235  * an 'answers' key, is an array and has at least one value
1236  */
1237 
1238  /*
1239  * I think looking at $mbrMfaQuest (result of HCU_MFADecode) may work
1240  * without having examine Random challenge setting as it always returns
1241  * an array
1242  */
1243  $aryMfaAnswers = $aryMfaQuest['answers']; // Set the array
1244  if (($userrec['flagset2'] & $GLOBALS['CU2_RANDOM_CHAL']) == $GLOBALS['CU2_RANDOM_CHAL'] && $mbrMfaQuest['challenge'] > 0) {
1245  // * Set a single key array
1246  $mfaAnswerIdx = Array($mbrMfaQuest['challenge']);
1247  } else {
1248  // * Set an array of all the keys in answers
1249  $mfaAnswerIdx = array_keys($aryMfaAnswers);
1250  }
1251 
1252  // ** NOW EVALUATE THE ANSWERS
1253  foreach (array_intersect_key($aryMfaAnswers, array_flip($mfaAnswerIdx)) as $qid => $qanswer) {
1254  $response = "qid$qid";
1255  if (strtolower(trim($qanswer)) != strtolower(trim($challengeresponses[$response]))) {
1256  $fail++;
1257  }
1258  }
1259  }
1260  if ($fail == 0) {
1261  $retVal = true;
1262  } else {
1263  $retVal = false;
1264  }
1265  return $retVal;
1266 }
1267 /**
1268  *
1269  * @param array $userrec user info from db as returned by GetUserbyName
1270  * @param string $authcode authcode entered by member
1271  * @return boolean returns true if posted response match db
1272  */
1273 function isValidAuthcode($userrec, $authcode) {
1274 
1275  // * Create the MFA Quest Array
1276  $aryMfaQuest = HCU_JsonDecode($userrec['mfaquest']);
1277  $mbrMfaQuest = HCU_MFADecode($aryMfaQuest);
1278 
1279  // ** VALIDATE AuthCode IF it hasn't expired
1280  if ( $mbrMfaQuest['authexpires'] > time() && trim($mbrMfaQuest['authcode']) > '' &&
1281  strtolower($authcode) == strtolower($mbrMfaQuest['authcode'])
1282  ) {
1283  $retVal = true;
1284  } else {
1285  $retVal = false;
1286  }
1287  return $retVal;
1288 }
1289 
1290 /**
1291  *
1292  * @param string $cu client code
1293  * @param array $userrec result of GetUserbyName or similar
1294  * @return boolean
1295  *
1296  * returns true if device cookie content matches and Force Reset is not set,
1297  * otherwise returns false.
1298  */
1299 function isValidDeviceCookie($cu, $userrec) {
1300 
1301 
1302  $mfaMode = (intval($userrec['flagset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')));
1303  //$mfaDate = HCU_array_key_exists("mfadate", $userrec) ? $userrec['mfadate'] : "";
1304  $mfaDate = HCU_array_key_value("mfadate", $userrec);
1305 
1306  $cookieParams = array ( "cu" => $cu,
1307  "user_name" => $userrec['user_name'],
1308  "saved_pass" => $userrec['passwd'],
1309  "saved_email" => $userrec['email'],
1310  "saved_confidence" => $userrec['confidence'],
1311  "mfa_mode" => $mfaMode,
1312  "mfa_date" => $mfaDate,
1313  "persists_time" => 0 // are not actually creating a cookie for use
1314  );
1315 
1316  $cookieInfo = CreateDeviceCookie( $cookieParams);
1317 
1318  if (!empty($_COOKIE[$cookieInfo["name"]]) && ($cookieInfo["content"] == $_COOKIE[$cookieInfo["name"]]) && ($userrec['freset'] != GetUserFlagsValue('MEM_FORCE_RESET'))) {
1319  $return_val = true;
1320  } else {
1321  $return_val = false;
1322  }
1323  return $return_val;
1324 }
1325 
1326 /**
1327  * If allowed, recognize if it is a valid Mammoth device cookie and replace it with a
1328  * new device cookie. Because Mammoth is account based and Odyssey is user based get
1329  * all the accounts related to the user because we don't know which one was the Mammoth
1330  * one.
1331  *
1332  * @param array $pHBEnv Environment
1333  * @param string $pCu CU code
1334  * @param array $pUserRec Result of GetUserbyName or similar
1335  * @return boolean
1336  *
1337  * Returns true if Mammoth device cookie is allowed AND have valid contents
1338  * and Force Reset is not set,
1339  * otherwise returns false.
1340  */
1341 function IsValidMammothDeviceCookie( $pHBEnv, $pCu, $pUserRec ) {
1342  $retVal = false; // assume not valid
1343 
1344  // check if allowed
1345  if ( ($pHBEnv["flagset3"] & GetFlagsetValue("CU3_ALLOW_COOKIE_MIGRATION")) > 0 &&
1346  $pUserRec['freset'] != GetUserFlagsValue('MEM_FORCE_RESET') ) {
1347  // get the possible account numbers
1348  $sql = "SELECT DISTINCT ua.accountnumber
1349  FROM {$pCu}user u
1350  INNER JOIN {$pCu}useraccounts ua on ua.user_id = u.user_id
1351  WHERE u.user_name ilike '{$pHBEnv["username"]}'";
1352 
1353  $rs = db_query( $sql, $pHBEnv["dbh"] );
1354 
1355  $row = 0;
1356  while ( $aRow = db_fetch_array( $rs, $row++ ) ) {
1357  $thisAccount = $aRow["accountnumber"];
1358  $mammothCookieName = Return2FactorName( $pHBEnv['cu'], $pHBEnv['2factorkey'], trim($thisAccount) );
1359 
1360  if ( isset( $_COOKIE[$mammothCookieName] ) ) {
1361  // check the contents
1362  $mammothCookieContent = sha1( trim($pUserRec["passwd"]) . trim($pUserRec["email"]) . trim($pUserRec["confidence"]) );
1363 
1364  $retVal = $mammothCookieContent == $_COOKIE[$mammothCookieName];
1365 
1366  // test if we are going to say it's valid
1367  if ( $retVal ) {
1368  // we need to set up some variables the new device cookie will need
1369  $pHBEnv["Cn"] = $pUserRec['user_name'];
1370  $pHBEnv["Fset3"] = $pUserRec['flagset3'];
1371  $pHBEnv["savepass"] = trim( $pUserRec["passwd"] );
1372  $pHBEnv["savemail"] = trim( $pUserRec["email"] );
1373 
1374  // remove the old one first in case the cookie names are the same
1375  $inThePast = time() - 3600 * 24;
1376  HCU_setcookie_env( $pHBEnv['SYSENV'], $mammothCookieName, "", $inThePast );
1377 
1378  // replace the old device cookie with a new one
1379  SetDeviceCookie( $pHBEnv, $pUserRec);
1380  }
1381 
1382  // exit the loop
1383  break;
1384  }
1385 
1386  }
1387  }
1388 
1389  return $retVal;
1390 } // end IsValidMammothDeviceCookie
1391 
1392 
1393 /*
1394  * Function: CreateDeviceCookie
1395  * Purpose: Create the 2Factor Device cookie so it can be used by browser and apps
1396  *
1397  * $param array pCookieParams --
1398  * string cu -- Credit union name (UPPER CASE)
1399  * string user_name -- username
1400  * string saved_pass -- saved password
1401  * string saved_email -- saved email
1402  * string saved_confidence -- saved confidence word
1403  * integer mfa_mode -- integer value of flag if AuthCode or not
1404  * string mfa_date -- The date associated with the last mfa update. Can
1405  * be obtained from the function GetUserByName
1406  * integer persists_time -- how long (in seconds) the device cookie persists
1407  *
1408  * @return cookie name, cookie content, and expire time as an array
1409  */
1410 function CreateDeviceCookie( $pCookieParams) {
1411  $cookieName = Return2FactorName($pCookieParams["cu"], Get2FactorKeyString(), trim($pCookieParams["user_name"]));
1412 
1413  $cookieContent = hash_hmac('sha384',GetDeviceCookieContentString(),trim($pCookieParams["saved_pass"]) . trim(strtolower($pCookieParams["saved_email"])) . trim(strtolower($pCookieParams["saved_confidence"])) . $pCookieParams["mfa_mode"] . $pCookieParams["mfa_date"]);
1414 
1415  $expire = time() + $pCookieParams["persists_time"];
1416 
1417  return array( "name" => $cookieName, "content" => $cookieContent, "expire" => $expire);
1418 }
1419 
1420 /*
1421  * Function: SetDeviceCookie
1422  * Purpose: To manage the 2Factor Device cookie -- At this time it is Browser based, but
1423  * returns the cookie info for other environments to use.
1424  *
1425  * @param array p_hb_env -- The current HB_ENV value
1426  * @param array pUserRec -- An array of User Record values. Can be obtained from the function GetUserByName
1427  * [mfadate] - The date associated with the last mfa update
1428  * @return array cookieInfo -- name, content, expire
1429  */
1430 function SetDeviceCookie($p_hb_env, $pUserRec) {
1431 
1432  $now = time();
1433 
1434  if (sizeof($_COOKIE) > 6
1435  && !preg_match("/^199.184.207/",$_SERVER['REMOTE_ADDR'])
1436  && !preg_match("/^192.168/",$_SERVER['REMOTE_ADDR'])) {
1437 
1438  $emsg = "{$_SERVER['REMOTE_ADDR']} {$p_hb_env['cu']}:{$p_hb_env['Cn']} " . date('Y-m-d H:i:s') . " " . sizeof($_COOKIE) . " Cookies";
1439  $p_hb_env['SYSENV']['logger']->warning($emsg);
1440  }
1441 
1442 
1443  // ** Make sure the COOKIES ARE KEPT TO MINIMUM FOR the browser, these add to
1444  // * size of packet being sent back to server from client
1445 
1446  if (sizeof($_COOKIE) > 23) {
1447  reset ($_COOKIE);
1448  $persists = ($now - 3600);
1449  foreach ($_COOKIE as $cookiename => $cookiecontent) {
1450  if (!preg_match("/^(Tx_mURI|Ticket|webconnect)/",$cookiename)) {
1451  HCU_setcookie_env($p_hb_env['SYSENV'], $cookiename, "", $persists);
1452  }
1453  }
1454  }
1455 
1456  // Get the MFA mode (authcode or not) flag
1457  $mfaMode = (intval($p_hb_env['Fset3'] & GetFlagsetValue('CU3_MFA_AUTHCODE')));
1458 
1459  // Get the mfadate from the UserRec
1460  $mfaDate = HCU_array_key_value("mfadate", $pUserRec);
1461 
1462  $cookieParams = array ( "cu" => $p_hb_env["cu"],
1463  "user_name" => $p_hb_env["Cn"],
1464  "saved_pass" => $p_hb_env["savepass"],
1465  "saved_email" => $p_hb_env['savemail'],
1466  "saved_confidence" => $p_hb_env['confidence'],
1467  "mfa_mode" => $mfaMode,
1468  "mfa_date" => $mfaDate,
1469  "persists_time" => $p_hb_env['SYSENV']['ticket']['persists']
1470  );
1471 
1472  $cookieInfo = CreateDeviceCookie( $cookieParams);
1473 
1474  // OLD REMOVE setcookie($cookiename, $cookiecontent, $persists, "/", $p_hb_env['TicketDomain'], 1);
1475  HCU_setcookie_env($p_hb_env['SYSENV'], $cookieInfo["name"], $cookieInfo["content"], $cookieInfo["expire"]);
1476 
1477  return $cookieInfo;
1478 }
1479 
1480 /**
1481  * TrackUserLogin
1482  * Write a user login tracking record
1483  * @param integer $p_dbh - current database handle
1484  * @param array $p_hb_env - current HB_ENV array (expecting Cu, user_id, user_name)
1485  * @param string $pLoginType - platform used: CLS, DSK, MBL, APP, ADA
1486  * @param integer $status - result code for login status
1487  * 0 = successful login
1488  * $MEM_LOGIN_FAILED_EMAIL = 4;
1489  * $MEM_LOGIN_FAILED_QST = 8;
1490  * $MEM_LOGIN_FAILED_PWD = 16;
1491  * $MEM_LOGIN_FAILED_ALIAS = 64;
1492  * @param string $p_ipaddr - remote ip address
1493  * will be cast as inet for insert
1494  * @param array $p_meta - content for metadata
1495 
1496  * @return nothing returned
1497  */
1498 function TrackUserLogin($p_dbh, $p_hb_env, $pLoginType, $status, $p_ipaddr, $p_meta) {
1499 // if ($status == 0) { # if we need to turn off failure logging
1500  /*
1501  * only add a tracking record if we have a valid Uid. Tracking failures for
1502  * non-existent login id may fill the table surprisingly fast, especially
1503  * if someone is deliberately hacking. These attempts should still be in
1504  * the web log if we had to find them
1505  */
1506  if (!empty($p_hb_env['Uid'])) {
1507  $Cu = $p_hb_env['Cu'];
1508  $user_id = (empty($p_hb_env['Uid']) ? 0 : $p_hb_env['Uid']);
1509  $user_name = prep_save($p_hb_env['user_name'], 50);
1510 
1511  $callback = function(&$item, $key) {
1512  $item = utf8_encode($item);
1513  };
1514  array_walk($p_meta, $callback);
1515 
1516  $meta = prep_save(json_encode($p_meta));
1517  if ($Cu <> '') {
1518  $sql = "insert into {$Cu}userlogins (user_id, user_name, login_dt, hcucode, status, remote_ip, metadata)
1519  values ('$user_id', E'{$user_name}', CURRENT_TIMESTAMP, '{$pLoginType}', {$status}, '{$p_ipaddr}', E'{$meta}')";
1520  $sth = db_query($sql, $p_dbh);
1521  }
1522  }
1523 // } # if we need to turn off failure logging
1524  return;
1525 }
1526 
1527 /*
1528  * Function: Return2FactorName
1529  * Purpose: This will return the name of the 2 Factor hash, which is a combo
1530  * cu, secret key , username
1531  * Parameters:
1532  * p_cu - CU Code
1533  * p_key - secret key
1534  * p_username - member username -- ALWAYS the username, not cellalias
1535  * Returns:
1536  * Returns the hashed string
1537  */
1538 function Return2FactorName($p_cu, $p_key, $p_username) {
1539 
1540  return sha1(trim($p_cu) . $p_key . trim($p_username));
1541 
1542 }
1543 
1544 
1545 function mobile_formatnumber($p_nbr, $p_show_separator = true) {
1546 
1547  $inc_comma = ($p_show_separator ? ",": "");
1548  if (is_numeric($p_nbr)) {
1549  return number_format($p_nbr, 2, ".", $inc_comma);
1550  } else {
1551  return $p_nbr;
1552  }
1553 }
1554 function mobile_displayhtml($text,$charset='ISO-8859-1') {
1555  return htmlspecialchars(trim($text),ENT_QUOTES,$charset,false);
1556 }
1557 function mobile_formatdate($p_date) {
1558  // ** ONLY FORMAT A VALID DATE
1559  $retVal = "";
1560  $timestamp = "";
1561 
1562  // ** Make accommdations for a N/A coming through on the date
1563  // Make sure NOT to format this date
1564  if (trim($p_date) != "N/A") {
1565  if (($timestamp = strtotime($p_date)) !== false) {
1566  $retVal = date("m/d/y", $timestamp);
1567  }
1568  } else {
1569  $retVal = $p_date;
1570  }
1571  return $retVal;
1572 }
1573 function mobile_formatdate_ipay($p_date) {
1574  // ** ipay seems to format dates as YYYY-MM-DDT00:00:00
1575  // ** I want to just return the informatin in the standard MM/DD/YYYY format
1576  $retVal = substr($p_date, 5, 2) . "/" . substr($p_date, 8, 2) . "/" . substr($p_date, 2, 2);
1577 
1578  return $retVal;
1579 }
1580 
1581 /**
1582  * Get_SpecialPwdCharacters
1583  *
1584  * This will return a comma delimited string of the special characters that are
1585  * supported when special characters are required.
1586  *
1587  * @param none
1588  *
1589  * @return string - Returns a comma-delimited list of the special characters.
1590  */
1591 function Get_PwdSpecialCharacters() {
1592  return "!,@,#,$,%,^,&,*,?,_,~,-,(,)";
1593 } // end Get_SpecialPwdCharacters
1594 
1595 /**
1596  * check_alias_available
1597  *
1598  * Check to see if a user alias is currently available in the system
1599  *
1600  * @param integer $p_dbh - Current database handle
1601  * @param array $p_hb_env - Current HB_ENV array
1602  * USES
1603  * cu - CU Code UPPER CASE
1604  * Uid - CURRENT Authenticated User - User ID
1605  * @param string $p_useralias - User alias we are checking to see if it already exists
1606  * @return boolean - {true, false} true - if the user alias IS available, false
1607  * if the user alias is NOT available
1608  */
1609 function check_alias_available($p_dbh, $p_hb_env, $p_useralias) {
1610  // Check database to see if anyone else is using this alias
1611  $sql = "SELECT count(*)
1612  FROM {$p_hb_env['Cu']}user
1613  WHERE
1614  (lower(user_name) = '" . strtolower(prep_save($p_useralias, 50)) . "'
1615  AND user_id <> '{$p_hb_env['Uid']}') ";
1616  $uniq_rs = db_query($sql, $p_dbh);
1617  list($uniq_cnt) = db_fetch_array($uniq_rs, 0);
1618 
1619  $uniq_cnt = ($uniq_cnt == 0 ? 0 : 1);
1620 
1621  // * if uniq_cnt is 0 then the alias is available, return True, else false
1622  return ($uniq_cnt == 0 ? true : false);
1623 }
1624 
1625 /**
1626  * Get_AccountHPRDetails
1627  * Get the Holds/Pending/Requested for a specified account key
1628  * It will call the 3 data functions that will retrieve the data
1629  *
1630  * @param integer $p_dbh - current database handle
1631  * @param array $p_hb_env - current HB_ENV array
1632  * @param object $p_mc - the current MC lanaguage class
1633  * @param string $p_acct_key - the current account key string
1634  * format AcctType|AcctNbr|Suffix|CertNo {only for deposit type}
1635  * @return array
1636  */
1637 function Get_AccountHPRDetails($p_dbh, $p_hb_env, $p_mc, $p_acct_key) {
1638  $retDetails = Array("holds" => Array(), "pending" => Array(), "requests" => Array());
1639 
1640  // ** RETRIEVE HOLDS
1641  $AcctHolds_ary = Get_HoldDetails($p_dbh, $p_hb_env, $p_acct_key);
1642  if ($AcctHolds_ary['status']['code'] == '000') {
1643  if (HCU_array_key_exists( $p_acct_key, $AcctHolds_ary )) {
1644  // ** SUCCESSFUL - SET THE HOLDS ARRAY
1645  $retDetails['holds'] = HCU_array_key_value( $p_acct_key, $AcctHolds_ary );
1646  }
1647  }
1648 
1649  // ** RETRIEVE PENDING
1650  $AcctPend_ary = Get_PendDetails($p_dbh, $p_hb_env, $p_acct_key);
1651 
1652  if ($AcctPend_ary['status']['code'] == '000') {
1653  if (HCU_array_key_exists( $p_acct_key, $AcctPend_ary )) {
1654  // ** SUCCESSFUL - set the pending array
1655  $retDetails['pending'] = HCU_array_key_value( $p_acct_key, $AcctPend_ary );
1656  }
1657  }
1658 
1659  // ** RETRIEVE REQUESTS
1660  $AcctReq_ary = Get_ReqDetails($p_dbh, $p_hb_env, $p_acct_key);
1661 
1662  if ($AcctReq_ary['status']['code'] == '000') {
1663  // ** SUCCESSFUL -- set the requests array
1664  // ** reset the fields within the array as the keys are NOT consistent
1665  if (HCU_array_key_exists('acctlist', $AcctReq_ary)) {
1666  if (count($AcctReq_ary['acctlist'][$p_acct_key]) > 0) {
1667  foreach ($AcctReq_ary['acctlist'][$p_acct_key] as $request_details) {
1668  // ** Add new item
1669  $retDetails['requests'][] = Array (
1670  "amount" => $request_details['amount'],
1671  "postdate" => HCU_array_key_value('date', $request_details),
1672  "description" => $request_details['txdesc'],
1673  "traceno" => $request_details['id']
1674  );
1675  }
1676  }
1677  }
1678  //$retDetails['requests'] = $AcctReq_ary['acctlist'][$p_acct_key];
1679  }
1680 
1681  return $retDetails;
1682 }
1683 
1684 /*
1685  * Function: Return_Random4Challenege
1686  * Purpose: This will parse the cookie PWDCHG and retrieve p1, which is the
1687  * random number that will be used to decrypt the challenge serialized hash
1688  * Paremeters: $p_hb_env -- HB_ENV
1689  * Returns: This will return an empty string OR the value if the expires was good
1690  * and the
1691  */
1692 function Return_Random4Challenge($p_hb_env) {
1693  $retVal = "";
1694  // ** GET THE COOKIE CONTENTS
1695  if ($_COOKIE['PWDCHG']) {
1696  $cookieval = $_COOKIE['PWDCHG'];
1697 
1698  $cookie_arr = Array();
1699  parse_str($cookieval, $cookie_arr);
1700  // ** FIRST Make sure the HASH MAKES SENSE
1701  if ($cookie_arr['p3'] == MD5($p_hb_env['secret'] . MD5(join (':', array($cookie_arr['p1'], $cookie_arr['p2']))))) {
1702  // ** NOW BE SURE OF TIME
1703  if ($cookie_arr['p2'] > time()) {
1704  $retVal = intval($cookie_arr['p1']);
1705  } else {
1706  }
1707  } else {
1708  }
1709  }
1710 
1711  return $retVal;
1712 }
1713 
1714 function hcuCheckUsername($username) {
1715  $pass_val = false;
1716  // Check the given username
1717  // Not empty, and not containing \`,"; or whitespace
1718  if (
1719  $username > '' && !(preg_match("/[\\\`,\"\s;]/", $username))
1720  ) {
1721  $pass_val = true;
1722  } else {
1723  $pass_val = false;
1724  }
1725  return $pass_val;
1726 
1727  }
1728 
1729  /**
1730  *
1731  * Generate a random Access code
1732  *
1733  * @param integer $length how long the code should be - default 6 digits
1734  * @param integer $ttl how long before the code expires - default 40 minutes
1735  *
1736  * @return array (Authcode, AuthExpires)
1737  */
1738  function generateAuthcode($length = 6, $ttl = 2400) {
1739  $length = intval($length);
1740  # valid range 6 to 10 digits, default 6 digits
1741  $length = ($length < 6 || $length >10 ? 6 : $length);
1742  $ttl = intval($ttl);
1743  # valid range 15 minutes to 3 hours, default 40 minutes
1744  $ttl = ($ttl < 900 || $ttl > 10800 ? 2400 : $ttl);
1745  $result = substr(str_repeat('0',$length) . rand(0,pow(10,$length)),-$length,$length);
1746  return array('authcode' => $result, 'authexpires' => time() + $ttl);
1747 }
1748 /**
1749  *
1750  * Update the user record to save authcode & expires timestamp
1751  *
1752  * @param integer $p_dbh - Current Database handle for Update_MemberInfo
1753  * @param array $p_hb_env - Current HB_ENV Update_MemberInfo uses hb_env['Cu']
1754  * @param object $p_mc - This points to the current MC value for the language class
1755  * Update_MemberInfo uses MC for error strings
1756  * @param array $userrec result of GetUserbyName or similar
1757  * will be updated with new authcode / mfaquest array values
1758  * @param string $authcode newly-generated authcode for member
1759  * @param timestamp $authexpires expiration timestamp for $authcode
1760  *
1761  * @return boolean TRUE/FALSE
1762  */
1763 function setAuthcode ($p_dbh, $p_hb_env, $p_mc, &$userrec, $authcode, $authexpires) {
1764 
1765  // Save information to the MFA array for the user identified in $userrec
1766  // leave as discrete function for now, because we may redesign. So just
1767  // save the values here
1768  // save authcode and authexpires as part of mfaquest array
1769  // also update userrec (or call GetUserbyName) so script has current values
1770 
1771  /*
1772  * **********************
1773  * UPDATE [cucode]user TABLE WITH AUTHCODE INFO
1774  * **********************
1775  */
1776 
1777  $retVal = TRUE;
1778 
1779  $mbrMfa = HCU_MFADecode(HCU_JsonDecode($userrec['mfaquest']));
1780  $mbrMfa['authcode'] = $authcode;
1781  $mbrMfa['authexpires'] = $authexpires;
1782 
1783  $mbrSaveMfa = PrepareMfaQuestString($mbrMfa);
1784 
1785  $userrec['authcode'] = $authcode;
1786  $userrec['authexpires'] = $authexpires;
1787  $userrec['mfaquest'] = $mbrSaveMfa;
1788 
1789  // ** update the user table
1790  $upd_list_array = serialize(Array("mfaquest" => $mbrSaveMfa));
1791  $upd_keys_array = serialize(Array("user_name" => $userrec['user_name']));
1792  $updateResponse = Update_MemberInfo($p_dbh, $p_hb_env, $p_mc, $upd_list_array, $upd_keys_array);
1793 
1794  if ($updateResponse['status']['code'] != '000') {
1795  // ** Unable to process for some reason...
1796  // ** What to do next
1797  // throw new Exception('User Record Not Updated', '905');
1798  $retVal = FALSE;
1799  }
1800  return $retVal;
1801 }
1802 
1803 function setAdminAuthcode ($dbh, $Cu, $Cn, $authcode) {
1804  /*
1805  * **********************
1806  * UPDATE cuadminusers TABLE WITH AUTHCODE INFO
1807  * **********************
1808  */
1809 
1810  $retVal = TRUE;
1811 
1812  $altIP = $_SERVER['REMOTE_ADDR'];
1813  $timeStamp = time();
1814 
1815  // pin|ip|timestamp started|retry count
1816  $saveUserConfirm = $authcode . "|" . $altIP . "|" . $timeStamp . "|0";
1817 
1818  // ** update the cuadminuser table
1819  $sql = "Update cuadminusers SET userconfirm = '$saveUserConfirm' WHERE cu = '$Cu' AND user_name = '$Cn'";
1820  $sth = db_query($sql,$dbh);
1821  if ($sth < 1) {
1822  // ** Unable to process for some reason...
1823  // ** What to do next
1824  // throw new Exception('User Record Not Updated', '905');
1825  $retVal = FALSE;
1826  }
1827 
1828  return $retVal;
1829 }
1830 
1831 /**
1832  *
1833  * Send the authcode using the selected transport method
1834  *
1835  * @param integer $dbh - Current Database handle
1836  * @param array $p_hb_env - Current HB_ENV SendLongCodeSMS uses it
1837  * @param array $userrec result of GetUserbyName or similar
1838  * @param string $retSendTo HCU_PayloadEncode value containing method and destination
1839  * @param string $retSendFrm valid sms # or email address (validate before call)
1840  *
1841  * @return boolean TRUE / FALSE
1842  */
1843 function sendAuthcode ($dbh, $p_hb_env, $userrec, $retSendTo) {
1844 
1845  $unmasked = HCU_PayloadDecode($userrec['cu'], $retSendTo, false);
1846  $retSendHow = $unmasked[0];
1847  $retSendTo = $unmasked[1];
1848  $MsgFromName = $p_hb_env['orgname']; # encoding / escaping needed? Handle empty?
1849  $MsgSubj = trim($p_hb_env['orgname']) . " Authentication Request";
1850 
1851  // * get the current code from the userrec
1852  $hcuAuth = $userrec['authcode'];
1853  $hcuExpires = $userrec['authexpires'];
1854  $timeExpire = round( ($userrec['authexpires'] - time()) / 60 );
1855 
1856  // $MsgBody = "Code: $hcuAuth good until " . gmdate("m/d/Y H:i:s e", $hcuExpires);
1857  // $MsgBody = "Access Code $hcuAuth expires in 20 minutes";
1858 
1859  if ($retSendHow == 'S') {
1860  // style the message for SMS
1861  $MsgBody = "Code: $hcuAuth\n";
1862  $MsgBody .= "Sent from {$p_hb_env['orgname']}\n";
1863  $MsgBody .= "Secure Access Code expires in $timeExpire minutes.\n";
1864 
1865  // Send a message to the user
1866  // ** Be sure to strip out just the number from the retSendto
1867  // Just to be certain strip out the potential + from prepended number
1868  $retSendTo = preg_replace('/^([\+]{0,1})(\d+)(\@.*)/', '$2', $retSendTo);
1869 
1870  if ($p_hb_env['flagset3'] & GetFlagsetValue('CU3_LONGCODE_MFA')) {
1871  $retSendFrm = GetCuSMSFrom($dbh, $userrec['cu']);
1872 
1873  // ** USE LONG CODE FOR SENDING AUTH CODE
1874  $msg_response = SendLongCodeSMS($GLOBALS['HOMECU_LONGCODE_API_KEY'], $GLOBALS['HOMECU_LONGCODE_URL'], $retSendFrm, $retSendTo, $MsgBody, $p_hb_env);
1875  } else {
1876  // ** USE AMAZON AWS SERVICE
1877  $msg_response = SendAwsSMS($retSendTo, $p_hb_env['orgname'], $MsgBody, true);
1878  }
1879 
1880  } elseif ($retSendHow == 'E') {
1881  // style the message for HTML email
1882  $MsgBody = "Code: $hcuAuth <br><br>";
1883  $MsgBody .= "Sent from {$p_hb_env['orgname']}<br><br>";
1884  $MsgBody .= "Secure Access Code expires in $timeExpire minutes.<br>";
1885 
1886  # send to the email address stored in the userrec
1887  // mail to the user
1888  $retSendFrm = GetCuEmailFrom($dbh, $userrec['cu']);
1889  $notify = new ErrorMail;
1890  $notify->header = "Content-type: text/html";
1891  $notify->mailto = $retSendTo;
1892  $notify->mailfromname = $MsgFromName;
1893  $notify->mailfrom = $retSendFrm;
1894  $notify->subject = $MsgSubj;
1895  $notify->msgbody = "$MsgBody";
1896  $notify->callingfunction = __FUNCTION__;
1897  $notify->file = __FILE__;
1898  $notify->cu = $userrec['cu'];
1899 
1900  $notify->SendMail();
1901  $msg_response = TRUE;
1902  } else {
1903  # error - invalid method
1904  $msg_response = FALSE;
1905  }
1906  return $msg_response;
1907 }
1908 
1909 function sendAdminAuthcode ($dbh, $Cu, $authResp, $retSendTo, $retSendHow) {
1910 
1911  // get the organization name
1912  $sql = "SELECT orgname FROM cuadmin WHERE cu = '$Cu'";
1913  $sth = db_query( $sql, $dbh );
1914  $orgName = db_fetch_array( $sth, 0 )[0];
1915 
1916  $MsgFromName = $orgName;
1917  $MsgSubj = trim($orgName) . " Authentication Request";
1918 
1919  // * get the current code
1920  $hcuAuth = $authResp['authcode'];
1921  $hcuExpires = $authResp['authexpires'];
1922 
1923  $timeExpire = round( ($authResp['authexpires'] - time()) / 60 );
1924 
1925  if ($retSendHow == 'sms') {
1926  // style the message for SMS
1927  $MsgBody = "Sent from {$orgName}\n\n";
1928  $MsgBody .= "Secure Access Code expires in $timeExpire minutes.\n";
1929  $MsgBody .= "Code: $hcuAuth";
1930 
1931  // Send a message to the user
1932  // ** Be sure to strip out just the number from the retSendto
1933  // Just to be certain strip out the potential + from prepended number
1934  $retSendTo = preg_replace('/^([\+]{0,1})(\d+)(\@.*)/', '$2', $retSendTo);
1935 
1936  // ** USE AMAZON AWS SERVICE
1937  $msg_response = SendAwsSMS($retSendTo, $orgName, $MsgBody, true);
1938 
1939  } elseif ($retSendHow == 'email') {
1940  // style the message for HTML email
1941  $MsgBody = "Sent from {$orgName}<br><br>";
1942  $MsgBody .= "Secure Access Code expires in $timeExpire minutes.<br>";
1943  $MsgBody .= "Code: $hcuAuth";
1944 
1945  # send to the email address stored in the userrec
1946  // mail to the user
1947  $retSendFrm = GetCuEmailFrom($dbh, $Cu);
1948  $notify = new ErrorMail;
1949  $notify->header = "Content-type: text/html";
1950  $notify->mailto = $retSendTo;
1951  $notify->mailfromname = $MsgFromName;
1952  $notify->mailfrom = $retSendFrm;
1953  $notify->subject = $MsgSubj;
1954  $notify->msgbody = "$MsgBody";
1955  $notify->callingfunction = __FUNCTION__;
1956  $notify->file = __FILE__;
1957  $notify->cu = $Cu;
1958  $notify->SendMail();
1959  $msg_response = TRUE;
1960  } else {
1961  # error - invalid method
1962  $msg_response = FALSE;
1963  }
1964  return $msg_response;
1965 }
1966 
1967 /**
1968  * GetUserContacts get user contact info, including email from {cu}user table
1969  * and phone numbers stored in {Cu}usercontact table
1970  * Only email / phone numbers that pass inspection via validateEmail and
1971  * hcuCheckPhone are included
1972  *
1973  * Also retrieves the 'from' email address and longcode from cuadmnotify. Use
1974  * longcode where role='txtbanking' and email where role='email_securitychgfrom'
1975  * @param integer $p_dbh - Current Database handle
1976  * @param array $p_hb_env - Current HB_ENV
1977  * @param array $userrec result of GetUserbyName or similar
1978  *
1979  * @return array contact list
1980  */
1981 function GetUserContacts($p_dbh, $p_hb_env, $userrec) {
1982  $retList = array('EMAIL' => array(), 'SMS' => array(), 'Found' => 0, 'GotIt' => 0);
1983  # load email from {cu}user record
1984  if (trim($userrec['email']) > '' && validateEmail($userrec['email'])) {
1985  $key = HCU_PayloadEncode($p_hb_env['Cu'], array('E',trim($userrec['email'])), false);
1986  $retList['EMAIL'][$key] = hcuMaskEmail(trim($userrec['email']));
1987  $retList['Found']++;
1988  }
1989  # get phones from {cu}usercontact
1990  $sql = "select uc.phones from {$p_hb_env['Cu']}usercontact as uc "
1991  . "join {$p_hb_env['Cu']}user as cu_user on cu_user.contact = uc.contact_id "
1992  . "where cu_user.user_id = " . intval($userrec['user_id']) . ";";
1993  $sth = db_query($sql, $p_dbh);
1994  if (db_num_rows($sth) == 1) {
1995  list($phones) = db_fetch_array($sth, 0);
1996  if ($phones != "" && $phones != "[]") {
1997  $phones = HCU_JsonDecode($phones);
1998  if (is_array($phones)) {
1999  foreach ($phones as $phtype=>$phnum) {
2000  /*
2001  * phnum can be an array or a single value
2002  * This will need to be tolerant of both cases
2003  */
2004  if (is_array($phnum)) {
2005  /* Array of Phone number for this type {mobile, home, work} */
2006  for ($phIdx = 0; $phIdx < count($phnum); $phIdx++) {
2007  if ($phnum[$phIdx] != "" && hcuCheckPhone($phnum[$phIdx])) {
2008  $key = HCU_PayloadEncode($p_hb_env['Cu'], array('S',preg_replace('/\D/', '', $phnum[$phIdx])), false);
2009  $retList['SMS'][$key] = hcuMaskPhone($phnum[$phIdx]);
2010  $retList['Found']++;
2011  }
2012 
2013  }
2014  } else {
2015  /* Single phone number for this type */
2016  if ($phnum != "" && hcuCheckPhone($phnum)) {
2017  $key = HCU_PayloadEncode($p_hb_env['Cu'], array('S',preg_replace('/\D/', '', $phnum)), false);
2018  $retList['SMS'][$key] = hcuMaskPhone($phnum);
2019  $retList['Found']++;
2020  }
2021  }
2022  }
2023  }
2024  }
2025  }
2026  // * If we have a valid authcode, allow for 'I already have a code' feature
2027  $aryMfaQuest = HCU_JsonDecode($userrec['mfaquest']);
2028  $mbrMfaQuest = HCU_MFADecode($aryMfaQuest);
2029  if ( $mbrMfaQuest['authexpires'] > (time() + 120) && trim($mbrMfaQuest['authcode']) > '' ) {
2030  # if we have a code good for at least 2 more minutes, send the link
2031  $retList['GotIt'] = true;
2032  }
2033  return $retList;
2034 }
2035 
2036 /**
2037  * Returns a longcode for sending SMS. Uses the cuadmnotify email saved for
2038  * txtbanking role, or picks random from HomeCU longcodes if cu setting is
2039  * not valid
2040  *
2041  * @param integer $dbh current database handle
2042  * @param string $cu
2043  * @return string longcode
2044  */
2045 function GetCuSMSFrom($dbh, $cu) {
2046 
2047  $sendfrom = '';
2048  $sql = "SELECT trim(email) FROM cuadmnotify
2049  WHERE cu = '$cu' and role = 'txtbanking' ";
2050 
2051  $sth = db_query($sql, $dbh);
2052 
2053  if (db_num_rows($sth) == 1) {
2054  list($sendfrom) = db_fetch_array($sth,0);
2055  }
2056 
2057  $pattern = '/^[+]{0,1}[0-9]{1,12}$/';
2058  if (empty($sendfrom) || !preg_match($pattern, $sendfrom)) {
2059  # invalid long code - get one from longcode round-robin
2060  $LONGCODE_ROUNDROBIN = array('+12672458136', '+12082982149', '+16017142198', '+12082547302', '+16017142199', '+13306492200');
2061 
2062  $index = rand(0, sizeof($LONGCODE_ROUNDROBIN) - 1);
2063  $sendfrom = $LONGCODE_ROUNDROBIN[$index];
2064 
2065  }
2066  return $sendfrom;
2067 }
2068 /**
2069  * Returns an email return address. Uses the cuadmnotify email saved for
2070  * securitychgfrom role, or noreply@homecu.com if cu setting is not valid
2071  *
2072  * Should probably also nag support@homecu.com or somebody at the cu
2073  * if using default, but not until I'm done testing...
2074  *
2075  * @param integer $dbh current database handle
2076  * @param string $cu
2077  * @return string email address
2078  */
2079 function GetCuEmailFrom($dbh, $cu) {
2080  $sql = "SELECT trim(email) FROM cuadmnotify
2081  WHERE cu = '$cu' and role = 'securitychgfrom' ";
2082 
2083  $sth = db_query($sql, $dbh);
2084  if (db_num_rows($sth) == 1) {
2085  list($sendfrom) = db_fetch_array($sth,0);
2086  }
2087  if (trim($sendfrom) == '' || !validateEmail($sendfrom) ) {
2088  # set default - also nag support@homecu.com to fix it?
2089  $sendfrom = 'noreply@homecu.com';
2090  }
2091  return $sendfrom;
2092 }
2093 
2094 /**
2095  *
2096  * @param string $phone
2097  * @return boolean
2098  *
2099  * Checks if the given string, stripped of any non-digits, contains
2100  * exactly 10 characters
2101  */
2102 function hcuCheckPhone($phone) {
2103  $retVal = TRUE;
2104  # strip punctuation
2105  $phone = preg_replace('/\D/', '', $phone);
2106  if (!is_numeric($phone) || $phone <= 0 || strlen($phone) != 10) {
2107  $retVal = FALSE;
2108  }
2109  return $retVal;
2110 }
2111 /**
2112  * Return the given string formatted as a phone number with only the last
2113  * 4 digits showing. No validation is performed on the string phone number
2114  * Use hcuCheckPhone to verify it first
2115  *
2116  * @param string $phone
2117  * @return string masked phone number
2118  *
2119  */
2120 function hcuMaskPhone($phone) {
2121  # strip punctuation & apply mask
2122  $phone = preg_replace('/\D/', '', $phone);
2123 // $phnum = substr($phnum, 0, 3) . "-" . substr($phnum, 3, 3) . "-" . substr($phnum, 6);
2124  $phone = "xxx-xxx-" . substr($phone, 6);
2125 
2126  return $phone;
2127 }
2128 /**
2129  * Return the given string with only the 1st and last characters showing before
2130  * the '@' with the rest of the string masked.
2131  * No validation is performed on the string email address - use validateEmail
2132  * to verify it first.
2133  *
2134  * @param string $email
2135  * @return string masked email
2136  */
2137 function hcuMaskEmail($email) {
2138  $marker = strpos($email,'@');
2139  $retVal = substr_replace($email,str_repeat('.',$marker - 2), 1,$marker -2);
2140 
2141  /*
2142  * this method showed up to 3 char before the '@' and up to 3 char after '@'
2143  *
2144  if ($marker > 3 ) {
2145  $retVal = substr($email, 0, 3) . str_repeat('x',$marker - 3);
2146  } else {
2147  $retVal = substr($email,0,$marker);
2148  }
2149  */
2150 
2151  /*
2152  * the following leaves the dot in place - but I didn't like it
2153  * as much as I thought I would
2154  *
2155  $lastdot = strrpos($email,'.');
2156  if ($lastdot - $marker > 4) {
2157  $retVal .= substr($email, $marker, 4) . str_repeat('x',$lastdot - ($marker + 4));
2158  } else {
2159  $retVal .= substr($email, $marker, $lastdot - $marker);
2160  }
2161  $retVal .= '.' . str_repeat('x',strlen($email) - ($lastdot + 1));
2162  */
2163 
2164 /*
2165  *
2166  if (strlen($email) > ($marker + 4) ) {
2167  $retVal .= substr($email, $marker, 4) . str_repeat('x',strlen($email) - ($marker + 4));
2168  } else {
2169  $retVal .= substr($email, $marker, 2) . str_repeat('x',strlen($email) - ($marker + 2));
2170  }
2171  */
2172 
2173  return $retVal;
2174 }
static GetInstance($dbh)
SendMail()
Definition: errormail.i:111