]> git.mxchange.org Git - friendica.git/blob - mod/salmon.php
Merge pull request #7767 from MrPetovan/bug/fatal-errors
[friendica.git] / mod / salmon.php
1 <?php
2 /**
3  * @file mod/salmon.php
4  */
5
6 use Friendica\App;
7 use Friendica\Core\Logger;
8 use Friendica\Core\PConfig;
9 use Friendica\Core\Protocol;
10 use Friendica\Database\DBA;
11 use Friendica\Model\Contact;
12 use Friendica\Protocol\ActivityNamespace;
13 use Friendica\Protocol\OStatus;
14 use Friendica\Protocol\Salmon;
15 use Friendica\Util\Crypto;
16 use Friendica\Util\Network;
17 use Friendica\Util\Strings;
18
19 function salmon_post(App $a, $xml = '') {
20
21         if (empty($xml)) {
22                 $xml = Network::postdata();
23         }
24
25         Logger::log('new salmon ' . $xml, Logger::DATA);
26
27         $nick       = (($a->argc > 1) ? Strings::escapeTags(trim($a->argv[1])) : '');
28
29         $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1",
30                 DBA::escape($nick)
31         );
32         if (! DBA::isResult($r)) {
33                 throw new \Friendica\Network\HTTPException\InternalServerErrorException();
34         }
35
36         $importer = $r[0];
37
38         // parse the xml
39
40         $dom = simplexml_load_string($xml,'SimpleXMLElement',0, ActivityNamespace::SALMON_ME);
41
42         $base = null;
43
44         // figure out where in the DOM tree our data is hiding
45         if (!empty($dom->provenance->data))
46                 $base = $dom->provenance;
47         elseif (!empty($dom->env->data))
48                 $base = $dom->env;
49         elseif (!empty($dom->data))
50                 $base = $dom;
51
52         if (empty($base)) {
53                 Logger::log('unable to locate salmon data in xml ');
54                 throw new \Friendica\Network\HTTPException\BadRequestException();
55         }
56
57         // Stash the signature away for now. We have to find their key or it won't be good for anything.
58
59
60         $signature = Strings::base64UrlDecode($base->sig);
61
62         // unpack the  data
63
64         // strip whitespace so our data element will return to one big base64 blob
65         $data = str_replace([" ","\t","\r","\n"],["","","",""],$base->data);
66
67         // stash away some other stuff for later
68
69         $type = $base->data[0]->attributes()->type[0];
70         $keyhash = $base->sig[0]->attributes()->keyhash[0];
71         $encoding = $base->encoding;
72         $alg = $base->alg;
73
74         // Salmon magic signatures have evolved and there is no way of knowing ahead of time which
75         // flavour we have. We'll try and verify it regardless.
76
77         $stnet_signed_data = $data;
78
79         $signed_data = $data  . '.' . Strings::base64UrlEncode($type) . '.' . Strings::base64UrlEncode($encoding) . '.' . Strings::base64UrlEncode($alg);
80
81         $compliant_format = str_replace('=', '', $signed_data);
82
83
84         // decode the data
85         $data = Strings::base64UrlDecode($data);
86
87         $author = OStatus::salmonAuthor($data, $importer);
88         $author_link = $author["author-link"];
89
90         if(! $author_link) {
91                 Logger::log('Could not retrieve author URI.');
92                 throw new \Friendica\Network\HTTPException\BadRequestException();
93         }
94
95         // Once we have the author URI, go to the web and try to find their public key
96
97         Logger::log('Fetching key for ' . $author_link);
98
99         $key = Salmon::getKey($author_link, $keyhash);
100
101         if(! $key) {
102                 Logger::log('Could not retrieve author key.');
103                 throw new \Friendica\Network\HTTPException\BadRequestException();
104         }
105
106         $key_info = explode('.',$key);
107
108         $m = Strings::base64UrlDecode($key_info[1]);
109         $e = Strings::base64UrlDecode($key_info[2]);
110
111         Logger::log('key details: ' . print_r($key_info,true), Logger::DEBUG);
112
113         $pubkey = Crypto::meToPem($m, $e);
114
115         // We should have everything we need now. Let's see if it verifies.
116
117         // Try GNU Social format
118         $verify = Crypto::rsaVerify($signed_data, $signature, $pubkey);
119         $mode = 1;
120
121         if (! $verify) {
122                 Logger::log('message did not verify using protocol. Trying compliant format.');
123                 $verify = Crypto::rsaVerify($compliant_format, $signature, $pubkey);
124                 $mode = 2;
125         }
126
127         if (! $verify) {
128                 Logger::log('message did not verify using padding. Trying old statusnet format.');
129                 $verify = Crypto::rsaVerify($stnet_signed_data, $signature, $pubkey);
130                 $mode = 3;
131         }
132
133         if (! $verify) {
134                 Logger::log('Message did not verify. Discarding.');
135                 throw new \Friendica\Network\HTTPException\BadRequestException();
136         }
137
138         Logger::log('Message verified with mode '.$mode);
139
140
141         /*
142         *
143         * If we reached this point, the message is good. Now let's figure out if the author is allowed to send us stuff.
144         *
145         */
146
147         $r = q("SELECT * FROM `contact` WHERE `network` IN ('%s', '%s')
148                                                 AND (`nurl` = '%s' OR `alias` = '%s' OR `alias` = '%s')
149                                                 AND `uid` = %d LIMIT 1",
150                 DBA::escape(Protocol::OSTATUS),
151                 DBA::escape(Protocol::DFRN),
152                 DBA::escape(Strings::normaliseLink($author_link)),
153                 DBA::escape($author_link),
154                 DBA::escape(Strings::normaliseLink($author_link)),
155                 intval($importer['uid'])
156         );
157
158         if (!DBA::isResult($r)) {
159                 Logger::log('Author ' . $author_link . ' unknown to user ' . $importer['uid'] . '.');
160
161                 if (PConfig::get($importer['uid'], 'system', 'ostatus_autofriend')) {
162                         $result = Contact::createFromProbe($importer['uid'], $author_link);
163
164                         if ($result['success']) {
165                                 $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s')
166                                         AND `uid` = %d LIMIT 1",
167                                         DBA::escape(Protocol::OSTATUS),
168                                         DBA::escape($author_link),
169                                         DBA::escape($author_link),
170                                         intval($importer['uid'])
171                                 );
172                         }
173                 }
174         }
175
176         // Have we ignored the person?
177         // If so we can not accept this post.
178
179         //if((DBA::isResult($r)) && (($r[0]['readonly']) || ($r[0]['rel'] == Contact::FOLLOWER) || ($r[0]['blocked']))) {
180         if (DBA::isResult($r) && $r[0]['blocked']) {
181                 Logger::log('Ignoring this author.');
182                 throw new \Friendica\Network\HTTPException\AcceptedException();
183         }
184
185         // Placeholder for hub discovery.
186         $hub = '';
187
188         $contact_rec = ((DBA::isResult($r)) ? $r[0] : []);
189
190         OStatus::import($data, $importer, $contact_rec, $hub);
191
192         throw new \Friendica\Network\HTTPException\OKException();
193 }