diff options
author | Mario <mario@mariovavti.com> | 2017-10-25 13:29:19 +0200 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2017-10-25 13:29:19 +0200 |
commit | 344aa13c64f0fa7a246f3284bf9cdb267e6101d3 (patch) | |
tree | 943809c01164dda7b6e4a26bc3d6c7e8c48bd6eb /include/zot.php | |
parent | 61c86212b944efa0d78dcc0364b81bfb8a0d19bc (diff) | |
parent | 69b22e3f7916b5ba19b5ed03a55ad72bf5995291 (diff) | |
download | volse-hubzilla-344aa13c64f0fa7a246f3284bf9cdb267e6101d3.tar.gz volse-hubzilla-344aa13c64f0fa7a246f3284bf9cdb267e6101d3.tar.bz2 volse-hubzilla-344aa13c64f0fa7a246f3284bf9cdb267e6101d3.zip |
Merge branch '2.8RC'
Diffstat (limited to 'include/zot.php')
-rw-r--r-- | include/zot.php | 335 |
1 files changed, 280 insertions, 55 deletions
diff --git a/include/zot.php b/include/zot.php index e120755b5..37c3c1444 100644 --- a/include/zot.php +++ b/include/zot.php @@ -31,9 +31,9 @@ require_once('include/perm_upgrade.php'); * @param string $channel_nick a unique nickname of controlling entity * @returns string */ + function zot_new_uid($channel_nick) { $rawstr = z_root() . '/' . $channel_nick . '.' . mt_rand(); - return(base64url_encode(hash('whirlpool', $rawstr, true), true)); } @@ -49,6 +49,7 @@ function zot_new_uid($channel_nick) { * @param string $guid * @param string $guid_sig */ + function make_xchan_hash($guid, $guid_sig) { return base64url_encode(hash('whirlpool', $guid . $guid_sig, true)); } @@ -62,17 +63,17 @@ function make_xchan_hash($guid, $guid_sig) { * @param string $hash - xchan_hash * @returns array of hubloc (hub location structures) * * \b hubloc_id int - * * \b hubloc_guid char(255) + * * \b hubloc_guid char(191) * * \b hubloc_guid_sig text - * * \b hubloc_hash char(255) - * * \b hubloc_addr char(255) + * * \b hubloc_hash char(191) + * * \b hubloc_addr char(191) * * \b hubloc_flags int * * \b hubloc_status int - * * \b hubloc_url char(255) + * * \b hubloc_url char(191) * * \b hubloc_url_sig text - * * \b hubloc_host char(255) - * * \b hubloc_callback char(255) - * * \b hubloc_connect char(255) + * * \b hubloc_host char(191) + * * \b hubloc_callback char(191) + * * \b hubloc_connect char(191) * * \b hubloc_sitekey text * * \b hubloc_updated datetime * * \b hubloc_connected datetime @@ -97,7 +98,7 @@ function zot_get_hublocs($hash) { * @param array $channel * sender channel structure * @param string $type - * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'force_refresh', 'notify', 'auth_check' + * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'keychange', 'force_refresh', 'notify', 'auth_check' * @param array $recipients * envelope information, array ( 'guid' => string, 'guid_sig' => string ); empty for public posts * @param string $remote_key @@ -111,18 +112,21 @@ function zot_get_hublocs($hash) { */ function zot_build_packet($channel, $type = 'notify', $recipients = null, $remote_key = null, $methods = '', $secret = null, $extra = null) { + $sig_method = get_config('system','signature_algorithm','sha256'); + $data = [ 'type' => $type, 'sender' => [ 'guid' => $channel['channel_guid'], - 'guid_sig' => base64url_encode(rsa_sign($channel['channel_guid'],$channel['channel_prvkey'])), + 'guid_sig' => base64url_encode(rsa_sign($channel['channel_guid'],$channel['channel_prvkey'],$sig_method)), 'url' => z_root(), - 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'])), + 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'],$sig_method)), 'sitekey' => get_config('system','pubkey') ], 'callback' => '/post', - 'version' => ZOT_REVISION, - 'encryption' => crypto_methods() + 'version' => Zotlabs\Lib\System::get_zot_revision(), + 'encryption' => crypto_methods(), + 'signing' => signing_methods() ]; if ($recipients) { @@ -134,7 +138,7 @@ function zot_build_packet($channel, $type = 'notify', $recipients = null, $remot if ($secret) { $data['secret'] = preg_replace('/[^0-9a-fA-F]/','',$secret); - $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'])); + $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'],$sig_method)); } if ($extra) { @@ -308,6 +312,7 @@ function zot_refresh($them, $channel = null, $force = false) { logger('zot_refresh: ' . $url, LOGGER_DATA, LOG_INFO); + $result = z_post_url($url . $rhs,$postvars); if ($result['success']) { @@ -356,8 +361,6 @@ function zot_refresh($them, $channel = null, $force = false) { else $permissions = $j['permissions']; - $connected_set = false; - if($permissions && is_array($permissions)) { $old_read_stream_perm = get_abconfig($channel['channel_id'],$x['hash'],'their_perms','view_stream'); @@ -529,7 +532,7 @@ function zot_gethub($arr, $multiple = false) { } $limit = (($multiple) ? '' : ' limit 1 '); - $sitekey = ((array_key_exists('sitekey',$arr) && $arr['sitekey']) ? " and hubloc_sitekey = '" . protect_sprintf($arr['sitekey']) . "' " : ''); + $sitekey = ((array_key_exists('sitekey',$arr) && $arr['sitekey']) ? " and hubloc_sitekey = '" . dbesc(protect_sprintf($arr['sitekey'])) . "' " : ''); $r = q("select hubloc.*, site.site_crypto from hubloc left join site on hubloc_url = site_url where hubloc_guid = '%s' and hubloc_guid_sig = '%s' @@ -575,6 +578,8 @@ function zot_register_hub($arr) { if($arr['url'] && $arr['url_sig'] && $arr['guid'] && $arr['guid_sig']) { + $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]); + $guid_hash = make_xchan_hash($arr['guid'],$arr['guid_sig']); $url = $arr['url'] . '/.well-known/zot-info/?f=&guid_hash=' . $guid_hash; @@ -594,17 +599,18 @@ function zot_register_hub($arr) { * our current communication. */ - if((rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$record['key'])) - && (rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$record['key'])) + foreach($sig_methods as $method) { + if((rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$record['key'],$method)) + && (rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$record['key'],$method)) && ($arr['guid'] === $record['guid']) && ($arr['guid_sig'] === $record['guid_sig'])) { - - $c = import_xchan($record); - if($c['success']) - $result['success'] = true; - } - else { - logger('zot_register_hub: failure to verify returned packet.'); + $c = import_xchan($record); + if($c['success']) + $result['success'] = true; + } + else { + logger('zot_register_hub: failure to verify returned packet using ' . $method); + } } } } @@ -657,8 +663,19 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { $import_photos = false; - if(! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key'])) { - logger('import_xchan: Unable to verify channel signature for ' . $arr['address']); + $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]); + $verified = false; + + foreach($sig_methods as $method) { + if(! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key'],$method)) { + logger('import_xchan: Unable to verify channel signature for ' . $arr['address'] . ' using ' . $method); + continue; + } + else { + $verified = true; + } + } + if(! $verified) { $ret['message'] = t('Unable to verify channel signature'); return $ret; } @@ -700,6 +717,16 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { if(intval($r[0]['xchan_pubforum']) != intval($arr['public_forum'])) $pubforum_changed = 1; + if($arr['protocols']) { + $protocols = implode(',',$arr['protocols']); + if($protocols !== 'zot') { + set_xconfig($xchan_hash,'system','protocols',$protocols); + } + else { + del_xconfig($xchan_hash,'system','protocols'); + } + } + if(($r[0]['xchan_name_date'] != $arr['name_updated']) || ($r[0]['xchan_connurl'] != $arr['connections_url']) || ($r[0]['xchan_addr'] != $arr['address']) @@ -917,7 +944,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { } elseif(! $ud_flags) { // nothing changed but we still need to update the updates record - q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ", + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) > 0 ", intval(UPDATE_FLAGS_UPDATED), dbesc($address), intval(UPDATE_FLAGS_UPDATED) @@ -959,6 +986,18 @@ function zot_process_response($hub, $arr, $outq) { } if(is_array($x) && array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { + + if(array_key_exists('iv',$x['delivery_report'])) { + $j = crypto_unencapsulate($x['delivery_report'],get_config('system','prvkey')); + if($j) { + $x['delivery_report'] = json_decode($j,true); + } + if(! (is_array($x['delivery_report']) && count($x['delivery_report']))) { + logger('encrypted delivery report could not be decrypted'); + return; + } + } + foreach($x['delivery_report'] as $xx) { if(is_array($xx) && array_key_exists('message_id',$xx) && delivery_report_is_storable($xx)) { q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s','%s','%s','%s','%s' ) ", @@ -1030,13 +1069,15 @@ function zot_fetch($arr) { foreach($ret_hubs as $ret_hub) { + $secret = substr(preg_replace('/[^0-9a-fA-F]/','',$arr['secret']),0,64); + $data = [ 'type' => 'pickup', 'url' => z_root(), 'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system','prvkey'))), 'callback' => z_root() . '/post', - 'secret' => $arr['secret'], - 'secret_sig' => base64url_encode(rsa_sign($arr['secret'], get_config('system','prvkey'))) + 'secret' => $secret, + 'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey'))) ]; $algorithm = zot_best_algorithm($ret_hub['site_crypto']); @@ -1046,8 +1087,11 @@ function zot_fetch($arr) { $result = zot_import($fetch, $arr['sender']['url']); - if($result) + if($result) { + $result = crypto_encapsulate(json_encode($result),$ret_hub['hubloc_sitekey'], $algorithm); return $result; + } + } return; @@ -1397,7 +1441,7 @@ function public_recips($msg) { if($msg['message']['tags']) { if(is_array($msg['message']['tags']) && $msg['message']['tags']) { foreach($msg['message']['tags'] as $tag) { - if(($tag['type'] === 'mention') && (strpos($tag['url'],z_root()) !== false)) { + if(($tag['type'] === 'mention' || $tag['type'] === 'forum') && (strpos($tag['url'],z_root()) !== false)) { $address = basename($tag['url']); if($address) { $z = q("select channel_hash as hash from channel where channel_address = '%s' @@ -2857,8 +2901,14 @@ function import_site($arr, $pubkey) { $site_directory = DIRECTORY_MODE_NORMAL; } + $site_flags = $site_directory; + + if(array_key_exists('zot',$arr)) { + set_sconfig($arr['url'],'system','zot_version',$arr['zot']); + } + if($exists) { - if(($siterecord['site_flags'] != $site_directory) + if(($siterecord['site_flags'] != $site_flags) || ($siterecord['site_access'] != $access_policy) || ($siterecord['site_directory'] != $directory_url) || ($siterecord['site_sellpage'] != $sellpage) @@ -2878,7 +2928,7 @@ function import_site($arr, $pubkey) { $r = q("update site set site_dead = 0, site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s', site_realm = '%s', site_type = %d, site_project = '%s', site_version = '%s', site_crypto = '%s' where site_url = '%s'", dbesc($site_location), - intval($site_directory), + intval($site_flags), intval($access_policy), dbesc($directory_url), intval($register_policy), @@ -2911,7 +2961,7 @@ function import_site($arr, $pubkey) { 'site_location' => $site_location, 'site_url' => $url, 'site_access' => intval($access_policy), - 'site_flags' => intval($site_directory), + 'site_flags' => intval($site_flags), 'site_update' => datetime_convert(), 'site_directory' => $directory_url, 'site_register' => intval($register_policy), @@ -2947,8 +2997,11 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { logger('build_sync_packet'); - if($packet) - logger('packet: ' . print_r($packet, true),LOGGER_DATA, LOG_DEBUG); + + $keychange = (($packet && array_key_exists('keychange',$packet)) ? true : false); + if($keychange) { + logger('keychange sync'); + } if(! $uid) $uid = local_channel(); @@ -2963,6 +3016,7 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { return; $channel = $r[0]; + unset($channel['channel_password']); unset($channel['channel_salt']); @@ -2973,12 +3027,11 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { } } - if(intval($channel['channel_removed'])) return; $h = q("select hubloc.*, site.site_crypto from hubloc left join site on site_url = hubloc_url where hubloc_hash = '%s' and hubloc_deleted = 0", - dbesc($channel['channel_hash']) + dbesc(($keychange) ? $packet['keychange']['old_hash'] : $channel['channel_hash']) ); if(! $h) @@ -3010,6 +3063,9 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { $env_recips = array(); $env_recips[] = array('guid' => $r[0]['xchan_guid'],'guid_sig' => $r[0]['xchan_guid_sig']); + if($packet) + logger('packet: ' . print_r($packet, true),LOGGER_DATA, LOG_DEBUG); + $info = (($packet) ? $packet : array()); $info['type'] = 'channel_sync'; $info['encoding'] = 'red'; // note: not zot, this packet is very platform specific @@ -3033,7 +3089,15 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { // don't pass these elements, they should not be synchronised - $disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey','channel_address','channel_deleted','channel_removed','channel_system'); + + $disallowed = [ + 'channel_id','channel_account_id','channel_primary','channel_address', + 'channel_deleted','channel_removed','channel_system' + ]; + + if(! $keychange) { + $disallowed[] = 'channel_prvkey'; + } if(in_array($k,$disallowed)) continue; @@ -3093,17 +3157,18 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { function process_channel_sync_delivery($sender, $arr, $deliveries) { - require_once('include/import.php'); /** @FIXME this will sync red structures (channel, pconfig and abook). Eventually we need to make this application agnostic. */ - $result = array(); + $result = []; + + $keychange = ((array_key_exists('keychange',$arr)) ? true : false); foreach ($deliveries as $d) { $r = q("select * from channel where channel_hash = '%s' limit 1", - dbesc($d['hash']) + dbesc(($keychange) ? $arr['keychange']['old_hash'] : $d['hash']) ); if (! $r) { @@ -3122,6 +3187,94 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { continue; } + if($keychange) { + // verify the keychange operation + if(! rsa_verify($arr['channel']['channel_pubkey'],base64url_decode($arr['keychange']['new_sig']),$channel['channel_prvkey'])) { + logger('sync keychange: verification failed'); + continue; + } + + $sig = base64url_encode(rsa_sign($channel['channel_guid'],$arr['channel']['channel_prvkey'])); + $hash = make_xchan_hash($channel['channel_guid'],$sig); + + + $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', + channel_hash = '%s' where channel_id = %d", + dbesc($arr['channel']['channel_prvkey']), + dbesc($arr['channel']['channel_pubkey']), + dbesc($sig), + dbesc($hash), + intval($channel['channel_id']) + ); + if(! $r) { + logger('keychange sync: channel update failed'); + continue; + } + + $r = q("select * from channel where channel_id = %d", + intval($channel['channel_id']) + ); + + if(! $r) { + logger('keychange sync: channel retrieve failed'); + continue; + } + + $channel = $r[0]; + + $h = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ", + dbesc($arr['keychange']['old_hash']), + dbesc(z_root()) + ); + + if($h) { + foreach($h as $hv) { + $hv['hubloc_guid_sig'] = $sig; + $hv['hubloc_hash'] = $hash; + $hv['hubloc_url_sig'] = base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'])); + hubloc_store_lowlevel($hv); + } + } + + $x = q("select * from xchan where xchan_hash = '%s' ", + dbesc($arr['keychange']['old_hash']) + ); + + $check = q("select * from xchan where xchan_hash = '%s'", + dbesc($hash) + ); + + if(($x) && (! $check)) { + $oldxchan = $x[0]; + foreach($x as $xv) { + $xv['xchan_guid_sig'] = $sig; + $xv['xchan_hash'] = $hash; + $xv['xchan_pubkey'] = $channel['channel_pubkey']; + xchan_store_lowlevel($xv); + $newxchan = $xv; + } + } + + $a = q("select * from abook where abook_xchan = '%s' and abook_self = 1", + dbesc($arr['keychange']['old_hash']) + ); + + if($a) { + q("update abook set abook_xchan = '%s' where abook_id = %d", + dbesc($hash), + intval($a[0]['abook_id']) + ); + } + + xchan_change_key($oldxchan,$newxchan,$arr['keychange']); + + // keychange operations can end up in a confused state if you try and sync anything else + // besides the channel keys, so ignore any other packets. + + continue; + } + + if(array_key_exists('config',$arr) && is_array($arr['config']) && count($arr['config'])) { foreach($arr['config'] as $cat => $k) { foreach($arr['config'][$cat] as $k => $v) @@ -3759,11 +3912,57 @@ function zot_reply_message_request($data) { json_return_and_die($ret); } +function zot_rekey_request($sender,$data) { + + $ret = array('success' => false); + + // newsig is newkey signed with oldkey + + // The original xchan will remain. In Zot/Receiver we will have imported the new xchan and hubloc to verify + // the packet authenticity. What we will do now is verify that the keychange operation was signed by the + // oldkey, and if so change all the abook, abconfig, group, and permission elements which reference the + // old xchan_hash. + + if((! $data['old_key']) && (! $data['new_key']) && (! $data['new_sig'])) + json_return_and_die($ret); + + $oldhash = make_xchan_hash($data['old_guid'],$data['old_guid_sig']); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($oldhash) + ); + + if(! $r) { + json_return_and_die($ret); + } + + $xchan = $r[0]; + + if(! rsa_verify($data['new_key'],base64url_decode($data['new_sig']),$xchan['xchan_pubkey'])) { + json_return_and_die($ret); + } + + $newhash = make_xchan_hash($sender['guid'],$sender['guid_sig']); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($newhash) + ); + + $newxchan = $r[0]; + + xchan_change_key($xchan,$newxchan,$data); + + $ret['success'] = true; + json_return_and_die($ret); +} + function zotinfo($arr) { $ret = array('success' => false); + $sig_method = get_config('system','signature_algorithm','sha256'); + $zhash = ((x($arr,'guid_hash')) ? $arr['guid_hash'] : ''); $zguid = ((x($arr,'guid')) ? $arr['guid'] : ''); $zguid_sig = ((x($arr,'guid_sig')) ? $arr['guid_sig'] : ''); @@ -3845,6 +4044,11 @@ function zotinfo($arr) { $id = $e['channel_id']; + $x = [ 'channel_id' => $id, 'protocols' => ['zot'] ]; + call_hooks('channel_protocols',$x); + $protocols = $x['protocols']; + + $sys_channel = (intval($e['channel_system']) ? true : false); $special_channel = (($e['channel_pageflags'] & PAGE_PREMIUM) ? true : false); $adult_channel = (($e['channel_pageflags'] & PAGE_ADULT) ? true : false); @@ -3927,7 +4131,7 @@ function zotinfo($arr) { // Communication details if($token) - $ret['signed_token'] = base64url_encode(rsa_sign('token.' . $token,$e['channel_prvkey'])); + $ret['signed_token'] = base64url_encode(rsa_sign('token.' . $token,$e['channel_prvkey'],$sig_method)); $ret['guid'] = $e['xchan_guid']; @@ -3945,6 +4149,7 @@ function zotinfo($arr) { $ret['target'] = $ztarget; $ret['target_sig'] = $zsig; $ret['searchable'] = $searchable; + $ret['protocols'] = $protocols; $ret['adult_content'] = $adult_channel; $ret['public_forum'] = $public_forum; if($deleted) @@ -3970,7 +4175,7 @@ function zotinfo($arr) { if($ztarget_hash) { $permissions['connected'] = false; - $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and abook_pending = 0 limit 1", dbesc($ztarget_hash), intval($e['channel_id']) ); @@ -3994,10 +4199,33 @@ function zotinfo($arr) { if($x) $ret['locations'] = $x; - $ret['site'] = array(); + $ret['site'] = zot_site_info($e['channel_prvkey']); + + check_zotinfo($e,$x,$ret); + + + call_hooks('zot_finger',$ret); + return($ret); + +} + + +function zot_site_info($channel_key = '') { + + $signing_key = get_config('system','prvkey'); + $sig_method = get_config('system','signature_algorithm','sha256'); + + $ret = []; + $ret['site'] = []; $ret['site']['url'] = z_root(); - $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(),$e['channel_prvkey'])); - $ret['site']['zot_auth'] = z_root() . '/magic'; + if($channel_key) { + $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(),$channel_key,$sig_method)); + } + $ret['site']['url_site_sig'] = base64url_encode(rsa_sign(z_root(),$signing_key,$sig_method)); + $ret['site']['post'] = z_root() . '/post'; + $ret['site']['openWebAuth'] = z_root() . '/owa'; + $ret['site']['authRedirect'] = z_root() . '/magic'; + $ret['site']['key'] = get_config('system','pubkey'); $dirmode = get_config('system','directory_mode'); if(($dirmode === false) || ($dirmode == DIRECTORY_MODE_NORMAL)) @@ -4014,6 +4242,8 @@ function zotinfo($arr) { $ret['site']['encryption'] = crypto_methods(); + $ret['site']['signing'] = signing_methods(); + $ret['site']['zot'] = Zotlabs\Lib\System::get_zot_revision(); // hide detailed site information if you're off the grid @@ -4066,15 +4296,10 @@ function zotinfo($arr) { } - check_zotinfo($e,$x,$ret); - - - call_hooks('zot_finger',$ret); - return($ret); + return $ret['site']; } - function check_zotinfo($channel,$locations,&$ret) { |