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