#!/usr/bin/php $message['apsAccountId'],'aps_device_token'=>$message['apsDeviceToken']]); } dbDelete('mailboxes',"device=$device AND user='$message[user]'"); // Erase previous subscriptions for user on this device foreach ($message['mailboxes'] as $mb) { if ($debug) syslog(LOG_INFO,"Mailbox: $mb"); dbWrite('mailboxes',['device'=>$device,'user'=>$message['user'],'mailbox'=>$mb]); } break; case 'MessageNew': // New message in mailbox - this is the most important one $mb=mbInfoFromURI($message['uri']); if ($debug) { syslog(LOG_INFO,"User: $mb[user]"); syslog(LOG_INFO,"Mailbox: $mb[mb]"); } tryPush($mb); break; case 'MessageRead': // User reads message. Only if INBOX, to allow for badge/msglist to update case 'MessageTrash': // User trash message. Only if INBOX, to allow for badge/msglist to update $mb=mbInfoFromURI($message['uri']); if ($debug) { syslog(LOG_INFO,"User: $mb[user]"); syslog(LOG_INFO,"Mailbox: $mb[mb]"); } if ($mb['mb']=='INBOX') tryPush($mb); break; case 'vnd.cmu.MessageMove': // This may seem a bit too much, but one may move message out of inbox without reading it $fromMB=mbInfoFromURI($message['oldMailboxID']); $toMB=mbInfoFromURI($message['uri']); if ($debug) { syslog(LOG_INFO,"From User: $fromMB[user]"); syslog(LOG_INFO,"From Mailbox: $fromMB[mb]"); syslog(LOG_INFO,"To User: $toMB[user]"); syslog(LOG_INFO,"To Mailbox: $toMB[mb]"); } tryPush($fromMB); break; case 'vnd.cmu.MessageCopy': // Some clients copy message to trash (MessageTrash gets called, so I guess we don't need this) case 'MessageExpunge': // My guess that we do not need to push on this one too, but leave it here default: } // Done exit; // Check if [user+mailbox] need a nudge and do it function tryPush($mb) { $result=dbQuery("SELECT * FROM devices,mailboxes WHERE mailbox='$mb[mb]' AND user='$mb[user]' AND device=devices.id"); if ($result) { foreach ($result as $device) { $payload['aps']['account-id']=$device['aps_account_id']; apnsPushPayload($payload,$device['aps_device_token']); // This is a single MB, so we need to call once for each device registered } } } // Quick MySQL interface // // Auto connect code function _dbConnect() { global $db; // If we already have a connection if (isset($db['current'])) return $db['current']; // Connect if ($db['host']=='' || $db['host']=='*') $db['host']='localhost'; $db_conn=mysqli_connect($db['host'],$db['userid'],$db['password'],$db['database']) or terminate('Cannot connect to database'); // Save it for future use and we are done! $db['current']=$db_conn; // Save it for direct access return $db['current']; }; // Generic query, returning array of associative arrays (rows) function dbQuery($query) { // This is simple, so let's get to it $result=mysqli_query(_dbConnect(),$query) or terminate('Database query error'); // Group result in array if (mysqli_num_rows($result)>0) { while ($row=mysqli_fetch_assoc($result)) $rows[]=$row; } else { $rows=null; } mysqli_free_result($result); // Done return $rows; } // Generic insert/replace from associative array into id based table function dbWrite($table,$values) { // Prepare data $cols=$data=""; foreach ($values as $c => $d) { $cols=$cols.",`".$c."`"; $data=$data.",'$d'"; } $cols=substr($cols,1); $data=substr($data,1); // Perform insert/replace $replace=(isset($values['id']) && $values['id']>0); if ($replace) { $query="REPLACE INTO $table ($cols) VALUES ($data)"; } else { $query="INSERT INTO $table ($cols) VALUES ($data)"; } $db_conn=_dbConnect(); mysqli_query($db_conn,$query) or terminate('Database write error'); // We are done, return the associated id if ($replace) { return $values['id']; } else { return mysqli_insert_id($db_conn); } }; // Generic delete from table function dbDelete($table,$query) { $query="DELETE FROM $table WHERE $query"; mysqli_query(_dbConnect(),$query) or terminate('Database delete error'); }; // Generic timestamp col updater function dbTimeStamp($table,$col,$query) { $query="UPDATE $table SET $col=NOW() WHERE $query"; mysqli_query(_dbConnect(),$query) or terminate('Database timestamp update error'); }; // Retrive user and mailbox name from Cyrus Style IMAP URI function mbInfoFromURI($uri) { $uri=explode(';',$uri,2)[0]; // Take care of ";" $path=parse_url($uri,PHP_URL_PATH); $path=explode('/',$path,4); // [0] will be "" as starts with "/" $user=$path[2]; $mb=(isset($path[3])) ? $path[3] : 'INBOX'; return ['user'=>$user,'mb'=>$mb]; } // Push payload to a list of devices function apnsPushPayload($payload,$deviceTokens) { global $apns, $debug; // Initialize socket $sc=stream_context_create(); stream_context_set_option($sc,'ssl','local_cert',$apns['cert']); $socket=stream_socket_client("ssl://$apns[url]:2195",$error,$errorstr,$apns['timeout'],STREAM_CLIENT_CONNECT,$sc); if (!$socket) return false; // Loop to send the same payload to all user's registered devices $payload=json_encode($payload); foreach ((array) $deviceTokens as $dt) { $dt=str_replace(' ','', $dt); // No blanks $message="\x00\x00\x20".pack('H*',$dt)."\x00".chr(strlen($payload)).$payload; $result=fwrite($socket, $message); if (1 || $debug && $result) syslog(LOG_INFO, 'Push notification sent'); } // Finish up @socket_close($socket); @fclose($socket); return true; } // Simple termination funcion (send simple error to syslog) function terminate($msg) { global $db; // Dump the error to syslog syslog(LOG_ERR,$msg); // Now check for an MySQL error if (isset($db['current'])) { // We have a connection, so check for possible error $mySQLError=mysqli_error($db['current']); if ($mySQLError!='') syslog(LOG_INFO,'MySQL: '.$mySQLError); } exit; // No need for a status code } // debug: var_dump to syslog function syslogDumpit() { $n=func_num_args(); $v=func_get_args(); ob_start(); for ($i=0;$i<$n;$i++) var_dump($v[$i]); syslog(LOG_INFO,ob_get_clean()); } ?>