Odyssey
hcuCommon.i
1 <?php
2 /**
3  * File: hcuCommon.i
4  * Purpose: These function can be used from Online Banking, Monitor, or Admin Backoffice
5  *
6  */
7 
8 /**
9  * Function: HCU_JsonDecode
10  * Purpose: Safely call the json_decode function.
11  * This may require certain calls to sanitize data or error check
12  * **** NOTE *****
13  * This function has different parameter order and defaults to return an assoc array .
14  * The php json_decode defaults to returning an object rather than array
15  * Parameters:
16  * @param string pJsonString - This is the 'encoded' json string
17  * @param boolean pBolOnEmptyArray - If something went wrong, should the json be returned as an Array(true) or string(false)
18  * @param boolean pBolAssoc - {Default: True} Should the returned value be an associative array
19  *
20  * Returns:
21  * mixed The return value will be determined on if the decode was successful and if there was an error.
22  * If no error occurred, then the function will return
23  * (object) if pBolAssoc was false
24  * array if pBolAssoc was true
25  * string if there was an error and the pBolEmptyArray was true
26  *
27  * mixed MFA Quest Array structure
28  */
29 function HCU_JsonDecode($pJsonString, $pBolOnEmptyArray=true, $pBolAssoc=true) {
30  $dataReturn = ($pBolOnEmptyArray ? Array() : '');
31 
32  try {
33  if (is_string($pJsonString)) {
34  $pJsonString = trim($pJsonString);
35  $dataReturn = json_decode($pJsonString, $pBolAssoc);
36  switch (json_last_error()) {
37  case JSON_ERROR_NONE:
38  // ** ALL IS GOOD
39  break;
40  case JSON_ERROR_DEPTH:
41  case JSON_ERROR_STATE_MISMATCH:
42  case JSON_ERROR_CTRL_CHAR:
43  case JSON_ERROR_SYNTAX:
44  case JSON_ERROR_UTF8:
45  default:
46  throw new exception('Json decode error');
47  break;
48  }
49  } else {
50  throw new Exception('Invalid Data Type');
51  }
52  } catch (Throwable $t){
53  // * Throwable Error Occurred - At this time do not report this
54  $dataReturn = ($pBolOnEmptyArray ? ($pBolAssoc ? Array() : (object) Array()) : '');
55  } catch (Exception $e) {
56  // * An exception occurred - At this time do not report this
57  $dataReturn = ($pBolOnEmptyArray ? ($pBolAssoc ? Array() : (object) Array()) : '');
58  }
59 
60  return $dataReturn;
61 }
62 
63 /**
64  * Function: HCU_JsonEncode
65  * Purpose: Safely call the json_encode function.
66  *
67  * Parameters:
68  * @param array pJsonArray - This is the 'encoded' json string
69  *
70  * Returns:
71  * string Returns a json encoded string
72  */
73 function HCU_JsonEncode($pJsonArray) {
74  // Default value is empty json string
75  $dataReturn = '{}';
76 
77  try {
78 
79  $dataReturn = json_encode($pJsonArray);
80  switch (json_last_error()) {
81  case JSON_ERROR_NONE:
82  // ** ALL IS GOOD
83  break;
84  case JSON_ERROR_DEPTH:
85  case JSON_ERROR_STATE_MISMATCH:
86  case JSON_ERROR_CTRL_CHAR:
87  case JSON_ERROR_SYNTAX:
88  case JSON_ERROR_UTF8:
89  default:
90  $dataReturn = '{}';
91  break;
92  }
93 
94  } catch (Throwable $t){
95  // * Throwable Error Occurred - At this time do not report this
96  $dataReturn = '{}';
97  } catch (Exception $e) {
98  // * An exception occurred - At this time do not report this
99  $dataReturn = '{}';
100  }
101  return $dataReturn;
102 }
103 
104 
105 /**
106  * Function: HCU_MFADecode
107  * Purpose: This will decode the mfaquest value from the {cucode}user table
108  * it will return the information related to the mfaquest
109  * Parameters:
110  * @param array pJsonAry This should be the mfaquest array from {cucode}users table
111  *
112  * Return:
113  * @return array The following keys will be returned.
114  * [mfacount] => "Number of answers in the mfaquest array" (ALWAYS RETURNED)
115  * [answers] => "array of key(questid)->value(text) pairs "
116  * [challenge] => "the active quest id for mfa challenge"
117  * [authcode] => "the current secure access code"
118  * [authexpires] => "timestamp when the current secure access code expires"
119  * [mfadate] => "timestamp when challenge questions were saved"
120  *
121  */
122 function HCU_MFADecode($pJsonAry) {
123  $retMfaArray = Array(
124  "mfacount" => 0,
125  "answers" => Array(),
126  "challenge" => 0,
127  "authcode" => '',
128  "authexpires" => '',
129  "mfadate" => 0
130  );
131 
132  $mfaQuestKey = 'answers';
133  $mfaChallengeKey = 'challenge';
134 
135  // * pJsonAry should be an array with the key 'answers', this will contain the actual answers
136  // * First check to ensure pJsonAry is an array
137  try {
138  if (HCU_array_key_exists($mfaQuestKey, $pJsonAry)) {
139  // Be sure 'answers' is also an array
140  if (is_array($pJsonAry[$mfaQuestKey])) {
141  $retMfaArray['mfacount'] = count($pJsonAry[$mfaQuestKey]);
142  $retMfaArray['answers'] = $pJsonAry[$mfaQuestKey];
143  }
144  }
145  if (HCU_array_key_exists($mfaChallengeKey, $pJsonAry)) {
146  // ** Challenge key found -- It is ALWAYS an integer
147  $retMfaArray['challenge'] = intval($pJsonAry[$mfaChallengeKey]);
148  }
149  if (HCU_array_key_exists("authcode", $pJsonAry)) {
150  // ** authcode key found -- It is ALWAYS string
151  $retMfaArray['authcode'] = $pJsonAry["authcode"];
152  }
153  if (HCU_array_key_exists("authexpires", $pJsonAry)) {
154  // ** authcode expiration key found -- It is ALWAYS an integer
155  $retMfaArray['authexpires'] = intval($pJsonAry["authexpires"]);
156  }
157  if (HCU_array_key_exists("mfadate", $pJsonAry)) {
158  // ** mfa change timestamp found -- It is ALWAYS an integer
159  $retMfaArray['mfadate'] = intval($pJsonAry["mfadate"]);
160  }
161  } catch (Exception $e) {
162  // Error occurred - set the return value to default
163  $retMfaArray = Array(
164  "mfacount" => 0,
165  "answers" => Array(),
166  "challenge" => 0,
167  "authcode" => '',
168  "authexpires" => '',
169  "mfadate" => 0
170  );
171  }
172 
173 
174  return $retMfaArray;
175 }
176 
177 /**
178  * This will prepare the mfaquest array into a json string that contains ONLY
179  * the key fields that should be included.
180  *
181  * @param array $pMfaQuestAry Mfaquest array with ALL values that need to be saved
182  *
183  * @return string Returns the json string that can be saved
184  *
185  */
186 function PrepareMfaQuestString($pMfaQuestAry) {
187 
188  $allowedKeys = Array("answers", "challenge", "authcode", "authexpires", "mfadate");
189 
190  // ** Return an array that ONLY exists with the elements in AllowedKeys
191 
192  return json_encode(array_intersect_key($pMfaQuestAry, array_flip($allowedKeys)));
193 
194 }
195 /**
196  *
197  * HCU_array_key_exists
198  *
199  * This function will check if a key exists in an array. BUT first it makes sure
200  * the Haystack is a valid array. If it is not, then the function will fail
201  *
202  * Returns: true - the needle exists in the haystack
203  * false - the haystack was NOT an array, or the key does not exist
204  *
205  * @param string $pNeedleKey
206  * @param array $pAryHaystack
207  *
208  * @return boolean
209  *
210  */
211 function HCU_array_key_exists($pNeedleKey, $pAryHaystack) {
212  $retVal = false; // ** Assume, it is NOT found
213 
214  if (is_array($pAryHaystack)) {
215  $retVal = array_key_exists($pNeedleKey, $pAryHaystack);
216  }
217 
218  return $retVal;
219 }
220 
221 
222 /**
223  *
224  * This function will get the value for a specific array key
225  * But it will first ensure the Haystack IS an array
226  * AND it will check the existence of the key prior to returning the value
227  *
228  * @param string $pNeedleKey - This is the Array Key that is being sought
229  * @param array[] $pAryHaystack - This is the array that should contain the Needle
230  *
231  * @return mixed[] This will return the mixed value if the key does in fact exist in the array
232  * If no value exists, then a false is returned
233  *
234  */
235 function HCU_array_key_value($pNeedleKey, $pAryHaystack) {
236 
237 //print "<br/>HKV - One <br/>";
238  if (HCU_array_key_exists($pNeedleKey, $pAryHaystack)) {
239 //print "<br/>HKV - 2 <br/>";
240  return $pAryHaystack[$pNeedleKey];
241  } else {
242 //print "<br/>HKV - 3 <br/>";
243  return false;
244  }
245 }
246 
247 /**
248  *
249  * This function will return the count of items in the array for that key field
250  * If no items exist, it will return 0
251  *
252  * @param string $pNeedleKey - This is the Array Key that is being sought
253  * @param array[] $pAryHaystack - This is the array that should contain the Needle
254  *
255  * @return mixed[] This will return the mixed value if the key does in fact exist in the array
256  * If no value exists, then a false is returned
257  *
258  */
259 function HCU_array_item_count($pNeedleKey, $pAryHaystack) {
260 
261  if (HCU_array_key_value($pNeedleKey, $pAryHaystack)) {
262  return count($pAryHaystack[$pNeedleKey]);
263  } else {
264  return 0;
265  }
266 }
267 
268 
269 /**
270  *
271  * setcookie replacement that will default to values in the settings array
272  * If you want to specify the values, consider using HCU_setcookie directly
273  *
274  * @param array $pEnvSet This is the ENVSET array for the current environment. It should have the minimum structure
275  * [
276  * 'ticket' => [
277  * 'domain' => GetEnvSetting('TICKET_DOMAIN', 'homecu.net'),
278  * 'expires' => GetEnvSetting('TICKET_EXPIRES', 900),
279  * 'persists' => GetEnvSetting('TICKET_PERSISTS', 94 * 86400),
280  * 'inactive' => GetEnvSetting('TICKET_INACTIVE', 600),
281  * ],
282  * 'site_path' => GetEnvSetting('SITE_PATH', '/home'),
283  * 'server_host' => GetEnvSetting('SERVER_HOST', 'localhost'),
284  * 'require_encryption' => GetEnvSetting('REQUIRE_ENCRYPTION', 1),
285  * 'logger' => $logger
286  * ];
287  *
288  * @param string $pCookieName The name associated with this cookie
289  * @param string $pCookieValue (defaults '') The value for this cookie
290  * @param int $pExpire (defaults 0) How long will this cookie exist
291  *
292  *
293  * @return mixed
294  */
295 function HCU_setcookie_env( $pEnvSet, $pCookieName, $pCookieValue='', $pExpire=0) {
296 
297  // Verify the ticket structure exist in envset
298  if (HCU_array_key_exists('ticket', $pEnvSet)) {
299  $domain = HCU_array_key_value('domain', $pEnvSet['ticket']);
300  $secure = HCU_array_key_value('require_encryption', $pEnvSet);
301  }
302  // ** This will always be / when using this function -- Cookie will be good for all directories
303  $path = '/';
304 
305  $retBaked = HCU_setcookie(HCU_array_key_value('logger', $pEnvSet), $pCookieName, $pCookieValue, $pExpire, $path, $domain, $secure);
306 
307  return $retBaked;
308 }
309 
310 /**
311  * Adjust some cookie requirements from `$settings.i` so infrastructure
312  * can control some security guidelines.
313  *
314  * @param object $logger This is the Logger Object
315  * @param string $name Cookie Name
316  * @param string $value (default '') Cookie Value
317  * @param int $expire (default 0) Cookie Expire Time
318  * @param string $path Cookie path
319  * @param string $domain Cookie Domain
320  * @param
321  */
322 function HCU_setcookie($logger, $name, $value="", $expire=0, $path="", $domain="", $secure=1, $httponly=false) {
323 
324 // $logger->info("Setting cookie $name ");
325  if (!$secure) {
326 // $logger->info("Setting cookie $name secure mode to: {$secure}");
327  }
328 
329  if ($secure && !HCU_http_encrypted()) {
330  $logger->error("Setting cookie {$name}={$value} expire={$expire} path={$path} domain={$domain}");
331  /* NOTE: Currently exception handling is a WIP. At some point this could just throw. */
332  // throw new Exception('Can not set secure cookie on non HTTPS connection');
333  }
334 
335  $ret = setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
336  if (!$ret) {
337  $logger->error("Failed to set cookie: {$name}");
338  }
339  return $ret;
340 }
341 
342 /**
343  * Generate a cryptographically secured secret key for
344  * openssl encryption algorithms.
345  */
346 function getOpenSSLKey($key_suffix, $bit_size='256') {
347  // output raw binary data for key
348  $raw_output = true;
349 
350  if ($bit_size != '256')
351  throw new exception("Invalid key size. Supported: [256] bits.", 3);
352 
353  $hashKeyBilbo = GetOpenSSLKeyBilbo() . $key_suffix;
354  $hashKeyBugs = GetOpenSSLKeyBugs() . $key_suffix;
355 
356  // TODO: consider sha3-256?
357  $defaultKey = hash_hmac('sha256', $hashKeyBilbo, $hashKeyBugs, $raw_output); // Get 256-bit key
358 
359  switch($bit_size) {
360  case '256':
361  return $defaultKey;
362  break;
363 
364  default:
365  return $defaultKey;
366  }
367 }
368 
369 /**
370  * Generate authentication hash given the message and derivation key.
371  */
372 function getEncryptionAuthHash($message, $key_suffix, $auth_hash_algo, $auth_hash_binary) {
373  if ($auth_hash_algo != 'sha1' && $auth_hash_algo != 'sha256') {
374  throw new exception("Invalid hash algorithm: ". $auth_hash_algo, 2);
375  }
376  $hashKeyBilbo = GetOpenSSLKeyBilbo() . $key_suffix;
377  return hash_hmac($auth_hash_algo, $message, $hashKeyBilbo, $auth_hash_binary);
378 }
379 
380 /**
381  * function hcuOpenSSLEncrypt($message)
382  * Encrypts a message by key using the openssl_encrypt format
383  *
384  *
385  * Supported encryption methods;
386  * - "aes-256-ctr"
387  * - "aes-256-ofb"
388  * - "aes-256-cfb"
389  * - "aes-256-cfb1"
390  * - "aes-256-cfb8"
391  * - "aes-256-ofb"
392  * - "aes-256-xts"
393  * - "aes-256-ecb"
394  *
395  * Note: $options argument in openssl_encrypt and openssl_decrypt functions
396  *
397  * OPENSSL_ZERO_PADDING:
398  * If OPENSSL_ZERO_PADDING is included (as in
399  * OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING) in the options, this
400  * tells openssl encryption method that the input data IS ALREADY
401  * zero padded to a right byte/bits size and openssl does not need
402  * to do apply any padding. Otherwise, OPENSSL_ZERO_PADDING is
403  * NOT INCLUDED as a part of $options to let openssl do the PKCS#7
404  * padding automatically; this means that the input string NEED
405  * NOT be padded.
406  *
407  * OPENSSL_RAW_DATA:
408  * This option only affects the format of the data returned to the
409  * caller; When NOT specified in options, returns Base64 encoded
410  * data, otherwise returns as-is.
411  *
412  * @param string $message -- the message to encrypt
413  * @param string $key_suffix -- encryption key or argument to getOpenSSLKey
414  * @param string $method -- encryption cipher method (default: aes-256-cbc)
415  * @param string $auth_hash_algo -- sha1 or sha256 (hash algo to used for encryption
416  * authentication) (default: sha1)
417  * @param string $auth_hash_binary -- output of authentication hash (raw or hexit?)
418  * (default: binary raw (true))
419  * @param string $iv -- initialization vector for some encryption modes (should
420  * be generated, used and returned as per $context if not provided)
421  * @param string $context -- context of encryption
422  * - default - default encryption with authentication
423  * - connect_chkfree - for CHKFREE encryption
424  * - connect_ezcard - for EasyCard encryption
425  * - connect_certegy - for Certegy encryption
426  * - connect_digital - for digital mailer encryption
427  * - connect_ipay - for IPAY encryption
428  * - connect_vsoft - for VSHO/VSOFT encryption
429  * - connect_mvi - for MVI images encryption
430  * @return array
431  * -- iv.message and hash -- default context
432  * -- message and iv -- otherwise
433  */
434 function hcuOpenSSLEncrypt($message,
435  $key_suffix,
436  $method='aes-256-cbc',
437  $auth_hash_algo='sha1',
438  $auth_hash_binary=true,
439  $iv="",
440  $context="default") {
441 
442  // auth algo validation
443  if ($auth_hash_algo != 'sha1' && $auth_hash_algo != 'sha256') {
444  throw new exception("Invalid hash algorithm: ". $auth_hash_algo, 2);
445  }
446  // if initialization vector is not provided, we generate one by default
447  // Obviously, IV would need to be returned along with the encrypted
448  // ciphercode for various encryption modes, eg. except ECB. use context
449  // argument to handle how to return the IV value back to the caller
450  // Default context appends the IV to the ciphertext.
451  $ivsize = openssl_cipher_iv_length($method);
452  if ($iv == "") {
453  $iv = openssl_random_pseudo_bytes($ivsize);
454  } else {
455  $iv = mb_substr($iv, 0, $ivsize, '8bit');
456  }
457 
458  // Set encryption options before executing w.r.t. $context
459  // OPTIONS:
460  // OPENSSL_ZERO_PADDING: expects data to be space padded before encryption
461  // OPENSSL_RAW_DATA: generate raw binary ciphertext
462  if($context == "connect_chkfree" ||
463  $context == "connect_ezcard" ||
464  $context == "connect_certegy" ||
465  $context == "connect_digital" ||
466  $context == "connect_ipay" ||
467  $context == "connect_vsoft" ||
468  $context == "connect_mvi" ||
469  $context == "connect_smo") {
470  // OPENSSL_ZERO_PADDING means that it is expected that
471  // the data is already padded appropriately; otherwise,
472  // encryption with these options will fail
473  $openssl_options = OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING;
474  $encryptionKey = $key_suffix;
475  } else if ($context == "credentials") {
476  // return output as is (i.e. not base64 encoded)
477  $openssl_options = OPENSSL_RAW_DATA;
478  $encryptionKey = $key_suffix;
479  } else { // defaults
480  // PKCS7 padding and RAW ciphertext
481  $openssl_options = OPENSSL_RAW_DATA;
482  $encryptionKey = getOpenSSLKey($key_suffix);
483  }
484 
485  // ENCRYPTION
486  $ciphertext = openssl_encrypt($message,
487  $method,
488  $encryptionKey,
489  $openssl_options,
490  $iv);
491 
492  // iv and ciphertext are part of the default message
493  // Default context appends the IV to the ciphertext in a raw format.
494  $defaultCipher = $iv . $ciphertext;
495  $defaultHash = getEncryptionAuthHash($defaultCipher,
496  $key_suffix,
497  $auth_hash_algo,
498  $auth_hash_binary);
499  $defaultResp = array("message" => $defaultCipher, "hash" => $defaultHash);
500 
501  // Prepare response after encryption based on the context of the encryption
502  switch($context) {
503  case "connect_chkfree":
504  case "connect_ezcard":
505  case "connect_certegy":
506  case "connect_digital":
507  case "connect_ipay":
508  case "connect_vsoft":
509  case "connect_mvi":
510  case "connect_smo":
511  $connectCipher = $ciphertext;
512  $returnArray = array("message" => $connectCipher, "iv" => $iv);
513  break;
514 
515  case "credentials":
516  $credentialCipher = $ciphertext;
517  $returnArray = array("message" => $credentialCipher, "iv" => $iv);
518  break;
519 
520  // also for context="default" (case "default":)
521  case "default":
522  default:
523  $returnArray= $defaultResp;
524  }
525  return $returnArray;
526 }
527 
528 /**
529  * function EncryptPayloadData($message, $key)
530  * Encrypts a message by key using the openssl_encrypt format.
531  *
532  * @param string $message -- the message to encrypt
533  * @param string $key -- the encryption key
534  * @return string -- the nonce and encrypted message concatinated together and Base64 encoded
535  */
536 function EncryptPayloadData( $message, $key )
537 {
538  $method = 'aes-256-cbc';
539 
540  $nonceSize = openssl_cipher_iv_length( $method );
541  $nonce = openssl_random_pseudo_bytes( $nonceSize );
542 
543  $ciphertext = openssl_encrypt( $message, $method, $key, OPENSSL_RAW_DATA, $nonce );
544 
545  $decrypttext = openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $nonce );
546 
547  $returnString = base64_encode( $nonce ) . "|" . base64_encode( $ciphertext );
548 
549  return $returnString;
550 } // end EncryptPayloadData
551 
552 /**
553  * function hcuOpenSSLDecrypt($message, $hash)
554  * Decrypts a message by key using the openssl_encrypt format but only if the hashes match
555  *
556  * Supported encryption methods;
557  * - "aes-256-ctr"
558  * - "aes-256-ofb"
559  * - "aes-256-cfb"
560  * - "aes-256-cfb1"
561  * - "aes-256-cfb8"
562  * - "aes-256-ofb"
563  * - "aes-256-xts"
564  * - "aes-256-ecb"
565  *
566  * Note: $options argument in openssl_encrypt and openssl_decrypt functions
567  *
568  * OPENSSL_ZERO_PADDING:
569  * If OPENSSL_ZERO_PADDING is included (as in
570  * OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING) in the options, this
571  * tells openssl encryption method that the input data IS ALREADY
572  * zero padded to a right byte/bits size and openssl does not need
573  * to do apply any padding. Otherwise, OPENSSL_ZERO_PADDING is
574  * NOT INCLUDED as a part of $options to let openssl do the PKCS#7
575  * padding automatically; this means that the input string NEED
576  * NOT be padded.
577  *
578  * OPENSSL_RAW_DATA:
579  * This option only affects the format of the data returned to the
580  * caller; When NOT specified in options, returns Base64 encoded
581  * data, otherwise returns as-is.
582  *
583  * @param string $message -- the message to decrypt
584  * @param string $hash -- authenticated hash of the $message, if applicable to $context
585  * @param string $key_suffix -- decryption key or argument to getOpenSSLKey
586  * @param string $method -- decryption cipher method (default: aes-256-cbc)
587  * @param string $auth_hash_algo -- sha1 or sha256 (hash algo to used for encryption
588  * authentication) (default: sha1)
589  * @param string $auth_hash_binary -- output of authentication hash (raw or hexit?)
590  * (default: binary raw (true))
591  * @param string $iv -- initialization vector for some encryption modes (should
592  * be generated, used and returned as per $context if not provided)
593  * @param string $context -- context of decryption
594  * - default - default decryption with authentication
595  * - connect_chkfree - for CHKFREE decryption
596  * - connect_ezcard - for EasyCard decryption
597  * - connect_certegy - for Certegy decryption
598  * - connect_digital - for digital mailer decryption
599  * - connect_ipay - for IPAY decryption
600  * - connect_vsoft - for VSHO/VSOFT decryption
601  * - connect_mvi - for MVI images decryption
602  *
603  * @return string decrypted text
604  */
605 function hcuOpenSSLDecrypt($message,
606  $hash,
607  $key_suffix,
608  $method='aes-256-cbc',
609  $auth_hash_algo="sha1",
610  $auth_hash_binary=true,
611  $iv="",
612  $context="default") {
613 
614  $hashKeyBilbo = GetOpenSSLKeyBilbo() . $key_suffix;
615  $ivsize = openssl_cipher_iv_length($method);
616  if ($iv != "") {
617  $iv = mb_substr($iv, 0, $ivsize, '8bit');
618  }
619 
620  // DEFAULTS
621  // OPTIONS:
622  // OPENSSL_ZERO_PADDING: expects data to be space padded before encryption
623  // OPENSSL_RAW_DATA: generate raw binary ciphertext
624  $openssl_options = OPENSSL_RAW_DATA;
625  $encryptionKey = getOpenSSLKey($key_suffix);
626  $ciphertext = $message;
627 
628  switch($context) {
629  case "connect_chkfree":
630  case "connect_ezcard":
631  case "connect_certegy":
632  case "connect_digital":
633  case "connect_ipay":
634  case "connect_vsoft":
635  case "connect_mvi":
636  $openssl_options = OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING;
637  $encryptionKey = $key_suffix;
638  break;
639 
640  case "credentials":
641  $encryptionKey = $key_suffix;
642  if ($iv == "") {
643  throw new exception("IV cannot be empty for Credentials.", 3);
644  }
645  break;
646 
647  case "default":
648  default:
649  // $expectedHash = hash_hmac($auth_hash_algo, $message, $hashKeyBilbo, $auth_hash_binary);
650  $expectedHash = getEncryptionAuthHash($message,
651  $key_suffix,
652  $auth_hash_algo,
653  $auth_hash_binary);
654  if (md5($expectedHash) !== md5($hash))
655  throw new exception("Hash doesn't match!", 2);
656 
657  // iv and ciphertext are the part of the default $message
658  $iv = mb_substr($message, 0, $ivsize, '8bit');
659  $ciphertext = mb_substr($message, $ivsize, null, '8bit');
660  }
661 
662  // DECRYPTION
663  return openssl_decrypt($ciphertext,
664  $method,
665  $encryptionKey,
666  $openssl_options,
667  $iv);
668 }
669 
670 /**
671  * function DecryptPayloadData($message, $key)
672  * Decrypts a message that was encrypted with EncryptPayloadData. The message is expected to
673  * arrive with Base64 encoding and the $nonce as the first part.
674  *
675  * @param string $encryptedMessage -- the encrypted message
676  * @param string $key -- the key used to encrypt the message
677  * @return string -- the original decrypted message
678  */
679 function DecryptPayloadData( $encodedMessage, $key )
680 {
681  $method = 'aes-256-cbc';
682 
683  // NOTE: The base64 value "+" might have been decoded by a browser to " "; change back
684  $encodedMessage = str_replace( " ", "+", $encodedMessage );
685 
686  $parts = explode( "|", $encodedMessage );
687 
688  $nonce = base64_decode( $parts[0] );
689  $encryptedMessage = base64_decode( $parts[1] );
690 
691  try {
692  $result = openssl_decrypt( $encryptedMessage, $method, $key, OPENSSL_RAW_DATA, $nonce );
693  } catch (Exception $e) {
694  $result = "Decryption failure";
695  }
696 
697  return $result;
698 } // end DecryptPayloadData
699 
700 /**
701  * Indicate in a platform independent way if the application is encrypted
702  * or more aptly behind and encryption proxy.
703  */
704 function HCU_http_encrypted() {
705  return !!((!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
706  $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||
707  (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'));
708 }
709 
710 /**
711  * This function should return account found when not using a MIR packet
712  *
713  * @param integer $pDbh database handle to retrieve the values
714  * @param string $pMemberAcct The Member Account Number we are seeking
715  *
716  * @return array
717  * status
718  * code - 102 successful - account was found
719  * 001 Member Number NOT Found
720  * 999 Error - General Error
721  *
722  * data - when [status][code] = 102
723  * [accountnumber]
724  * [accounts] (always returned)
725  * [deposit][array] (structure is missing if there are NO results)
726  * [accountnumber] - should match above
727  * [accounttype] - deposits accounttype
728  * [certnumber] - deposits only
729  * [deposittype] - {Y-Share Draft, N-Shares, O-Other, C-Certificate}
730  * [description] - Description from the core
731  * [may_deposit] - Does the account allow deposits? (DEPOSIT ONLY)
732  * [may_withdraw] - Does the account allow Withdrawals? (DEPOSIT ONLY)
733  * [may_payment] _ Does the Loan allow payume
734  * [loan][array] (structure is missing if there are NO results)
735  * [accountnumber] - should match above
736  * [loannumber] - loannumber
737  * [description] - Description from the core
738  * [may_payment] - Does the Loan allow payume
739  * [may_addon] - Does the account allow deposits? (DEPOSIT ONLY)
740  *
741  */
742 function FindMemberAccountsWoMIR($pDbh, $pCu, $pMemberAcct) {
743 
744  $retAry = Array("code" => "000", "errors" => Array(), "data" => Array());
745 
746  $admLibrary= dirname(__FILE__) . "/../../admcom/library";
747 
748  $retData = Array();
749  try {
750 
751  require_once ("$admLibrary/MbrExHcuMIR.i");
752 
753  // ** I want to look up data in the <cucode>useraccounts table
754  $dms_ok = array('accountnumber' => 'digits',
755  'firstname' => 'string',
756  'middlename' => 'string',
757  'lastname' => 'string',
758  'email' => 'string',
759  'homephone' => 'string',
760  'workphone' => 'string',
761  'cellphone' => 'string',
762  'fax' => 'string',
763  'ssn' => 'string',
764  'address1' => 'string',
765  'address2' => 'string',
766  'city' => 'string',
767  'state' => 'string',
768  'zip' => 'string',
769  'cc' => 'string',
770  'dob' => 'string');
771 
772  /*
773  * SEARCH FOR MEMBER ACCOUNT IN <cucode>useraccounts table
774  */
775  $sql = "SELECT user_id, trim(accountnumber) as accountnumber
776  FROM " . prep_save($pCu, 10) . "useraccounts
777  WHERE accountnumber = '" . prep_save($pMemberAcct, 12) . "'
778  LIMIT 1; ";
779  // ** THERE SHOULD ONLY BE ONE, I AM PROGRAMMING AS ONLY ONE
780 
781  $sqlRs = db_query($sql, $pDbh);
782  if (!($sqlRs)) {
783  throw new ErrorException("SQL failed ($sql).");
784  }
785 
786  if (db_num_rows($sqlRs) > 0) {
787 
788  // account record found, but return 102 so we know this was without MIR
789  $retAry['code'] = "102";
790  // * fetch the record
791  $dRecord = db_fetch_assoc($sqlRs);
792 
793  $retData['accounts'] = Array();
794  /*
795  * FETCH DEPOSIT ACCOUNTS
796  */
797  $sql = "SELECT trim(accountnumber) as accountnumber, trim(accounttype) as accounttype,
798  certnumber, deposittype, trim(description) as description, may_deposit, may_withdraw
799  FROM " . prep_save($pCu, 10) . "accountbalance
800  WHERE accountnumber = '" . prep_save($pMemberAcct, 12) . "'
801  ORDER BY accounttype; ";
802  $sqlRs = db_query($sql, $pDbh);
803 
804  if (db_num_rows($sqlRs) > 0) {
805  $acctList = Array();
806  while ($dRecord = db_fetch_assoc($sqlRs)) {
807  $acctList[] = $dRecord;
808  }
809  $retData['accounts']['deposit'] = $acctList;
810  }
811  /*
812  * FETCH LOAN ACCOUNTS
813  */
814  $sql = "SELECT trim(accountnumber) as accountnumber, trim(loannumber) as loannumber,
815  trim(description) as description, may_payment, may_addon
816  FROM " . prep_save($pCu, 10) . "loanbalance
817  WHERE accountnumber = '" . prep_save($pMemberAcct, 12) . "'
818  ORDER BY loannumber; ";
819  $sqlRs = db_query($sql, $pDbh);
820 
821  if (db_num_rows($sqlRs) > 0) {
822  $acctList = Array();
823  while ($dRecord = db_fetch_assoc($sqlRs)) {
824  $acctList[] = $dRecord;
825  }
826  $retData['accounts']['loan'] = $acctList;
827  }
828  } else {
829  // ** NO ACCOUNT FOUND 001
830  $retAry['code'] = "001";
831  }
832 
833  $retAry['data'] = $retData;
834 
835 
836  } catch (ErrorException $err) {
837  $retAry['code'] = '999';
838  $retAry['errors'] = 'An unexpected error occurred' . $err->getMessage();
839  }
840 
841  return $retAry;
842 }
843 
844 /**
845  *
846  * Create a function that will Emulate the process of going to the core to get data to retrieve a member account and the list of accounts
847  * This function should return data similar to how we expect when we start using a live interface
848  *
849  * @param integer $pDbh database handle to retrieve the values
850  * @param string $pMemberAcct The Member Account Number we are seeking
851  *
852  * @return array
853  *
854  * status
855  * code - 101 successful - member was found ** Standard throtlpkt resposne for successful result
856  * 001 Member Number NOT Found
857  * 999 Error - General Error
858  *
859  * data - when [status][code] = 101
860  * [mir]
861  * [accountnumber]
862  * [firstname]
863  * [middlename]
864  * [lastname]
865  * [email]
866  * [homephone]
867  * [workphone]
868  * [cellphone]
869  * [fax]
870  * [ssn]
871  * [address1]
872  * [address2]
873  * [city]
874  * [state]
875  * [zip]
876  * [cc]
877  * [dob]
878  * [accounts] (always returned)
879  * [deposit][array] (structure is missing if there are NO results)
880  * [accountnumber] - should match above
881  * [accounttype] - deposits accounttype
882  * [certnumber] - deposits only
883  * [deposittype] - {Y-Share Draft, N-Shares, O-Other, C-Certificate}
884  * [description] - Description from the core
885  * [may_deposit] - Does the account allow deposits? (DEPOSIT ONLY)
886  * [may_withdraw] - Does the account allow Withdrawals? (DEPOSIT ONLY)
887  * [may_payment] _ Does the Loan allow payume
888  * [loan][array] (structure is missing if there are NO results)
889  * [accountnumber] - should match above
890  * [loannumber] - loannumber
891  * [description] - Description from the core
892  * [may_payment] - Does the Loan allow payume
893  * [may_addon] - Does the account allow deposits? (DEPOSIT ONLY)
894  *
895  */
896 function SpoofFindMemberAccounts($pDbh, $pCu, $pMemberAcct) {
897 
898  $retAry = Array("code" => "000", "errors" => Array(), "data" => Array());
899 
900  $admLibrary= dirname(__FILE__) . "/../../admcom/library";
901 
902  $retData = Array();
903  try {
904 
905  require_once ("$admLibrary/MbrExHcuMIR.i");
906 
907  // ** I want to look up data in the <cucode>extkey table where providermode = 'HcuMIR'
908  $dms_ok = array('accountnumber' => 'digits',
909  'firstname' => 'string',
910  'middlename' => 'string',
911  'lastname' => 'string',
912  'email' => 'string',
913  'homephone' => 'string',
914  'workphone' => 'string',
915  'cellphone' => 'string',
916  'fax' => 'string',
917  'ssn' => 'string',
918  'address1' => 'string',
919  'address2' => 'string',
920  'city' => 'string',
921  'state' => 'string',
922  'zip' => 'string',
923  'cc' => 'string',
924  'dob' => 'string');
925 
926  /*
927  * SEARCH FOR MEMBER ACCOUNT IN <cucode>extkey table
928  */
929  $sql = "SELECT user_id, trim(accountnumber) as accountnumber, parms
930  FROM " . prep_save($pCu, 10) . "extkey
931  WHERE providermode = 'HcuMIR'
932  AND accountnumber = '" . prep_save($pMemberAcct, 12) . "'
933  LIMIT 1; ";
934  // ** THERE SHOULD ONLY BE ONE, I AM PROGRAMMING AS ONLY ONE
935 
936  $sqlRs = db_query($sql, $pDbh);
937  if (!($sqlRs)) {
938  throw new ErrorException("SQL failed ($sql).");
939  }
940 
941  if (db_num_rows($sqlRs) > 0) {
942 
943  $retAry['code'] = "101";
944  // * fetch the record
945  $dRecord = db_fetch_assoc($sqlRs);
946 
947  // * Get the
948  $payload = $HcuMIRi->parms_parse($dRecord['parms']);
949 
950  // fix up the dob to match what import will do
951  $payload["dob"] = date( "m/d/Y", strtotime( $payload["dob"] ) );
952 
953  $retData['mir'] = $payload;
954  $retData['accounts'] = Array();
955  /*
956  * FETCH DEPOSIT ACCOUNTS
957  */
958  $sql = "SELECT trim(accountnumber) as accountnumber, trim(accounttype) as accounttype,
959  certnumber, deposittype, trim(description) as description, may_deposit, may_withdraw
960  FROM " . prep_save($pCu, 10) . "accountbalance
961  WHERE accountnumber = '" . prep_save($pMemberAcct, 12) . "'
962  ORDER BY accounttype; ";
963  $sqlRs = db_query($sql, $pDbh);
964 
965  if (db_num_rows($sqlRs) > 0) {
966  $acctList = Array();
967  while ($dRecord = db_fetch_assoc($sqlRs)) {
968  $acctList[] = $dRecord;
969  }
970  $retData['accounts']['deposit'] = $acctList;
971  }
972  /*
973  * FETCH LOAN ACCOUNTS
974  */
975  $sql = "SELECT trim(accountnumber) as accountnumber, trim(loannumber) as loannumber,
976  trim(description) as description, may_payment, may_addon
977  FROM " . prep_save($pCu, 10) . "loanbalance
978  WHERE accountnumber = '" . prep_save($pMemberAcct, 12) . "'
979  ORDER BY loannumber; ";
980  $sqlRs = db_query($sql, $pDbh);
981 
982  if (db_num_rows($sqlRs) > 0) {
983  $acctList = Array();
984  while ($dRecord = db_fetch_assoc($sqlRs)) {
985  $acctList[] = $dRecord;
986  }
987  $retData['accounts']['loan'] = $acctList;
988  }
989  } else {
990  // ** NO ACCOUNT FOUND 001
991  $retAry['code'] = "001";
992  }
993 
994  $retAry['data'] = $retData;
995 
996 
997  } catch (ErrorException $err) {
998  $retAry['code'] = '999';
999  $retAry['errors'] = 'An unexpected error occurred' . $err->getMessage();
1000  }
1001 
1002  return $retAry;
1003 }
1004 
1005 
1006 /*
1007  * @uses Ecoding sensitive data for passing through the client side.
1008  *
1009  * this function will take an associative array of data, use the keys
1010  * found in the array to build a new array of the same key/value pairs
1011  * to encode.
1012  *
1013  * this seems redundant at the moment, but in the future we may want to
1014  * have a flag in an array to say don't include this key or value.
1015  *
1016  * this function will take a json string and decode it into an associative
1017  * array to be encoded as above.
1018  *
1019  * @param $pCu string : credit union code to be used in encrypting data.
1020  * @param $pMessage array : associative array of data to encrypt.
1021  * @param $pMessage string : json encoded string containing data to encrypt.
1022  * @param $pJson boolean : flag to tell the function if the message needs to be
1023  * decoded from a json string into an asociative array.
1024  *
1025  * @return $eReturn array
1026  */
1027 function HCU_PayloadEncode($pCu, $pMessage, $pJson = false) {
1028  $eReturn = null;
1029  $eEncode = array();
1030  $eMessage = $pMessage;
1031 
1032  if ($pJson) {
1033  $eJson = html_entity_decode($eMessage, ENT_QUOTES);
1034  $eJson = trim($eMessage);
1035 
1036  if ($eJson == "") { throw new Exception("Payload not found."); }
1037  $eMessage = HCU_JsonDecode($eJson);
1038  }
1039 
1040  if (!is_array($eMessage)) { throw new Exception("Payload not found."); }
1041  if (!count($eMessage)) { throw new Exception("Payload not found."); }
1042 
1043  foreach ($eMessage as $key => $value) {
1044  $eEncode[$key] = $value;
1045  }
1046 
1047  $eEncode = HCU_JsonEncode($eEncode);
1048  $eEncrypt = hcuOpenSSLEncrypt($eEncode, $pCu);
1049  $eGlue = GetPayloadGlue();
1050 
1051  $eReturn = $eEncrypt['message'] . $eGlue . $eEncrypt['hash'];
1052  $eReturn = base64_encode($eReturn);
1053 
1054  return $eReturn;
1055 }
1056 
1057 /**
1058  * @uses this function is used to decrypt a payload of data encoded by the function
1059  * above.
1060  *
1061  * @param $pCu string : credit union code use to encrypt data
1062  * @param $pMessage string : encrypted data
1063  * @param $pJson boolean : flag to determine return type
1064  *
1065  * @return $dDecrypt array || json
1066  */
1067 function HCU_PayloadDecode($pCu, $pMessage, $pJson = false) {
1068  $dMessage = base64_decode($pMessage);
1069  $dEncode = explode("|*|*|*|", $dMessage);
1070 
1071  $dMessage = $dEncode[0];
1072  $dHash = $dEncode[1];
1073 
1074  $dDecrypt = hcuOpenSSLDecrypt($dMessage, $dHash, $pCu);
1075  $dReturn = $pJson ? $dDecrypt : HCU_JsonDecode($dDecrypt);
1076 
1077  return $dReturn;
1078 }
1079 
1080 /**
1081  * Get the liveserver from the cuadmin table and return value
1082  *
1083  * @param array $pHbEnv -- The current HB_ENV . Need the following
1084  * Cu - Credit Union Code
1085  * dbh - database handle
1086  *
1087  * @return mixed The liveserver value from the database
1088  * if there is an error with the value, it will return a false
1089  *
1090  */
1091 function GetHculiveUrl($pHbEnv) {
1092 
1093  $retVal = "";
1094 
1095 
1096  try {
1097  // ** Some things to check
1098  /* ** CREDIT UNION CODE ** */
1099  $cu = strtoupper(HCU_array_key_value("Cu", $pHbEnv));
1100  if ($cu == '') {
1101  // ** Credit union code is blank
1102  throw new ErrorException("CU Code Not Set");
1103  }
1104 
1105  /* ** DATABASE CONNECTION ** */
1106  $dbh = HCU_array_key_value("dbh", $pHbEnv);
1107 
1108  if (db_connection_status($dbh) !== PGSQL_CONNECTION_OK) {
1109  // * * database handle is bad -- throw error
1110  throw new ErrorException("Bad Database Connection");
1111  }
1112 
1113 
1114 
1115  $sql = "SELECT cu, liveserver
1116  FROM cuadmin
1117  WHERE cu = '" . prep_save($cu, 10) . "' ";
1118  $rs = db_query($sql, $dbh);
1119  if ($rs) {
1120  /* ** Recordset Value ** */
1121  $row = db_fetch_assoc($rs);
1122 
1123  // ** We now should have the value -- Get the value from the row associative array
1124  $retVal = HCU_array_key_value("liveserver", $row);
1125  }
1126 
1127  } catch (ErrorException $err) {
1128  $retVal = false;
1129  }
1130 
1131  return $retVal;
1132 }
1133 /**
1134  * Get a consistent confirmation code based on the data passed in. There are three cases:
1135  * 1. immediate processing of internal transfer,
1136  * 2. background processing of a scheduled transfer,
1137  * 3. admin processing of an ACH transaction
1138  *
1139  * For (1) the processed_by field will be NULL so we will use the posted_by id.
1140  * For (2) the processed_by field will have "scheduled" so we will use the posted_by id.
1141  * For (3) the processed_by field will have the admin username. For this one the name can be part of the confirmation code.
1142  *
1143  * Also need to include the type of confirmation code so know how to reverse it.
1144  *
1145  * Build the confirmation code using the epoch integer for time, converted to hex, the transaction id, the user id, and which type
1146  * of confirmation code. Return in a concatinated string that is broken up in quads from right to left to account for increasing
1147  * size of the ids.
1148  *
1149  * It is possible the code is being built right after the "which" operation completed as return data, or it is possible the code is
1150  * being built after the fact and the caller just read the <cu>transhdr table. The fields passed in will be named as if the table
1151  * was just read, but the caller could just supply the minimum necessary.
1152  * NOTE: the date is the date stamp that would be in the table.
1153  *
1154  * @param string $pWhich -- posted, approved, processed
1155  * @param array $pTransInfo -- array with necessary information (depends on which code needed)
1156  *
1157  * @return False if unsuccessful; string if successful
1158  */
1159 function GetTransferConfirmCode( $pEnv, $pWhich, $pTranInfo ) {
1160 
1161  try {
1162  switch( $pWhich ) {
1163  case "post":
1164  // Posted pattern: Pan1bn2cn3, where a = # hex digits n1 (in hex), b = # hex digits n2, c = # hex digits n3
1165  // and n1 = trans id, n2 = date timestamp, n3 = user id
1166  $n1hex = dechex( $pTranInfo["id"] );
1167  $n1len = strlen( $n1hex );
1168 
1169  $n2hex = dechex( strtotime( $pTranInfo["posted_date"] ) );
1170  $n2len = strlen( $n2hex );
1171 
1172  $n3hex = dechex( $pTranInfo["posted_by"] );
1173  $n3len = strlen( $n3hex );
1174 
1175  $intermediateCode = "P" . $n1len . $n1hex . $n2len . $n2hex . $n3len . $n3hex;
1176  break;
1177  case "approve":
1178  // Approved pattern: Aan1bn2cn3, where a = # hex digits n1, b = # hex digits n2, c = # hex digits n3
1179  // and n1 = trans id, n2 = date timestamp, n3 = user id
1180  $n1hex = dechex( $pTranInfo["id"] );
1181  $n1len = strlen( $n1hex );
1182 
1183  $n2hex = dechex( strtotime( $pTranInfo["approved_date"] ) );
1184  $n2len = strlen( $n2hex );
1185 
1186  $n3hex = dechex( $pTranInfo["approved_by"] );
1187  $n3len = strlen( $n3hex );
1188 
1189  $intermediateCode = "A" . $n1len . $n1hex . $n2len . $n2hex . $n3len . $n3hex;
1190  break;
1191  case "process":
1192  // Processed pattern: Ran1bn2user, where a = # hex digits n1, b = # hex digits n2, c = # hex digits n3
1193  // and n1 = trans id, n2 = date timestamp, user = admin user name
1194  $n1hex = dechex( $pTranInfo["id"] );
1195  $n1len = strlen( $n1hex );
1196 
1197  $n2hex = dechex( strtotime( $pTranInfo["processed_date"] ) );
1198  $n2len = dechex( strlen( $n2hex ) );
1199 
1200  // this depends on the value
1201  if ( $pTranInfo["processed_by"] == "*immed*" ||
1202  $pTranInfo["processed_by"] == "*sched*" ) {
1203  // immediate or scheduled transfer, use the posted_by
1204  $whoProcessed = dechex( $pTranInfo["posted_by"] );
1205  $leadingCode = ( $pTranInfo["processed_by"] == "*immed*" ) ? "I" : "S";
1206  } else {
1207  // use the contents since likely an admin
1208  $whoProcessed = $pTranInfo["processed_by"] ;
1209  $leadingCode = "R";
1210  }
1211 
1212  $intermediateCode = $leadingCode . $n1len . $n1hex . $n2len . $n2hex . $whoProcessed;
1213  break;
1214  default:
1215  throw new Exception ( "Bad call to create confirmation code" );
1216  break;
1217  }
1218 
1219  // make all caps
1220  $upperCode = strtoupper( $intermediateCode );
1221 
1222  // break up string for output
1223  $aryConfirmId = str_split( $upperCode, 4 );
1224  $confirmId = implode( "-", $aryConfirmId );
1225 
1226  } catch(Exception $ex) {
1227  $logInfo = array( "message" => $ex->getMessage(), "code" => $ex->getCode() );
1228  $pEnv["SYSENV"]["logger"]->info( HCU_JsonEncode( $logInfo ) );
1229 
1230  $confirmId = false;
1231  }
1232 
1233  return $confirmId;
1234 
1235 } // end GetTransferConfirmCode
1236 
1237 // unresolved - This is just a stub so things work. At some point this function goes away and a real one replaces it in an included file.
1238 /**
1239  * This function will be the called function when making a transaction, than based on the CU configuration it will make the appropriate
1240  * requests/calls/updates
1241  * For a live CU, this will make the request to the core.
1242  * For a batch CU, this will make a different request that will insert a record into the <client>transaction table
1243  *
1244  * @param array $pHBEnv - The HB_ENV environment settings
1245  * - passed by address it could
1246  * @param string $pOpCode - (needed?) The type of transaction to be sent
1247  * @param array $pTxnValues - Contains the list of data items to be posted
1248  * {TRANSFER}
1249  * "txncode" => Transaction Code
1250  "source_sub_account" => Source Sub Account
1251  "dest_sub_account" => Destination Sub Account
1252  "email" => Email
1253  "misc1" => Misc. needed for some credit card transactions
1254  "dest_account" => $destAccount, // this could be 10/20 for checking/savings
1255  "amount" => $AMT,
1256  "memo" => $TRMEMO
1257  *
1258  *
1259  *
1260  * @return array
1261  * [code] - {000 - a request was made to the core and a response was returned, 999 - an error occured and was unable to get a response from the core}
1262  * [errors] - list
1263  * [data]
1264  * [code] - The code returned from the core under normal circumstances
1265  * [desc] - The description from the core
1266  *
1267  *
1268  */
1269 function SendTransaction( $pHBEnv, $pOpCode, $pTxnValues ) {
1270  $retStatusAry = Array(
1271  "status" => Array( "code"=>'000', "errors" => Array() ),
1272  "data" => ""
1273  );
1274 
1275  $logger = $pHBEnv['SYSENV']['logger'];
1276 
1277 
1278  try {
1279  if ($pHBEnv['live']) {
1280  /**
1281  * ** LIVE CREDIT UNION
1282  * ** REAL TIME POST TO CORE INTERFACE
1283  */
1284  /**
1285  * ** PRE PROCESS
1286  */
1287  switch ($pOpCode) {
1288  case "TRANSFER":
1289  $pTxnValues['pkt-type'] = PACKET_REQUEST_TRN;
1290  break;
1291  case "MEMBERACTIVATE":
1292  $pTxnValues['pkt-type'] = PACKET_REQUEST_MA;
1293  break;
1294  case "ESTMTACTIVATE":
1295  $pTxnValues['pkt-type'] = PACKET_REQUEST_ES;
1296  break;
1297  case "ESTMTSTOP":
1298  $pTxnValues['pkt-type'] = PACKET_REQUEST_ES;
1299  break;
1300  default:
1301  // ** ERROR - Operation NOT SPECIFIED
1302  throw new Exception ("No Operation Selection");
1303  }
1304  $reqResult = PostTransactionRequest($pHBEnv, $pTxnValues);
1305  if ($reqResult['code'] == '999') {
1306  throw new Exception ("Unexpected Error");
1307  }
1308 
1309  // ** Success -- SET the response of this function to match from the CORE
1310  $retStatusAry['status']['code'] = HCU_array_key_value("code", $reqResult['data']);
1311 
1312  // ** Assume the transfer failed UNLESS the code returned is 000
1313  if ($retStatusAry['status']['code'] != '000') {
1314  $retStatusAry['status']['errors'][] = HCU_array_key_value("desc", $reqResult['data']);
1315  }
1316  $retStatusAry['data'] = HCU_array_key_value("data", $reqResult);
1317 
1318  /**
1319  * **
1320  * ** POST PROCESSING **
1321  * **
1322  */
1323  switch ($pOpCode) {
1324  case "TRANSFER":
1325 
1326  /* ** Update the packet stamp for the user.
1327  * ** This has the affect of forcing the bal/history to update on the next
1328  * ** Get_Balances call
1329  */
1330  if (HCU_array_key_value('code', $retStatusAry['data']) == '000') {
1331 
1332  // * ONLY UPDATE ON SUCCESS
1333  // * *Create an array with the member numbers
1334  $userData = Array("accountnumbers" => Array(HCU_array_key_value("member", $pTxnValues)));
1335 
1336  if (HCU_array_key_value("member", $pTxnValues) != HCU_array_key_value("ref5", $pTxnValues)) {
1337  // If the From/To member are different then add the To member
1338  // userData was declared an array above
1339  $userData['accountnumbers'][] = HCU_array_key_value("ref5", $pTxnValues);
1340  }
1341 
1342  // ** If the transfer is between two overloaded accounts, then the main account is not
1343  // the [from / to] member. I need to also check the 'tauth' member to see if it's
1344  // different. If it is, then include the tauth member in the list of accounts to
1345  // retrieve.
1346  if (HCU_array_key_value("tauth", $pTxnValues) != '' && HCU_array_key_value("member", $pTxnValues) != HCU_array_key_value("tauth", $pTxnValues)) {
1347  $userData['accountnumbers'][] = HCU_array_key_value("tauth", $pTxnValues);
1348  }
1349 
1350  $updResp = SetbackMemberStamps($pHBEnv, $pHBEnv['Uid'], $userData);
1351  // * Not much that can be done if this fails.. so let it go.
1352  }
1353  break;
1354  case "MEMBERACTIVATE":
1355  /* ** NOTHING ** */
1356  break;
1357  }
1358  } else {
1359  /**
1360  * ** BATCH CREDIT UNION
1361  * ** INSERT RECORD INTO <client>transaction table
1362  */
1363 /*
1364  $sql = "insert into {$Cu}transaction (
1365  accountnumber, memberto, transactioncode,
1366  reference1, reference2, reference3,
1367  amount, date)
1368  values ('$acct', '$R5', '$tc', '$R1', '$R2', '" .
1369  prep_save(urldecode($R3)) . "'," .
1370  $AMT . ", now());";
1371  $sql.="select currval('${Cu}transaction_id_seq')";
1372 
1373 */
1374  }
1375  } catch (Exception $e) {
1376  // ** Unexpected Error Occurred
1377  $retStatusAry['status']['code'] = '999';
1378  $retStatusAry['status']['errors'] = 'Unexpected Error';
1379  $retStatusAry['data'] = Array();
1380  }
1381 
1382 
1383  return $retStatusAry;
1384 } // end SendTransaction
1385 
1386 /**
1387  * Reverse the confirmation code to get the identifying data.
1388  *
1389  * @param string $pEnv -- environment info with at least the db handle
1390  * @param string $pCUCode -- the credit union code (to get to correct table)
1391  * @param string $pConfirmCode -- the confirmation code
1392  *
1393  * @return structure with user name, transaction date, <cu>transhdr id
1394  */
1395 function ReverseTransferConfirmCode( $pEnv, $pCUCode, $pConfirmCode ) {
1396 
1397  try {
1398  $condensedCode = strtoupper( str_replace( "-", "", $pConfirmCode ) );
1399 
1400  // get all the common information first
1401  $which = substr( $condensedCode, 0, 1 );
1402 
1403  $currOffset = 1;
1404  $n1len = hexdec( substr( $condensedCode, $currOffset, 1 ) );
1405 
1406  if ( !$n1len ) {
1407  throw new Exception ( "Bad value when reversing confirmation code" );
1408  }
1409 
1410  $currOffset++;
1411  $transId = hexdec( substr( $condensedCode, $currOffset, $n1len ) );
1412 
1413  $currOffset += $n1len;
1414 
1415  $n2len = hexdec( substr( $condensedCode, $currOffset, 1 ) );
1416 
1417  if ( !$n2len ) {
1418  throw new Exception ( "Bad value when reversing confirmation code" );
1419  }
1420 
1421  $currOffset++;
1422  $txnStamp = hexdec( substr( $condensedCode, $currOffset, $n2len ) );
1423  $txnString = date( "m/d/Y h:ia", $txnStamp );
1424 
1425  $currOffset += $n2len;
1426  $n3len = hexdec( substr( $condensedCode, $currOffset, 1 ) );
1427 
1428  if ( !$n3len ) {
1429  throw new Exception ( "Bad value when reversing confirmation code" );
1430  }
1431 
1432  $currOffset++;
1433 
1434  switch( $which ) {
1435  case "P":
1436  // posted
1437  case "A":
1438  // accepted
1439  case "I":
1440  // posted immediately (uses user id)
1441  case "S":
1442  // posted by the scheduler (uses user id)
1443  $userId = hexdec( substr( $condensedCode, $currOffset, $n3len ) );
1444 
1445  if ( !$userId ) {
1446  throw new Exception ( "Bad user id when reversing confirmation code" );
1447  }
1448 
1449  // look up user name
1450  $sql = "SELECT user_name
1451  FROM {$pEnv["Cu"]}user
1452  WHERE user_id = $userId";
1453  $rs = db_query( $sql, $pEnv["dbh"] );
1454  list( $username ) = db_fetch_array( $rs );
1455  db_free_result( $rs );
1456 
1457  // return the correct operation
1458  if ( $which == "P" ) {
1459  $operation = "Posted";
1460  } else if ( $which == "A" ) {
1461  $operation = "Accepted";
1462  } else if ( $which == "I" ) {
1463  $operation = "Immediate Posted";
1464  } else if ( $which == "S" ) {
1465  $operation = "Scheduled Posted";
1466  }
1467  break;
1468  case "R":
1469  // processed by admin (has admin username)
1470  $currOffset++;
1471  $adminUsername = substr( $condensedCode, $currOffset, $n3len );
1472 
1473  $operation = "Processed";
1474  $userId = 0;
1475  $username = $adminUsername;
1476  break;
1477  default:
1478  throw new Exception ( "Bad call to reverse confirmation code" );
1479  break;
1480  }
1481 
1482  $returnParts = array( "operation" => $operation,
1483  "date" => $txnString,
1484  "id" => $transId,
1485  "name" => $username );
1486 
1487  } catch(Exception $ex) {
1488  $returnParts = false;
1489  }
1490 
1491  return $returnParts;
1492 
1493 } // end ReverseTransferConfirmCode
1494 
1495 // Get the timezone for the credit union (from the database).
1496 function GetCreditUnionTimezone( $pDbh, $pCu ) {
1497  // get the timezone for the CU
1498  $sql = "select rtrim(tz) from cuadmin where cu= '$pCu'";
1499  $sth = db_query( $sql, $pDbh );
1500  if ( $sth ) { list($tz) = db_fetch_array( $sth, 0 ); }
1501  $tz = ("$tz" == "" ? "Mountain" : $tz);
1502  if (strpos("$tz","/") === false) { $tz = "US/$tz"; }
1503 
1504  return $tz;
1505 } // end GetCreditUnionTimezone
1506 
1507 /**
1508  * Apply a timezone to an epoch using the supplied format
1509  *
1510  * @param integer $pEpoch - The date represented in second from 1/1/1970
1511  * @param string $pFormat - The desired format for the date representation
1512  * @param string $pTz - The Timezone the date should use with the format
1513  *
1514  * @return string
1515  */
1516 function GetDateFormatTimezone($pEpoch, $pFormat, $pTz) {
1517  try {
1518 
1519  if ($pEpoch == 0) {
1520  throw new Exception ("No Date to Format");
1521  }
1522 
1523  // ** To create this format from an epoch use the createFromFormat and specify U for "seconds since epoch"
1524  $srcFormat = "U";
1525  $myDateTime = DateTime::createFromFormat($srcFormat, $pEpoch);
1526 
1527  $myDateTime->setTimezone(new DateTimeZone($pTz));
1528 
1529  $formatted = $myDateTime->format($pFormat);
1530  } catch (exception $e) {
1531  // ** On Error Set the formatted to empty string
1532  $formatted = '';
1533  }
1534 
1535  return $formatted;
1536 }
1537 
1538 /**
1539  * Function to explode the Acct ID value into the appropriate pieces
1540  *
1541  * @param $pAcctId - the AcctId -- this will be formatted similar to:
1542  * D|ACCTNBR|ACCTSFX|CERTNO
1543  * L|ACCTNBR|LOANNBR
1544  * @return array
1545  * [valid] - Did the value appear to be valid
1546  * [original] - Original value that was being parsed
1547  * [type] - {D, L}
1548  * [acctnumber] - Member number for account
1549  * [segment3] - Deposit - Account Suffix; Loan - Loan Number
1550  * [segment4] - Deposit - Cert Number; Loan - Empty
1551  */
1552 function HCU_AcctIdExplode($pAcctId) {
1553  $retAcctAry = Array("valid" => false, "original" => $pAcctId, "type" => "", "acctnumber" => "", "segment3" => "", "segment4" => "");
1554 
1555  $acctIdType = substr($pAcctId, 0, 1);
1556 
1557  switch (strtoupper($acctIdType)) {
1558  case "D":
1559  // ** Deposit
1560  if (substr_count($pAcctId, "|") === 3) {
1561  list($retAcctAry['type'], $retAcctAry['acctnumber'], $retAcctAry['segment3'], $retAcctAry['segment4']) = explode("|", $pAcctId);
1562  $retAcctAry['valid'] = true;
1563  } // ** ELSE FAIL
1564 
1565  break;
1566  case "L":
1567  case "O":
1568  case "P":
1569  case "C":
1570  // ** Loan
1571  if (substr_count($pAcctId, "|") === 2) {
1572  list($retAcctAry['type'], $retAcctAry['acctnumber'], $retAcctAry['segment3']) = explode("|", $pAcctId);
1573  $retAcctAry['valid'] = true;
1574  } // ** ELSE FAIL
1575  break;
1576  case "M":
1577  // ** Member to Member transfer destination
1578  // M|999999|{10,20}
1579  if (substr_count($pAcctId, "|") === 2) {
1580  list($retAcctAry['type'], $retAcctAry['acctnumber'], $retAcctAry['segment3']) = explode("|", $pAcctId);
1581  $retAcctAry['valid'] = true;
1582  } // ** ELSE FAIL
1583 
1584  }
1585 
1586  return $retAcctAry;
1587 }
1588 
1589 
1590 /**
1591  * Function: GetAwsCertFile
1592  *
1593  * This function will call the python script aws_get_certificate.py that will
1594  * in turn fetch the "key" from AWS secrets manager to unlock the file
1595  * and save in the local container
1596  *
1597  * @param string $pCertSecretId - the secret id used to identify the cert information
1598  * in aws secrets manager.
1599  * @param string $pEncFileBaseDir - the base directory for the secret id eg (/home/homecu/ssl/).
1600  * @param string $pOutBaseDir - the base directory where the certs will be saved.
1601  *
1602  * @return string This will return the path to the cert where it was saved in the container.
1603  */
1604 function GetAwsCertFile($pCertSecretId, $pEncFileBaseDir, $pOutBaseDir) {
1605 
1606  $retCertFileLocation = '';
1607 
1608  // ** Location of the python script to be executed.
1609  $getCertPy = "/opt/odyssey/tools/bin/aws_get_certificate.py";
1610 
1611  try {
1612  // ** VALIDATION **
1613  // * Validate the Encrypted file exists
1614  $encFileLocation = $pEncFileBaseDir . $pCertSecretId;
1615  if (!is_readable($encFileLocation)) {
1616  throw new Exception("Encrypted file not found.");
1617  }
1618 
1619  $expectedFileLocation = $pOutBaseDir . $pCertSecretId;
1620  /* **
1621  * Is the file found and readable?
1622  * YES, then return
1623  *
1624  * The file being readable assumes that any problems during decryption will result
1625  * in the file NOT existing. If we find that this one check is not enough then
1626  * we may need to make system calls to validate the file was correctly decrypted.
1627  */
1628  if (is_readable($expectedFileLocation)) {
1629  return $expectedFileLocation;
1630  }
1631 
1632  /* ** Build shell command ** */
1633  // ** Parameters **
1634  $certPyArgs = " '" . escapeshellarg($encFileLocation) . "' '" . escapeshellarg($pCertSecretId). "'";
1635 
1636  // ** Put it together
1637  $shellPyCmd = "python3 " . escapeshellcmd($getCertPy) . " " . $certPyArgs;
1638 
1639  $execResults = exec($shellPyCmd);
1640 
1641  // ** We are back -- Verify the expected file was found
1642  if (is_readable($expectedFileLocation)) {
1643  /* ** SUCCESS ** */
1644  $retCertFileLocation = $expectedFileLocation;
1645  }
1646 
1647  } catch (Exception $ex ) {
1648  /** ** ERROR ** */
1649  // * Ensure the returned location is blank.
1650  $retCertFileLocation = '';
1651  }
1652 
1653  return $retCertFileLocation;
1654 }
1655 /**
1656  * Load HB_ENV array (passed by reference) with values from the cuusers / cuadmin records
1657  * for the Cu / Member given.
1658  *
1659  * Intended for use by scripts outside the "normal" HB channel - OFXRequest, MoneyDesk,
1660  * and friends - to load the environment array in a standard fashion
1661  *
1662  * 4/3/18 Moved out of OFXRequest / into this include so it can be shared
1663  * 4/3/18 Replaces in-line query and processing in RemoteAuth
1664  * TBD Replace in-line query and processing in MoneyDesk
1665  * @param resource $dbh database handle
1666  * @param string $CU HCU cu code / ORG id
1667  * @param string $MEMBER HCU Cu / USERID value
1668  * @param string $USERPASS password
1669  * @param string $CAUTH authenticated member; same as USERID in this context
1670  * @param numeric $live true/1 for live clients false/0 for batch
1671  * @param array $HB_ENV existing array to be populated. Passed by reference
1672  * @param numeric $CFGFLAG # not fully implemented, left for backward compatibility
1673  */
1674 function Load_HB_ENVc($dbh, $CU, $MEMBER, &$HB_ENV, $CFGFLAG = 0) {
1675  // ** First thing is First Check the username
1676  $username = trim($MEMBER);
1677  $live = $HB_ENV['live'];
1678 
1679  # on first (method MFA) login, MEMBER will have username
1680  # after that, (method SSO) MEMBER will have USERID
1681 // if ($HB_ENV['AuthMode'] == 'MFQ') {
1682  # AuthMode=MFQ Multi-factor w/questions
1683  $qby = "cuuser.user_name ilike '" . prep_save($MEMBER) . "' ";
1684 // } else {
1685 // # AuthMode=SSO w/Userkey
1686 // $qby = "cuuser.user_id = $MEMBER ";
1687 // }
1688  // ** QUERY THE USER INFORMATION
1689  $sqluser = "SELECT cuuser.user_id as user_id, trim(cuuser.user_name) as user_name,
1690  trim(cuuser.passwd) as password, forcechange, forceremain, failedremain,
1691  pwchange, trim(email) as email, egenl_flag, trim(confidence) as confidence,
1692  cuuser.user_id as cuuser_id,
1693  cuuser.group_id as cuuser_group_id, lastlogin, failedlogin, msg_tx,
1694  userflags & {$GLOBALS['MEM_FORCE_RESET']}::int4 as mem_force_reset, userflags,
1695  histdays, gracelimit, trmemomaxlen, mfaquest, primary_account
1696 
1697  FROM {$CU}user as cuuser
1698  JOIN cuadmin on cuadmin.cu = '" . prep_save($CU) . "'
1699  WHERE $qby ";
1700 
1701  $mbr_sth = db_query($sqluser, $dbh);
1702  $HB_ENV['rowfound'] = db_num_rows($mbr_sth);
1703  if ($HB_ENV['rowfound']) {
1704  $drow = db_fetch_array($mbr_sth, 0);
1705  $HB_ENV['Cu'] = $CU;
1706  $HB_ENV['cu'] = $CU;
1707  $HB_ENV['chome'] = strtolower($CU);
1708  $HB_ENV['Cauth'] = trim($drow['user_id']);
1709  $HB_ENV['Uid'] = $drow['user_id'];
1710  $HB_ENV['Cn'] = $drow['user_name'];
1711  $HB_ENV['username'] = $drow['user_name'];
1712  $HB_ENV['user_name'] = $drow['user_name'];
1713  $HB_ENV['confidence'] = $drow['confidence'];
1714 // $HB_ENV['Ml'] = urlencode($drow['email']);
1715  $HB_ENV['Ml'] = $drow['email'];
1716  $HB_ENV['savemail'] = $drow['email'];
1717  $HB_ENV['egenl_flag'] = urlencode($drow['egenl_flag']);
1718  $HB_ENV['password'] = $drow['password'];
1719  $HB_ENV['userflags'] = $drow['userflags'];
1720  $HB_ENV['failedremain'] = $drow['failedremain'];
1721  $HB_ENV['Ffchg'] = $drow['forcechange'];
1722  $HB_ENV['Ffremain'] = $drow['forceremain'];
1723  $HB_ENV['dbforceremain'] = $drow['forceremain'];
1724  $HB_ENV['Ffreset'] = (is_null($drow['mem_force_reset']) ? 0 : $drow['mem_force_reset']);
1725 // $HB_ENV['Fmsg_tx'] = ($drow['msg_tx'] | $CFGFLAG); # this would add any CFGFLAG passed to msg_tx only
1726  $HB_ENV['Fmsg_tx'] = (is_null($drow['msg_tx']) ? 0 : $drow['msg_tx']);
1727  $HB_ENV['cfgflag'] = $CFGFLAG; # set cfgflag if CFGFLAG if passed
1728  $HB_ENV['Fverifyml'] = ($drow['msg_tx'] & 512);
1729  # mammoth data calls use Clw; odyssey switched to livewait so define both
1730  $HB_ENV['Clw'] = ((is_null($HB_ENV['livewait']) || $HB_ENV['livewait'] == 0) ? 300 : $HB_ENV['livewait']);
1731  $HB_ENV['lastupdate'] = (empty($drow['lastupdate']) ? "Unknown" : urlencode(trim($drow['lastupdate'])));
1732  $HB_ENV['pwchange'] = (is_null($drow['pwchange']) ? date('Ymd') : $drow['pwchange']);
1733 // $HB_ENV['employee'] = $drow['employee'];
1734  $HB_ENV['HCUPOST'] = array(); # set empty parameter array
1735  if ($HB_ENV['flagset2'] & $GLOBALS['CU2_ALIAS_REQ']) {
1736  $alias = 'REQUIRE';
1737  } else {
1738  $alias = 'ALLOW';
1739  }
1740 // } elseif ($HB_ENV['flagset2'] & $GLOBALS['CU2_ALIAS_OK']) {
1741 // $alias = 'ALLOW';
1742 // } else {
1743 // $alias = 'NONE';
1744 // }
1745  $HB_ENV['alias'] = $alias; # this shouldn't be needed - oh but it is
1746  # alias is always allowed
1747  # required means must start with non-digit
1748  $HB_ENV['Fset'] = $HB_ENV['flagset'];
1749  $HB_ENV['Fset2'] = $HB_ENV['flagset2'];
1750  $HB_ENV['Fset3'] = $HB_ENV['flagset3'];
1751 
1752  // * Create the MFA Quest Array (or set empty if Legacy, which shouldn't happen in Odyssey...)
1753  $HB_ENV['MFA'] = ($HB_ENV['cver'] == 'L' ? array() : HCU_MFADecode(HCU_JsonDecode($drow['mfaquest'])));
1754  $HB_ENV['mfaquest'] = $drow['mfaquest'];
1755  $HB_ENV['savecqid'] = $HB_ENV['MFA']['challenge'];
1756  $HB_ENV['chcount'] = $HB_ENV['MFA']['mfacount'];
1757 
1758  $FORCEUPDATE = 0;
1759  if ($HB_ENV['Ffchg'] == 'Y') {
1760  $FORCEUPDATE += 1; #password
1761  }
1762  if ($HB_ENV['Fverifyml'] == 512 || $HB_ENV['Ml'] == '') {
1763  $FORCEUPDATE += 2; # email
1764  }
1765 
1766  if (intval($HB_ENV['flagset3'] & $GLOBALS['CU3_MFA_AUTHCODE']) == 0 &&
1767  ( $HB_ENV['Ffreset'] == 2 || $HB_ENV['chcount'] < $HB_ENV['cu_chgqst_count'] )) {
1768  $FORCEUPDATE += 4; #challenge questions
1769  }
1770 
1771  if (($HB_ENV['flagset2'] & $GLOBALS['CU2_ALIAS_REQ']) && !Check_Member_UseAlias($HB_ENV['user_name'])) {
1772  $FORCEUPDATE += 8;
1773  }
1774 
1775  if (intval($HB_ENV['flagset3'] & $GLOBALS['CU3_MFA_AUTHCODE']) > 0 && $HB_ENV['Ffreset'] == 2) {
1776  $FORCEUPDATE += 16; #phone numbers
1777  }
1778 
1779  $HB_ENV['forceupdate'] = $FORCEUPDATE;
1780  $HB_ENV['allowupdate'] = 11; # password, email, and user_name update always allowed
1781  $HB_ENV['allowupdate'] += (intval($HB_ENV['flagset3'] & $GLOBALS['CU3_MFA_AUTHCODE']) == 0 ? 4 : 0); # can't update Challenge Questions if SAC in use
1782  $HB_ENV['allowupdate'] += (intval($HB_ENV['flagset3'] & $GLOBALS['CU3_MFA_AUTHCODE']) == 0 ? 0 : 16); # can only update contact phone if SAC in use
1783 #
1784  $HB_ENV['requpdate'] = 0; # assume at first this is not a 'getsettings' request
1785 
1786  if ($HB_ENV['failedremain'] <= 0 ||
1787  ( ($HB_ENV['forceupdate'] & 29) > 0 && $HB_ENV['Ffremain'] <= 0 )
1788  ) {
1789 
1790  $HB_ENV['lockedacct'] = 1;
1791  } else {
1792  $HB_ENV['lockedacct'] = 0;
1793  }
1794 
1795 # eventually this will come from a new column in cuadmin
1796 # but for now....
1797  $HB_ENV['AppTimeout'] = intval($HB_ENV['SYSENV']['ticket']['expires'] * .8);
1798  $lastlogin = (trim(HCU_array_key_value('lastlogin', $drow)) == '' ? 'None' : $drow['lastlogin']);
1799 
1800  $HB_ENV['Fplog'] = ($lastlogin == 'None' ? '' : (strftime("%D %R", mktime(
1801  substr($lastlogin, 11, 2), substr($lastlogin, 14, 2), substr($lastlogin, 17, 2), substr($lastlogin, 5, 2), substr($lastlogin, 8, 2), substr($lastlogin, 0, 4)))));
1802  $failedlogin = (trim(HCU_array_key_value('failedlogin', $drow)) == '' ? 'None' : $drow['failedlogin']);
1803  $HB_ENV['Fflog'] = ($failedlogin == 'None' ? '' : (strftime("%D %R", mktime(
1804  substr($failedlogin, 11, 2), substr($failedlogin, 14, 2), substr($failedlogin, 17, 2), substr($failedlogin, 5, 2), substr($failedlogin, 8, 2), substr($failedlogin, 0, 4)))));
1805  }
1806 }
1807 
1808 /**
1809  * Is the current platform on an iPhone or Android app?
1810  *
1811  * This is determined with the 'platform' key of the HB_ENV
1812  * structure.
1813  *
1814  * @param $pHbEnv Common Array Structure
1815  *
1816  * @return boolean
1817  *
1818  */
1819 function hcuIsAppPhone($pHbEnv) {
1820 
1821  $platForm = HCU_array_key_value("platform", $pHbEnv);
1822 
1823  $appList = array("APP", "ADA");
1824 
1825  if (in_array($platForm, $appList)) {
1826  return true;
1827  }
1828 
1829  return false;
1830 }