Python APNs does not process request - iphone

I'm trying to implement a server side script for sending push notifications to apple push notification server. I create the ssl connection, I send the payload - but am unable to get a response from the APNs. Here is my code:
import socket, ssl, pprint, struct, time, binascii
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# require a certificate from the server
ssl_sock = ssl.wrap_socket( s,
keyfile="/Users/Jeff/Desktop/pickmeup-key2-noenc.pem",
certfile="/Users/Jeff/Desktop/pickmeup-cert2.pem",
server_side=False,
do_handshake_on_connect=True,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs="/Users/Jeff/Desktop/entrustrootcert.pem",)
#ciphers="ALL")
ssl_sock.connect(('gateway.sandbox.push.apple.com', 2195))
print repr(ssl_sock.getpeername())
print ssl_sock.cipher()
print pprint.pformat(ssl_sock.getpeercert())
command = '\x00'
identifier = 1987
expiry = time.time()
deviceToken = "9858d81caa236a86cc67d01e1a07ba1df0982178dd7c95aae115d033b93cb3f5"
alert = "This is a test message"
sound = "UILocalNotificationDefaultSoundName"
payload = "{\"aps\":{\"alert\":\"%s\",\"sound\":\"%s\"}}" %(alert, sound)
packetFormat = "!cIIH%dsH%ds" %(32, len(payload))
packet = struct.pack(packetFormat,
command,
identifier,
int(expiry),
32,
binascii.unhexlify(deviceToken),
len(payload),
payload)
nBytesWritten = ssl_sock.write(packet)
print "nBytesWritten = %d" %(nBytesWritten)
data = ssl_sock.read(1024)
print len(data)
ssl_sock.close()
Running this script, I generate the following output:
('17.149.34.132', 2195)
('AES256-SHA', 'TLSv1/SSLv3', 256)
{'notAfter': 'May 31 00:04:27 2012 GMT',
'subject': ((('countryName', u'US'),),
(('stateOrProvinceName', u'California'),),
(('localityName', u'Cupertino'),),
(('organizationName', u'Apple Inc'),),
(('organizationalUnitName', u'Internet Services'),),
(('commonName', u'gateway.sandbox.push.apple.com'),))}
nBytesWritten = 133
0
Any ideas on what might be going wrong? (I am sending enhanced push notifications so I am expecting a response from apple push notification server)

The key thing to note is that read() is returning no data. In Python, read() is supposed to block until data is available or the connection closes. Apple is closing your connection.
Why? Well, probably because you sent a malformed request. command=0 is a normal push notification; command=1 is enhanced. The big-endian 1987 will be interpreted as a 0-byte device token and a 1987-byte payload, neither of which are valid.
(And FWIW, I'd use B instead of c for the command ID; it seems to make more sense.)

you may consider https://github.com/djacobs/PyAPNs that wrapped lot of useful features, including:
error handling
support enhanced message format and auto resend messages which are sent before error response
non-blocking ssl socket connection with great performance

Apple Push notification server doesn't give a response, it's a one-way binary socket.
Rather than rolling your own solution you could try apns-python-wrapper or apns

Related

Perl Net::SSLeay check if socket is available to read

I am using a perl module called Net::APNS::Persistent. It helps me to open up a persistent connection with apple's apns server and send push notifications through APNS. This module uses Net::SSLeay for ssl communication with APNS server.
Now, I want to read from my socket periodically to check if APNS sends back any response. Net::APNS::Persistent already has a function called _read() which looks like below:
sub _read {
my $self = shift;
my ($socket, $ctx, $ssl) = #{$self->_connection};
my $data = Net::SSLeay::ssl_read_all( $ssl );
die_if_ssl_error("error reading from ssl connection: $!");
return $data;
}
However, this function works only after APNS drops the connection and I get error while trying to write. On other times my script gets stuck at,
my $data = Net::SSLeay::ssl_read_all( $ssl );
I checked Net::SSLeay doc and found it has a method called peek
Copies $max bytes from the specified $ssl into the returned value. In contrast to the Net::SSLeay::read() function, the data in the SSL buffer is unmodified after the SSL_peek() operation.
I though it might be useful, so I added another function within the Net::APNS::Persistent module:
sub ssl_peek {
my $self = shift;
my ($socket, $ctx, $ssl) = #{$self->_connection};
print "Peeking \n";
my $data = Net::SSLeay::peek( $ssl, $pending );
print "Done peeking \n";
return $data;
}
Unfortunately this also gave me the same problem. It only prints Peeking and never reaches the line where it would print Done peeking. Had same problem using Net::SSLeay::read. Is there a way to check if the socket can be read or maybe set a read timeout so that my script doesnt get stuck while trying to read from socket?
The APNS documentation says the following:
If you send a notification that is accepted by APNs, nothing is returned.
If you send a notification that is malformed or otherwise unintelligible, APNs returns an error-response packet and closes the connection. Any notifications that you sent after the malformed notification using the same connection are discarded, and must be resent
As long as your notifications as accepted, there won't be any data to read and thus a read operation on the socket will block. The only time there's data available is when there's an error, and then the connection is immediately closed. That should explain the behaviour you're observing.
To check if the underlying socket can be read use select, i.e.
IO::Select->new(fileno($socket))->can_read(timeout);
timeout can be 0 to just check and not wait, can be a number of seconds or can be undef to wait forever. But before you do the select check if data are still available in the SSL buffer:
if (Net::SSLeay::pending($ssl)) { ... use SSL_peek or SSL_read ... }
Apart from that it does look like that the module you use does not even attempt to validate the servers certificate :(

Perl: proper way to read APNS error_response

I have implemented a Perl script that sends push notifications through Apples apns services. I am having some issues with the error handling. As per APNS documentation:
If the stream isn't ready for writing, see if the stream is available for reading. If it is, read everything available from the stream. If you get zero bytes back, the connection was closed because of an error such as an invalid command byte or other parsing error. If you get six bytes back, that's an error response that you can check for the response code and the ID of the notification that caused the error. You'll need to send every notification following that one again.
I am doing the same. Whenever I get a write error due to connection drop; I read the socket. Everytime I get 6 bytes return from the socket. Meaning APNS is sending me back an error_response. The format of error-response packet as per APNS documentation is like below
The packet has a command value of 8 followed by a one-byte status code and the notification identifier of the malformed notification.
I am using the below code to unpack the data I read from the socket:
my $hex = unpack( 'H*', $data );
print $hex;
Everytime, I get the same value 080800000000. As per APNS documentation the first byte will always be 8, the next byte will represent the error status code. 8 means "Invalid Token". Up to this part it is OK. However, the remaining 4 bytes which is the identifier, always gives me 00000000. What does it mean ?
APNS has two different push notification format, Simple Notification Format and Enhanced Notification Format. The Simple Notification Format does not have a field for specifying the message ID (notification identifier). I was using a Perl module (Net::APNS::Persistent) for communicating with APNS; that only supports Simple Notification Format. Thats why I was always getting 00000000 for the notification identifier part. I updated the code of the module to use the enhanced format which is :
pack(
'cNNnH*na*', # format
1, # command
$id, # Notification identifier
0, # expiry timestamp
32, # token length
$devicetoken, # token
length $json, # payload length
$json # payload
);
Then for reading the response I used the below message format:
my($c,$status,$identifier) = unpack('ccN',$error);
Where $error is the response from APNS. Now the whole thing is working fine.

My SMTP server can't process the incoming DATA of certain SMTP server

We used a SMTP server to receive emails, It works perfectly when I send an email to my server from gmail or hotmail. But I'm having problems with a specific company ( I will name it company x), we can read the sender, the recipient, etc, but when it comes to the DATA, the buffered reader hangs forever reading the line on the socket.
This is what happen when I receive the email from the company x:
When I receive a company x's email. they send me the EHLO command
my server returns 500 command unrecognised
the company x's server send the HELO command
my server sends 250 ok to the company x's server.
the copany x's server send me MAIL From: <sender#email.com>
my server sends 250 ok
the company x's server send me RCPT To:<recipient#email.com>
my server sends 250 ok
the company x's server sends DATA
my server sends 354 Start mail input; end with <CRLF>.<CRLF>
At this moment the server hangs forever reading the incoming data (in the in.readline()) and my server throws socket time out exception. (obviously we tried increasing the time out, but it didn't work)
what could be the difference with the company x's SMTP server and the gmail or hotmail server??, what is the problem???
We have the same error with the Java mail server and the james mail server.
The company x, is a bank so they have a high information security level.
here is the code how we send it.
private static final String MESSAGE_SEND_DATA = "354 Start mail input; end with <CRLF>.<CRLF>";
and this is the method that write the comand on the outputstream.
private void write( String message ) {
if( log.isDebugEnabled() ) { log.debug( "Writing: " + message ); }
out.print( message + "\r\n" );
out.flush();
}
we call write after we receive the data commanx from the client.
Most likely it hangs forever awaiting for line terminator symbol. BufferedReader#readLine is platform dependent in terms of line terminators, so it's a poor Reader choice for SMTP server implementation. As a matter of fact, I would advice not to use BufferedReader and PrintWriter for socket-originated streams to avoid platform-dependent issues like this one.
All SMTP server implementations in Java I'm aware of (Apache James, SubethaSMTP) use dedicated Reader implementations, focused on CRLF-terminated lines to read lines from a Socket, in full accordance with RFC 5321. So my advice would be to try one of these readers (for example this one) instead of BufferedReader.

Read from Half Open Socket

I am trying to connect to Apple Push Notification Service which uses a simple binary protocol over TCP protected with TLS (or SSL). The protocol indicates that when an error is encountered (there are about 10 well defined error conditions) APNS will send back an error response and then close the connection. This results in a half closed socket because the remote peer closed the socket. I can see its a graceful shutdown because APNS sends a FIN and RST using tcpdump.
Out of all the error conditions, I can deal with most before sending with validation. The situation in which this fails is when a notification is sent to an invalid device token which cannot be dealt with that easily because the tokens could be malformed. Tokens are opaque 32 byte values that are provided by APNS to a device and then registered with me. I have no way of knowing if it is valid when submitted to my service. Presumably APNS checksums the tokens in some way that they can do quick validation on the token fast.
Anyway,
I did what I thought was the right thing:-
a. open socket
b. try writing
c. if write failed, read the error response
Unfortunately, this doesn't seem to work. I figure APNS is sending an error response and I am not reading it back right or I am not setting the socket up right. I have tried the following techniques:-
Use a separate thread per socket to try-read the response if any every 5ms or so.
Use a blocking read after write failure.
Use a final read after remote disconnect.
I have tried this with C# + .NET 4.5 on Windows and Java 1.7 on Linux. In either case, I never seem to get the error response and the socket indicates that no data is available to read.
Are half-closed sockets supported on these operating systems and/or frameworks? There isn't anything that seems to indicate either way.
I know that the way I am setting things up works correctly because if I use a valid token with a valid notification, those do get delivered.
In response to one of the comments, I am using the enhanced notification format so a response should arrive from APNS.
Here is the code I have for C#:-
X509Certificate certificate =
new X509Certificate(#"Foo.cer", "password");
X509CertificateCollection collection = new X509CertificateCollection();
collection.Add(certificate);
Socket socket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect("gateway.sandbox.push.apple.com", 2195);
NetworkStream stream =
new NetworkStream(socket, System.IO.FileAccess.ReadWrite, false);
stream.ReadTimeout = 1000;
stream.WriteTimeout = 1000;
sslStream =
new SslStream(stream, true,
new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com", collection,
SslProtocols.Default, false);
sslStream.ReadTimeout = 10000;
sslStream.WriteTimeout = 1000;
// Task rdr = Task.Factory.StartNew(this.reader);
// rdr is used for parallel read of socket sleeping 5ms between each read.
// Not used now but another alternative that was tried.
Random r = new Random(DateTime.Now.Second);
byte[] buffer = new byte[32];
r.NextBytes(buffer);
byte[] resp = new byte[6];
String erroneousToken = toHex(buffer);
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
int timestamp = (int) t.TotalSeconds;
try
{
for (int i = 0; i < 1000; ++i)
{
// build the notification; format is published in APNS docs.
var not = new ApplicationNotificationBuilder().withToken(buffer).withPayload(
#'{"aps": {"alert":"foo","sound":"default","badge":1}}').withExpiration(
timestamp).withIdentifier(i+1).build();
sslStream.Write(buffer);
sslStream.Flush();
Console.Out.WriteLine("Sent message # " + i);
int rd = sslStream.Read(resp, 0, 6);
if (rd > 0)
{
Console.Out.WriteLine("Found response: " + rd);
break;
}
// doesn't really matter how fast or how slow we send
Thread.Sleep(500);
}
}
catch (Exception ex)
{
Console.Out.WriteLine("Failed to write ...");
int rd = sslStream.Read(resp, 0, 6);
if (rd > 0)
{
Console.Out.WriteLine("Found response: " + rd); ;
}
}
// rdr.Wait(); change to non-infinite timeout to allow error reader to terminate
I implemented server side for APNS in Java and have problems reading the error responses reliably (meaning - never miss any error response), but I do manage to get error responses.
You can see this related question, though it has no adequate answer.
If you never manage to read the error response, there must be something wrong with your code.
Using a separate thread for reading worked for me, though not 100% reliable.
Use a blocking read after write fail - that's what Apple suggest to do, but it doesn't always work. It's possible that you send 100 messages, and the first has an invalid token, and only after the 100th message you get a write failure. At this point it is sometimes too late to read the error response from the socket.
I'm not sure what you mean there.
If you want to guarantee that the reading of the error responses will work, you should try to read after each write, with a sufficient timeout. This, of course, is not practical for using in production (since it's incredibly slow), but you can use it to verify that your code of reading and parsing the error response is correct. You can also use it to iterate over all the device tokens you have, and find all the invalid ones, in order to clean your DB.
You didn't post any code, so I don't know what binary format you are using to send messages to APNS. If you are using the simple format (that starts with a 0 byte and has no message ID), you won't get any responses from Apple.

WebSocket Server Data Framing (RFC6455)

I have a problem. When I want to achieve WebSocket server, the server can't send data to the client (in Chrome 16). For example, sending the text "Hello", the server sends the data framing "0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f" to the client, but the browser can't receive the data. Is this code wrong?
sub getSendDataNoMask{
my $dataStr="Hello";
my #ret;
push(#ret,pack("H*","81"));
push(#ret,pack("H*","05"));
push(#ret,$dataStr);
return join("",#ret);
}
What error do you get from the Chrome Javascript console?
You also didn't post your handshake code (the more likely thing to have a problem). Are you certain that the handshake was completed successfully? In other words, did you get an onopen event in the browser?
var ws = WebSocket("ws://myhost:6080/websocket");
ws.onopen = function (e) {
console.log("connection opened");
};
ws.onmessage
console.log("Got data: " + e.data);
};
If you didn't get an opopen event then the handshake never finished successfully. If you are getting on onopen event, then I would try sending data the opposite direction and make sure you can receive and decode frames from your perl server before trying to send.