From 4bde2f64bdef15aba833528ac8c663c99e55e8dd Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 27 May 2021 19:55:06 +0000 Subject: remove zot --- Zotlabs/Zot/Auth.php | 363 --------------------------------------------- Zotlabs/Zot/Finger.php | 155 ------------------- Zotlabs/Zot/IHandler.php | 24 --- Zotlabs/Zot/Receiver.php | 304 ------------------------------------- Zotlabs/Zot/ZotHandler.php | 39 ----- 5 files changed, 885 deletions(-) delete mode 100644 Zotlabs/Zot/Auth.php delete mode 100644 Zotlabs/Zot/Finger.php delete mode 100644 Zotlabs/Zot/IHandler.php delete mode 100644 Zotlabs/Zot/Receiver.php delete mode 100644 Zotlabs/Zot/ZotHandler.php diff --git a/Zotlabs/Zot/Auth.php b/Zotlabs/Zot/Auth.php deleted file mode 100644 index 6ce2174f7..000000000 --- a/Zotlabs/Zot/Auth.php +++ /dev/null @@ -1,363 +0,0 @@ -test = ((array_key_exists('test',$req)) ? intval($req['test']) : 0); - $this->test_results = array('success' => false); - $this->debug_msg = ''; - - $this->success = false; - $this->address = $req['auth']; - $this->desturl = $req['dest']; - $this->sec = $req['sec']; - $this->version = $req['version']; - $this->delegate = $req['delegate']; - - $c = get_sys_channel(); - if(! $c) { - logger('unable to obtain response (sys) channel'); - $this->Debug('no local channels found.'); - $this->Finalise(); - } - - if(strpbrk($this->sec,'.:')) { - logger('illegal security context'); - $this->Debug('illegal security context.'); - $this->Finalise(); - } - - $x = $this->GetHublocs($this->address); - - if($x) { - foreach($x as $xx) { - if($this->Verify($c,$xx)) - break; - } - } - - /** - * @FIXME we really want to save the return_url in the session before we - * visit rmagic. This does however prevent a recursion if you visit - * rmagic directly, as it would otherwise send you back here again. - * But z_root() probably isn't where you really want to go. - */ - - if(strstr($this->desturl,z_root() . '/rmagic')) - goaway(z_root()); - - $this->Finalise(); - - } - - function GetHublocs($address) { - - // Try and find a hubloc for the person attempting to auth. - // Since we're matching by address, we have to return all entries - // some of which may be from re-installed hubs; and we'll need to - // try each sequentially to see if one can pass the test - - $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash - where hubloc_addr = '%s' order by hubloc_id desc", - dbesc($address) - ); - - if(! $x) { - // finger them if they can't be found. - $j = Finger::run($address, null); - if ($j['success']) { - import_xchan($j); - $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash - where hubloc_addr = '%s' order by hubloc_id desc", - dbesc($address) - ); - } - } - if(! $x) { - logger('mod_zot: auth: unable to finger ' . $address); - $this->Debug('no hubloc found for ' . $address . ' and probing failed.'); - $this->Finalise(); - } - - return $x; - } - - - function Verify($channel,$hubloc) { - - logger('auth request received from ' . $hubloc['hubloc_addr'] ); - - $this->remote = remote_channel(); - $this->remote_service_class = ''; - $this->remote_level = 0; - $this->remote_hub = $hubloc['hubloc_url']; - $this->dnt = 0; - - if(! $this->sec) { - logger('missing security context.'); - if($this->test) - $this->Debug('missing security context.'); - return false; - } - - - // check credentials and access - - // If they are already authenticated and haven't changed credentials, - // we can save an expensive network round trip and improve performance. - - // Also check that they are coming from the same site as they authenticated with originally. - - $already_authed = (((remote_channel()) && ($hubloc['hubloc_hash'] == remote_channel()) - && ($hubloc['hubloc_url'] === $_SESSION['remote_hub'])) ? true : false); - - if($this->delegate && $this->delegate !== $_SESSION['delegate_channel']) - $already_authed = false; - - if($already_authed) - return true; - - if(local_channel()) { - - // tell them to logout if they're logged in locally as anything but the target remote account - // in which case just shut up because they don't need to be doing this at all. - - if (\App::$channel['channel_hash'] == $hubloc['xchan_hash']) { - return true; - } - else { - logger('already authenticated locally as somebody else.'); - notice( t('Remote authentication blocked. You are logged into this site locally. Please logout and retry.') . EOL); - if($this->test) { - $this->Debug('already logged in locally with a conflicting identity.'); - return false; - } - } - return false; - } - - // Auth packets MUST use ultra top-secret hush-hush mode - e.g. the entire packet is encrypted using the - // site private key - // The actual channel sending the packet ($c[0]) is not important, but this provides a - // generic zot packet with a sender which can be verified - - $x = q("select site_crypto from site where site_url = '%s' limit 1", - dbesc($hubloc['hubloc_url']) - ); - - $p = zot_build_packet($channel,$type = 'auth_check', - array(array('guid' => $hubloc['hubloc_guid'],'guid_sig' => $hubloc['hubloc_guid_sig'])), - $hubloc['hubloc_sitekey'], (($x) ? $x[0]['site_crypto'] : ''), $this->sec); - - $this->Debug('auth check packet created using sitekey ' . $hubloc['hubloc_sitekey']); - $this->Debug('packet contents: ' . $p); - - $result = zot_zot($hubloc['hubloc_callback'],$p); - if(! $result['success']) { - logger('auth_check callback failed.'); - if($this->test) - $this->Debug('auth check request to your site returned .' . print_r($result, true)); - return false; - } - - $j = json_decode($result['body'], true); - if(! $j) { - logger('auth_check json data malformed.'); - if($this->test) - $this->Debug('json malformed: ' . $result['body']); - return false; - } - - $this->Debug('auth check request returned ' . print_r($j, true)); - - if(! $j['success']) - return false; - - // legit response, but we do need to check that this wasn't answered by a man-in-middle - - if (! Crypto::verify($this->sec . $hubloc['xchan_hash'],base64url_decode($j['confirm']),$hubloc['xchan_pubkey'])) { - logger('final confirmation failed.'); - if($this->test) - $this->Debug('final confirmation failed. ' . $sec . print_r($j,true) . print_r($hubloc,true)); - return false; - } - - if (array_key_exists('service_class',$j)) - $this->remote_service_class = $j['service_class']; - if (array_key_exists('level',$j)) - $this->remote_level = $j['level']; - if (array_key_exists('DNT',$j)) - $this->dnt = $j['DNT']; - - - // log them in - - if ($this->test) { - // testing only - return the success result - $this->test_results['success'] = true; - $this->Debug('Authentication Success!'); - $this->Finalise(); - } - - $_SESSION['authenticated'] = 1; - - // check for delegation and if all is well, log them in locally with delegation restrictions - - $this->delegate_success = false; - - if($this->delegate) { - $r = q("select * from channel left join xchan on channel_hash = xchan_hash where xchan_addr = '%s' limit 1", - dbesc($this->delegate) - ); - if ($r && intval($r[0]['channel_id'])) { - $allowed = perm_is_allowed($r[0]['channel_id'],$hubloc['xchan_hash'],'delegate'); - if($allowed) { - $_SESSION['delegate_channel'] = $r[0]['channel_id']; - $_SESSION['delegate'] = $hubloc['xchan_hash']; - $_SESSION['account_id'] = intval($r[0]['channel_account_id']); - require_once('include/security.php'); - // this will set the local_channel authentication in the session - change_channel($r[0]['channel_id']); - $this->delegate_success = true; - } - } - } - - if (! $this->delegate_success) { - // normal visitor (remote_channel) login session credentials - $_SESSION['visitor_id'] = $hubloc['xchan_hash']; - $_SESSION['my_url'] = $hubloc['xchan_url']; - $_SESSION['my_address'] = $this->address; - $_SESSION['remote_service_class'] = $this->remote_service_class; - $_SESSION['remote_level'] = $this->remote_level; - $_SESSION['remote_hub'] = $this->remote_hub; - $_SESSION['DNT'] = $this->dnt; - } - - $arr = array('xchan' => $hubloc, 'url' => $this->desturl, 'session' => $_SESSION); - call_hooks('magic_auth_success',$arr); - \App::set_observer($hubloc); - require_once('include/security.php'); - \App::set_groups(init_groups_visitor($_SESSION['visitor_id'])); - info(sprintf( t('Welcome %s. Remote authentication successful.'),$hubloc['xchan_name'])); - logger('mod_zot: auth success from ' . $hubloc['xchan_addr']); - $this->success = true; - return true; - } - - function Debug($msg) { - $this->debug_msg .= $msg . EOL; - } - - - function Finalise() { - - if($this->test) { - $this->test_results['message'] = $this->debug_msg; - json_return_and_die($this->test_results); - } - - goaway($this->desturl); - } - -} - - -/** - * - * Magic Auth - * ========== - * - * So-called "magic auth" takes place by a special exchange. On the site where the "channel to be authenticated" lives (e.g. $mysite), - * a redirection is made via $mysite/magic to the zot endpoint of the remote site ($remotesite) with special GET parameters. - * - * The endpoint is typically https://$remotesite/post - or whatever was specified as the callback url in prior communications - * (we will bootstrap an address and fetch a zot info packet if possible where no prior communications exist) - * - * Five GET parameters are supplied: - * * auth => the urlencoded webbie (channel@host.domain) of the channel requesting access - * * dest => the desired destination URL (urlencoded) - * * sec => a random string which is also stored on $mysite for use during the verification phase. - * * version => the zot revision - * * delegate => optional urlencoded webbie of a local channel to invoke delegation rights for - * - * * test => (optional 1 or 0 - debugs the authentication exchange and returns a json response instead of redirecting the browser session) - * - * When this packet is received, an "auth-check" zot message is sent to $mysite. - * (e.g. if $_GET['auth'] is foobar@podunk.edu, a zot packet is sent to the podunk.edu zot endpoint, which is typically /post) - * If no information has been recorded about the requesting identity a zot information packet will be retrieved before - * continuing. - * - * The sender of this packet is an arbitrary/random site channel. The recipients will be a single recipient corresponding - * to the guid and guid_sig we have associated with the requesting auth identity - * - * \code{.json} - * { - * "type":"auth_check", - * "sender":{ - * "guid":"kgVFf_...", - * "guid_sig":"PT9-TApz...", - * "url":"http:\/\/podunk.edu", - * "url_sig":"T8Bp7j...", - * "sitekey":"aMtgKTiirXrICP..." - * }, - * "recipients":{ - * { - * "guid":"ZHSqb...", - * "guid_sig":"JsAAXi..." - * } - * } - * "callback":"\/post", - * "version":1, - * "secret":"1eaa661", - * "secret_sig":"eKV968b1..." - * } - * \endcode - * - * auth_check messages MUST use encapsulated encryption. This message is sent to the origination site, which checks the 'secret' to see - * if it is the same as the 'sec' which it passed originally. It also checks the secret_sig which is the secret signed by the - * destination channel's private key and base64url encoded. If everything checks out, a json packet is returned: - * - * \code{.json} - * { - * "success":1, - * "confirm":"q0Ysovd1u...", - * "service_class":(optional) - * "level":(optional) - * "DNT": (optional do-not-track - 1 or 0) - * } - * \endcode - * - * 'confirm' in this case is the base64url encoded RSA signature of the concatenation of 'secret' with the - * base64url encoded whirlpool hash of the requestor's guid and guid_sig; signed with the source channel private key. - * This prevents a man-in-the-middle from inserting a rogue success packet. Upon receipt and successful - * verification of this packet, the destination site will redirect to the original destination URL and indicate a successful remote login. - * Service_class can be used by cooperating sites to provide different access rights based on account rights and subscription plans. It is - * a string whose contents are not defined by protocol. Example: "basic" or "gold". - * - * @param[in,out] \App &$a - */ diff --git a/Zotlabs/Zot/Finger.php b/Zotlabs/Zot/Finger.php deleted file mode 100644 index cadde5415..000000000 --- a/Zotlabs/Zot/Finger.php +++ /dev/null @@ -1,155 +0,0 @@ - true) or array('success' => false); - */ - - static public function run($webbie, $channel = null, $autofallback = true) { - - $ret = array('success' => false); - - self::$token = random_string(); - - if (strpos($webbie, '@') === false) { - $address = $webbie; - $host = \App::get_hostname(); - } else { - $address = substr($webbie,0,strpos($webbie,'@')); - $host = substr($webbie,strpos($webbie,'@')+1); - if(strpos($host,'/')) - $host = substr($host,0,strpos($host,'/')); - } - - $xchan_addr = $address . '@' . $host; - - if ((! $address) || (! $xchan_addr)) { - logger('zot_finger: no address :' . $webbie); - - return $ret; - } - - logger('using xchan_addr: ' . $xchan_addr, LOGGER_DATA, LOG_DEBUG); - - // potential issue here; the xchan_addr points to the primary hub. - // The webbie we were called with may not, so it might not be found - // unless we query for hubloc_addr instead of xchan_addr - - $r = q("select xchan.*, hubloc.* from xchan - left join hubloc on xchan_hash = hubloc_hash - where xchan_addr = '%s' and hubloc_primary = 1 and hubloc_deleted = 0 and hubloc_network = 'zot' limit 1", - dbesc($xchan_addr) - ); - - if($r) { - $url = $r[0]['hubloc_url']; - - if($r[0]['hubloc_network'] && $r[0]['hubloc_network'] !== 'zot') { - logger('zot_finger: alternate network: ' . $webbie); - logger('url: ' . $url . ', net: ' . var_export($r[0]['hubloc_network'],true), LOGGER_DATA, LOG_DEBUG); - return $ret; - } - } else { - $url = 'https://' . $host; - } - - $m = parse_url($url); - if($m) { - $parsed_host = strtolower($m['host']); - } - - $rhs = '/.well-known/zot-info'; - $https = ((strpos($url,'https://') === 0) ? true : false); - - logger('zot_finger: ' . $address . ' at ' . $url, LOGGER_DEBUG); - - if ($channel) { - $postvars = array( - 'address' => $address, - 'target' => $channel['channel_guid'], - 'target_sig' => $channel['channel_guid_sig'], - 'key' => $channel['channel_pubkey'], - 'token' => self::$token - ); - - $headers = []; - $headers['X-Zot-Channel'] = $channel['channel_address'] . '@' . \App::get_hostname(); - $headers['X-Zot-Nonce'] = random_string(); - $headers['Host'] = $parsed_host; - - $xhead = HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel)); - - $retries = 0; - - $result = z_post_url($url . $rhs,$postvars,$retries, [ 'headers' => $xhead ]); - - if ((! $result['success']) && ($autofallback)) { - if ($https) { - logger('zot_finger: https failed. falling back to http'); - $result = z_post_url('http://' . $host . $rhs,$postvars, $retries, [ 'headers' => $xhead ]); - } - } - } - else { - $rhs .= '?f=&address=' . urlencode($address) . '&token=' . self::$token; - - $result = z_fetch_url($url . $rhs); - if((! $result['success']) && ($autofallback)) { - if($https) { - logger('zot_finger: https failed. falling back to http'); - $result = z_fetch_url('http://' . $host . $rhs); - } - } - } - - if(! $result['success']) { - logger('zot_finger: no results'); - - return $ret; - } - - $x = json_decode($result['body'], true); - - $verify = HTTPSig::verify($result,(($x) ? $x['key'] : '')); - - if($x && (! $verify['header_valid'])) { - $signed_token = ((is_array($x) && array_key_exists('signed_token', $x)) ? $x['signed_token'] : null); - if($signed_token) { - $valid = Crypto::verify('token.' . self::$token, base64url_decode($signed_token), $x['key']); - if(! $valid) { - logger('invalid signed token: ' . $url . $rhs, LOGGER_NORMAL, LOG_ERR); - - return $ret; - } - } - else { - logger('No signed token from ' . $url . $rhs, LOGGER_NORMAL, LOG_WARNING); - return $ret; - } - } - - return $x; - } - -} diff --git a/Zotlabs/Zot/IHandler.php b/Zotlabs/Zot/IHandler.php deleted file mode 100644 index dd82f5be6..000000000 --- a/Zotlabs/Zot/IHandler.php +++ /dev/null @@ -1,24 +0,0 @@ -error = false; - $this->validated = false; - $this->messagetype = ''; - $this->response = array('success' => false); - - $this->handler = $handler; - - if(! is_array($data)) - $data = json_decode($data,true); - - if($data && is_array($data)) { - $this->encrypted = ((array_key_exists('iv',$data)) ? true : false); - - if($this->encrypted) { - $data['encrypted'] = true; - $this->data = @json_decode(@Crypto::unencapsulate($data,$prvkey),true); - } - if(! $this->data) - $this->data = $data; - - if($this->data && is_array($this->data) && array_key_exists('type',$this->data)) - $this->messagetype = $this->data['type']; - } - if(! $this->messagetype) - $this->error = true; - - if($this->data) { - $this->sender = ((array_key_exists('sender',$this->data)) ? $this->data['sender'] : null); - $this->recipients = ((array_key_exists('recipients',$this->data)) ? $this->data['recipients'] : null); - } - - if($this->sender) - $this->ValidateSender(); - - $this->Dispatch(); - } - - function ValidateSender() { - $hubs = zot_gethub($this->sender,true); - if (! $hubs) { - - /* Have never seen this guid or this guid coming from this location. Check it and register it. */ - /* (!!) this will validate the sender. */ - - $result = zot_register_hub($this->sender); - - if ((! $result['success']) || (! ($hubs = zot_gethub($this->sender,true)))) { - $this->response['message'] = 'Hub not available.'; - json_return_and_die($this->response); - } - } - foreach($hubs as $hub) { - update_hub_connected($hub,((array_key_exists('sitekey',$this->sender)) ? $this->sender['sitekey'] : '')); - } - $this->validated = true; - } - - - function Dispatch() { - - /* Handle tasks which don't require sender validation */ - - switch($this->messagetype) { - case 'ping': - /* no validation needed */ - $this->handler->Ping(); - break; - case 'pickup': - /* perform site validation, as opposed to sender validation */ - $this->handler->Pickup($this->data); - break; - - default: - if(! $this->validated) { - $this->response['message'] = 'Sender not valid'; - json_return_and_die($this->response); - } - break; - } - - /* Now handle tasks which require sender validation */ - - switch($this->messagetype) { - - case 'auth_check': - $this->handler->AuthCheck($this->data,$this->encrypted); - break; - - case 'request': - $this->handler->Request($this->data); - break; - - case 'purge': - $this->handler->Purge($this->sender,$this->recipients); - break; - - case 'refresh': - case 'force_refresh': - $this->handler->Refresh($this->sender,$this->recipients); - break; - - case 'notify': - $this->handler->Notify($this->data); - break; - - case 'rekey': - $this->handler->Rekey($this->sender, $this->data); - break; - - default: - $this->response['message'] = 'Not implemented'; - json_return_and_die($this->response); - break; - } - - } -} - - - -/** - * @brief zot communications and messaging. - * - * Sender HTTP posts to this endpoint ($site/post typically) with 'data' parameter set to json zot message packet. - * This packet is optionally encrypted, which we will discover if the json has an 'iv' element. - * $contents => array( 'alg' => 'aes256cbc', 'iv' => initialisation vector, 'key' => decryption key, 'data' => encrypted data); - * $contents->iv and $contents->key are random strings encrypted with this site's RSA public key and then base64url encoded. - * - * Once decrypted, one will find the normal json_encoded zot message packet. - * - * Defined packet types are: notify, purge, refresh, force_refresh, auth_check, ping, and pickup - * - * Standard packet: (used by notify, purge, refresh, force_refresh, and auth_check) - * \code{.json} - * { - * "type": "notify", - * "sender":{ - * "guid":"kgVFf_1...", - * "guid_sig":"PT9-TApzp...", - * "url":"http:\/\/podunk.edu", - * "url_sig":"T8Bp7j5...", - * }, - * "recipients": { optional recipient array }, - * "callback":"\/post", - * "version":"1.2", - * "encryption":["aes256cbc"], - * "secret":"1eaa...", - * "secret_sig": "df89025470fac8..." - * } - * \endcode - * - * Signature fields are all signed with the sender channel private key and base64url encoded. - * Recipients are arrays of guid and guid_sig, which were previously signed with the recipients private - * key and base64url encoded and later obtained via channel discovery. Absence of recipients indicates - * a public message or visible to all potential listeners on this site. - * - * "pickup" packet: - * The pickup packet is sent in response to a notify packet from another site - * \code{.json} - * { - * "type":"pickup", - * "url":"http:\/\/example.com", - * "callback":"http:\/\/example.com\/post", - * "callback_sig":"teE1_fLI...", - * "secret":"1eaa...", - * "secret_sig":"O7nB4_..." - * } - * \endcode - * - * In the pickup packet, the sig fields correspond to the respective data - * element signed with this site's system private key and then base64url encoded. - * The "secret" is the same as the original secret from the notify packet. - * - * If verification is successful, a json structure is returned containing a - * success indicator and an array of type 'pickup'. - * Each pickup element contains the original notify request and a message field - * whose contents are dependent on the message type. - * - * This JSON array is AES encapsulated using the site public key of the site - * that sent the initial zot pickup packet. - * Using the above example, this would be example.com. - * - * \code{.json} - * { - * "success":1, - * "pickup":{ - * "notify":{ - * "type":"notify", - * "sender":{ - * "guid":"kgVFf_...", - * "guid_sig":"PT9-TApz...", - * "url":"http:\/\/z.podunk.edu", - * "url_sig":"T8Bp7j5D..." - * }, - * "callback":"\/post", - * "version":1, - * "secret":"1eaa661..." - * }, - * "message":{ - * "type":"activity", - * "message_id":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu", - * "message_top":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu", - * "message_parent":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu", - * "created":"2012-11-20 04:04:16", - * "edited":"2012-11-20 04:04:16", - * "title":"", - * "body":"Hi Nickordo", - * "app":"", - * "verb":"post", - * "object_type":"", - * "target_type":"", - * "permalink":"", - * "location":"", - * "longlat":"", - * "owner":{ - * "name":"Indigo", - * "address":"indigo@podunk.edu", - * "url":"http:\/\/podunk.edu", - * "photo":{ - * "mimetype":"image\/jpeg", - * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" - * }, - * "guid":"kgVFf_...", - * "guid_sig":"PT9-TAp...", - * }, - * "author":{ - * "name":"Indigo", - * "address":"indigo@podunk.edu", - * "url":"http:\/\/podunk.edu", - * "photo":{ - * "mimetype":"image\/jpeg", - * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" - * }, - * "guid":"kgVFf_...", - * "guid_sig":"PT9-TAp..." - * } - * } - * } - * } - * \endcode - * - * Currently defined message types are 'activity', 'mail', 'profile', 'location' - * and 'channel_sync', which each have different content schemas. - * - * Ping packet: - * A ping packet does not require any parameters except the type. It may or may - * not be encrypted. - * - * \code{.json} - * { - * "type": "ping" - * } - * \endcode - * - * On receipt of a ping packet a ping response will be returned: - * - * \code{.json} - * { - * "success" : 1, - * "site" { - * "url": "http:\/\/podunk.edu", - * "url_sig": "T8Bp7j5...", - * "sitekey": "-----BEGIN PUBLIC KEY----- - * MIICIjANBgkqhkiG9w0BAQE..." - * } - * } - * \endcode - * - * The ping packet can be used to verify that a site has not been re-installed, and to - * initiate corrective action if it has. The url_sig is signed with the site private key - * and base64url encoded - and this should verify with the enclosed sitekey. Failure to - * verify indicates the site is corrupt or otherwise unable to communicate using zot. - * This return packet is not otherwise verified, so should be compared with other - * results obtained from this site which were verified prior to taking action. For instance - * if you have one verified result with this signature and key, and other records for this - * url which have different signatures and keys, it indicates that the site was re-installed - * and corrective action may commence (remove or mark invalid any entries with different - * signatures). - * If you have no records which match this url_sig and key - no corrective action should - * be taken as this packet may have been returned by an imposter. - * - * @param[in,out] App &$a - */ - diff --git a/Zotlabs/Zot/ZotHandler.php b/Zotlabs/Zot/ZotHandler.php deleted file mode 100644 index ab8815b3d..000000000 --- a/Zotlabs/Zot/ZotHandler.php +++ /dev/null @@ -1,39 +0,0 @@ -