]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2006-2008,2010,2012-2013 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * io_sock.c - SecureTransport sample I/O module, X sockets version | |
5 | */ | |
6 | ||
7 | #include "ioSock.h" | |
8 | #include <errno.h> | |
9 | #include <stdio.h> | |
10 | #include <Security/SecBase.h> | |
11 | #include <unistd.h> | |
12 | #include <sys/types.h> | |
13 | #include <netinet/in.h> | |
14 | #include <sys/socket.h> | |
15 | #include <netdb.h> | |
16 | #include <arpa/inet.h> | |
17 | #include <fcntl.h> | |
18 | ||
19 | #include <Security/SecBase.h> | |
20 | ||
21 | #include <time.h> | |
22 | #include <strings.h> | |
23 | ||
24 | /* debugging for this module */ | |
25 | #define SSL_OT_DEBUG 1 | |
26 | ||
27 | /* log errors to stdout */ | |
28 | #define SSL_OT_ERRLOG 1 | |
29 | ||
30 | /* trace all low-level network I/O */ | |
31 | #define SSL_OT_IO_TRACE 0 | |
32 | ||
33 | /* if SSL_OT_IO_TRACE, only log non-zero length transfers */ | |
34 | #define SSL_OT_IO_TRACE_NZ 1 | |
35 | ||
36 | /* pause after each I/O (only meaningful if SSL_OT_IO_TRACE == 1) */ | |
37 | #define SSL_OT_IO_PAUSE 0 | |
38 | ||
39 | /* print a stream of dots while I/O pending */ | |
40 | #define SSL_OT_DOT 1 | |
41 | ||
42 | /* dump some bytes of each I/O (only meaningful if SSL_OT_IO_TRACE == 1) */ | |
43 | #define SSL_OT_IO_DUMP 0 | |
44 | #define SSL_OT_IO_DUMP_SIZE 256 | |
45 | ||
46 | /* indicate errSSLWouldBlock with a '.' */ | |
47 | #define SSL_DISPL_WOULD_BLOCK 0 | |
48 | ||
49 | /* general, not-too-verbose debugging */ | |
50 | #if SSL_OT_DEBUG | |
51 | #define dprintf(s) printf s | |
52 | #else | |
53 | #define dprintf(s) | |
54 | #endif | |
55 | ||
56 | /* errors --> stdout */ | |
57 | #if SSL_OT_ERRLOG | |
58 | #define eprintf(s) printf s | |
59 | #else | |
60 | #define eprintf(s) | |
61 | #endif | |
62 | ||
63 | /* trace completion of every r/w */ | |
64 | #if SSL_OT_IO_TRACE | |
65 | static void tprintf( | |
66 | const char *str, | |
67 | UInt32 req, | |
68 | UInt32 act, | |
69 | const UInt8 *buf) | |
70 | { | |
71 | #if SSL_OT_IO_TRACE_NZ | |
72 | if(act == 0) { | |
73 | return; | |
74 | } | |
75 | #endif | |
76 | printf("%s(%u): moved (%u) bytes\n", str, (unsigned)req, (unsigned)act); | |
77 | #if SSL_OT_IO_DUMP | |
78 | { | |
79 | unsigned i; | |
80 | ||
81 | for(i=0; i<act; i++) { | |
82 | printf("%02X ", buf[i]); | |
83 | if(i >= (SSL_OT_IO_DUMP_SIZE - 1)) { | |
84 | break; | |
85 | } | |
86 | } | |
87 | printf("\n"); | |
88 | } | |
89 | #endif | |
90 | #if SSL_OT_IO_PAUSE | |
91 | { | |
92 | char instr[20]; | |
93 | printf("CR to continue: "); | |
94 | gets(instr); | |
95 | } | |
96 | #endif | |
97 | } | |
98 | ||
99 | #else | |
100 | #define tprintf(str, req, act, buf) | |
101 | #endif /* SSL_OT_IO_TRACE */ | |
102 | ||
103 | /* | |
104 | * If SSL_OT_DOT, output a '.' every so often while waiting for | |
105 | * connection. This gives user a chance to do something else with the | |
106 | * UI. | |
107 | */ | |
108 | ||
109 | #if SSL_OT_DOT | |
110 | ||
111 | static time_t lastTime = (time_t)0; | |
112 | #define TIME_INTERVAL 3 | |
113 | ||
114 | static void outputDot() | |
115 | { | |
116 | time_t thisTime = time(0); | |
117 | ||
118 | if((thisTime - lastTime) >= TIME_INTERVAL) { | |
119 | printf("."); fflush(stdout); | |
120 | lastTime = thisTime; | |
121 | } | |
122 | } | |
123 | #else | |
124 | #define outputDot() | |
125 | #endif | |
126 | ||
127 | ||
128 | /* | |
129 | * One-time only init. | |
130 | */ | |
131 | void initSslOt(void) | |
132 | { | |
133 | ||
134 | } | |
135 | ||
136 | /* | |
137 | * Connect to server. | |
138 | */ | |
139 | #define GETHOST_RETRIES 3 | |
140 | ||
141 | OSStatus MakeServerConnection( | |
142 | const char *hostName, | |
143 | int port, | |
144 | int nonBlocking, // 0 or 1 | |
145 | otSocket *socketNo, // RETURNED | |
146 | PeerSpec *peer) // RETURNED | |
147 | { | |
148 | struct sockaddr_in addr; | |
149 | struct hostent *ent = NULL; | |
150 | struct in_addr host; | |
151 | int sock = 0; | |
152 | ||
153 | *socketNo = 0; | |
154 | if (hostName[0] >= '0' && hostName[0] <= '9') | |
155 | { | |
156 | host.s_addr = inet_addr(hostName); | |
157 | } | |
158 | else { | |
159 | unsigned dex; | |
160 | /* seeing a lot of soft failures here that I really don't want to track down */ | |
161 | for(dex=0; dex<GETHOST_RETRIES; dex++) { | |
162 | if(dex != 0) { | |
163 | printf("\n...retrying gethostbyname(%s)", hostName); | |
164 | } | |
165 | ent = gethostbyname(hostName); | |
166 | if(ent != NULL) { | |
167 | break; | |
168 | } | |
169 | } | |
170 | if(ent == NULL) { | |
171 | printf("\n***gethostbyname(%s) returned: %s\n", hostName, hstrerror(h_errno)); | |
172 | return errSecIO; | |
173 | } | |
174 | memcpy(&host, ent->h_addr, sizeof(struct in_addr)); | |
175 | } | |
176 | sock = socket(AF_INET, SOCK_STREAM, 0); | |
177 | addr.sin_addr = host; | |
178 | addr.sin_port = htons((u_short)port); | |
179 | ||
180 | addr.sin_family = AF_INET; | |
181 | if (connect(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) != 0) | |
182 | { | |
183 | perror("connect returned error"); | |
184 | return errSecIO; | |
185 | } | |
186 | ||
187 | if(nonBlocking) { | |
188 | /* OK to do this after connect? */ | |
189 | int rtn = fcntl(sock, F_SETFL, O_NONBLOCK); | |
190 | if(rtn == -1) { | |
191 | perror("fctnl(O_NONBLOCK)"); | |
192 | return errSecIO; | |
193 | } | |
194 | } | |
195 | ||
196 | peer->ipAddr = addr.sin_addr.s_addr; | |
197 | peer->port = htons((u_short)port); | |
198 | *socketNo = (otSocket)sock; | |
199 | return errSecSuccess; | |
200 | } | |
201 | ||
202 | /* | |
203 | * Set up an otSocket to listen for client connections. Call once, then | |
204 | * use multiple AcceptClientConnection calls. | |
205 | */ | |
206 | OSStatus ListenForClients( | |
207 | int port, | |
208 | int nonBlocking, // 0 or 1 | |
209 | otSocket *socketNo) // RETURNED | |
210 | { | |
211 | struct sockaddr_in addr; | |
212 | struct hostent *ent; | |
213 | int len; | |
214 | int sock; | |
215 | ||
216 | sock = socket(AF_INET, SOCK_STREAM, 0); | |
217 | if(sock < 1) { | |
218 | perror("socket"); | |
219 | return errSecIO; | |
220 | } | |
221 | ||
222 | int reuse = 1; | |
223 | int err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); | |
224 | if (err != 0) { | |
225 | perror("setsockopt"); | |
226 | return err; | |
227 | } | |
228 | ||
229 | ent = gethostbyname("localhost"); | |
230 | if (!ent) { | |
231 | perror("gethostbyname"); | |
232 | return errSecIO; | |
233 | } | |
234 | memcpy(&addr.sin_addr, ent->h_addr, sizeof(struct in_addr)); | |
235 | ||
236 | addr.sin_port = htons((u_short)port); | |
237 | addr.sin_addr.s_addr = INADDR_ANY; | |
238 | addr.sin_family = AF_INET; | |
239 | len = sizeof(struct sockaddr_in); | |
240 | if (bind(sock, (struct sockaddr *) &addr, len)) { | |
241 | int theErr = errno; | |
242 | perror("bind"); | |
243 | if(theErr == EADDRINUSE) { | |
244 | return errSecOpWr; | |
245 | } | |
246 | else { | |
247 | return errSecIO; | |
248 | } | |
249 | } | |
250 | if(nonBlocking) { | |
251 | int rtn = fcntl(sock, F_SETFL, O_NONBLOCK); | |
252 | if(rtn == -1) { | |
253 | perror("fctnl(O_NONBLOCK)"); | |
254 | return errSecIO; | |
255 | } | |
256 | } | |
257 | ||
258 | for(;;) { | |
259 | int rtn = listen(sock, 1); | |
260 | switch(rtn) { | |
261 | case 0: | |
262 | *socketNo = (otSocket)sock; | |
263 | rtn = errSecSuccess; | |
264 | break; | |
265 | case EWOULDBLOCK: | |
266 | continue; | |
267 | default: | |
268 | perror("listen"); | |
269 | rtn = errSecIO; | |
270 | break; | |
271 | } | |
272 | return rtn; | |
273 | } | |
274 | } | |
275 | ||
276 | /* | |
277 | * Accept a client connection. | |
278 | */ | |
279 | ||
280 | /* | |
281 | * Currently we always get back a different peer port number on successive | |
282 | * connections, no matter what the client is doing. To test for resumable | |
283 | * session support, force peer port = 0. | |
284 | */ | |
285 | #define FORCE_ACCEPT_PEER_PORT_ZERO 1 | |
286 | ||
287 | OSStatus AcceptClientConnection( | |
288 | otSocket listenSock, // obtained from ListenForClients | |
289 | otSocket *acceptSock, // RETURNED | |
290 | PeerSpec *peer) // RETURNED | |
291 | { | |
292 | struct sockaddr_in addr; | |
293 | int sock; | |
294 | socklen_t len; | |
295 | ||
296 | len = sizeof(struct sockaddr_in); | |
297 | do { | |
298 | sock = accept((int)listenSock, (struct sockaddr *) &addr, &len); | |
299 | if (sock < 0) { | |
300 | if(errno == EAGAIN) { | |
301 | /* nonblocking, no connection yet */ | |
302 | continue; | |
303 | } | |
304 | else { | |
305 | perror("accept"); | |
306 | return errSecIO; | |
307 | } | |
308 | } | |
309 | else { | |
310 | break; | |
311 | } | |
312 | } while(1); | |
313 | *acceptSock = (otSocket)sock; | |
314 | peer->ipAddr = addr.sin_addr.s_addr; | |
315 | #if FORCE_ACCEPT_PEER_PORT_ZERO | |
316 | peer->port = 0; | |
317 | #else | |
318 | peer->port = ntohs(addr.sin_port); | |
319 | #endif | |
320 | return errSecSuccess; | |
321 | } | |
322 | ||
323 | /* | |
324 | * Shut down a connection. | |
325 | */ | |
326 | void endpointShutdown( | |
327 | otSocket sock) | |
328 | { | |
329 | close((int)sock); | |
330 | } | |
331 | ||
332 | /* | |
333 | * R/W. Called out from SSL. | |
334 | */ | |
335 | OSStatus SocketRead( | |
336 | SSLConnectionRef connection, | |
337 | void *data, /* owned by | |
338 | * caller, data | |
339 | * RETURNED */ | |
340 | size_t *dataLength) /* IN/OUT */ | |
341 | { | |
342 | size_t bytesToGo = *dataLength; | |
343 | size_t initLen = bytesToGo; | |
344 | UInt8 *currData = (UInt8 *)data; | |
345 | int sock = (int)((long)connection); | |
346 | OSStatus rtn = errSecSuccess; | |
347 | ssize_t rrtn; | |
348 | ||
349 | *dataLength = 0; | |
350 | ||
351 | for(;;) { | |
352 | /* paranoid check, ensure errno is getting written */ | |
353 | errno = -555; | |
354 | rrtn = recv(sock, currData, bytesToGo, 0); | |
355 | if (rrtn <= 0) { | |
356 | if(rrtn == 0) { | |
357 | /* closed, EOF */ | |
358 | rtn = errSSLClosedGraceful; | |
359 | break; | |
360 | } | |
361 | int theErr = errno; | |
362 | switch(theErr) { | |
363 | case ENOENT: | |
364 | /* | |
365 | * Undocumented but I definitely see this. | |
366 | * Non-blocking sockets only. Definitely retriable | |
367 | * just like an EAGAIN. | |
368 | */ | |
369 | dprintf(("SocketRead RETRYING on ENOENT, rrtn %d\n", | |
370 | (int)rrtn)); | |
371 | /* normal... */ | |
372 | //rtn = errSSLWouldBlock; | |
373 | /* ...for temp testing.... */ | |
374 | rtn = errSecIO; | |
375 | break; | |
376 | case ECONNRESET: | |
377 | /* explicit peer abort */ | |
378 | rtn = errSSLClosedAbort; | |
379 | break; | |
380 | case EAGAIN: | |
381 | /* nonblocking, no data */ | |
382 | rtn = errSSLWouldBlock; | |
383 | break; | |
384 | default: | |
385 | dprintf(("SocketRead: read(%u) error %d, rrtn %d\n", | |
386 | (unsigned)bytesToGo, theErr, (int)rrtn)); | |
387 | rtn = errSecIO; | |
388 | break; | |
389 | } | |
390 | /* in any case, we're done with this call if rrtn <= 0 */ | |
391 | break; | |
392 | } | |
393 | bytesToGo -= rrtn; | |
394 | currData += rrtn; | |
395 | ||
396 | if(bytesToGo == 0) { | |
397 | /* filled buffer with incoming data, done */ | |
398 | break; | |
399 | } | |
400 | } | |
401 | *dataLength = initLen - bytesToGo; | |
402 | tprintf("SocketRead", initLen, *dataLength, (UInt8 *)data); | |
403 | ||
404 | #if SSL_OT_DOT || (SSL_OT_DEBUG && !SSL_OT_IO_TRACE) | |
405 | if((rtn == 0) && (*dataLength == 0)) { | |
406 | /* keep UI alive */ | |
407 | outputDot(); | |
408 | } | |
409 | #endif | |
410 | #if SSL_DISPL_WOULD_BLOCK | |
411 | if(rtn == errSSLWouldBlock) { | |
412 | printf("."); fflush(stdout); | |
413 | } | |
414 | #endif | |
415 | return rtn; | |
416 | } | |
417 | ||
418 | int oneAtATime = 0; | |
419 | ||
420 | OSStatus SocketWrite( | |
421 | SSLConnectionRef connection, | |
422 | const void *data, | |
423 | size_t *dataLength) /* IN/OUT */ | |
424 | { | |
425 | size_t bytesSent = 0; | |
426 | int sock = (int)((long)connection); | |
427 | size_t length; | |
428 | size_t dataLen = *dataLength; | |
429 | const UInt8 *dataPtr = (UInt8 *)data; | |
430 | OSStatus ortn; | |
431 | ||
432 | if(oneAtATime && (*dataLength > 1)) { | |
433 | size_t i; | |
434 | size_t outLen; | |
435 | size_t thisMove; | |
436 | ||
437 | outLen = 0; | |
438 | for(i=0; i<dataLen; i++) { | |
439 | thisMove = 1; | |
440 | ortn = SocketWrite(connection, dataPtr, &thisMove); | |
441 | outLen += thisMove; | |
442 | dataPtr++; | |
443 | if(ortn) { | |
444 | return ortn; | |
445 | } | |
446 | } | |
447 | return errSecSuccess; | |
448 | } | |
449 | *dataLength = 0; | |
450 | ||
451 | do { | |
452 | length = write(sock, | |
453 | (char*)dataPtr + bytesSent, | |
454 | dataLen - bytesSent); | |
455 | } while ((length > 0) && | |
456 | ( (bytesSent += length) < dataLen) ); | |
457 | ||
458 | if(length <= 0) { | |
459 | int theErr = errno; | |
460 | switch(theErr) { | |
461 | case EAGAIN: | |
462 | ortn = errSSLWouldBlock; break; | |
463 | case EPIPE: | |
464 | ortn = errSSLClosedAbort; break; | |
465 | default: | |
466 | dprintf(("SocketWrite: write(%u) error %d\n", | |
467 | (unsigned)(dataLen - bytesSent), theErr)); | |
468 | ortn = errSecIO; | |
469 | break; | |
470 | } | |
471 | } | |
472 | else { | |
473 | ortn = errSecSuccess; | |
474 | } | |
475 | tprintf("SocketWrite", dataLen, bytesSent, dataPtr); | |
476 | *dataLength = bytesSent; | |
477 | return ortn; | |
478 | } |