Commit | Line | Data |
---|---|---|
d667da5b IK |
1 | /* tclink.c - Library code for the TCLink client API. |
2 | * | |
3 | * TCLink Copyright (c) 2013 TrustCommerce. | |
4 | * http://www.trustcommerce.com | |
5 | * techsupport@trustcommerce.com | |
6 | * (949) 387-3747 | |
7 | * | |
8 | * This library is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License as published by the Free Software Foundation; either | |
11 | * version 2.1 of the License, or (at your option) any later version. | |
12 | * | |
13 | * This library is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * Lesser General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU Lesser General Public | |
19 | * License along with this library; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | */ | |
22 | ||
23 | #include "tclink.h" | |
24 | ||
25 | #include <stdio.h> | |
26 | #include <memory.h> | |
27 | #include <errno.h> | |
28 | #include <string.h> | |
29 | ||
30 | #ifdef WIN32 | |
31 | #include <io.h> | |
32 | #include <winsock2.h> | |
33 | #else | |
34 | #include <strings.h> | |
35 | #include <sys/time.h> | |
36 | #include <sys/types.h> | |
37 | #include <sys/socket.h> | |
38 | #include <netinet/in.h> | |
39 | #include <arpa/inet.h> | |
40 | #include <netdb.h> | |
41 | #include <unistd.h> | |
42 | #endif | |
43 | ||
44 | #include <sys/stat.h> | |
45 | #include <sys/types.h> | |
46 | #include <stdlib.h> | |
47 | #include <fcntl.h> | |
48 | #include <signal.h> | |
49 | ||
50 | #define OPENSSL_NO_KRB5 1 | |
51 | ||
52 | #include <openssl/crypto.h> | |
53 | #include <openssl/x509.h> | |
54 | #include <openssl/pem.h> | |
55 | #include <openssl/ssl.h> | |
56 | #include <openssl/err.h> | |
57 | #include <openssl/rand.h> | |
58 | ||
59 | #ifdef WIN32 | |
60 | #define strcasecmp(x,y) stricmp(x,y) | |
61 | #else | |
62 | #define closesocket(x) close(x) | |
63 | #endif | |
64 | ||
65 | #define DEFAULT_HOST "pgw1.trustcommerce.com" | |
66 | ||
67 | /* changed from forty second to one hundred second to reflect more complicated transaction processing logic */ | |
68 | #define TIMEOUT 100 /* seconds */ | |
69 | #define TC_BUFF_MAX 16000 | |
70 | #define TC_LINE_MAX ((PARAM_MAX_LEN * 2) + 2) | |
71 | ||
72 | char *tclink_version = TCLINK_VERSION; /* TCLINK_VERSION is defined in Makefile */ | |
73 | char *tclink_host = DEFAULT_HOST; | |
74 | int tclink_port = 443; | |
75 | ||
76 | /*************************************************/ | |
77 | /* Data structures used only within this module. */ | |
78 | /*************************************************/ | |
79 | ||
80 | /* Variables used for transaction data. */ | |
81 | ||
82 | typedef struct param_data | |
83 | { | |
84 | char *name; | |
85 | char *value; | |
86 | struct param_data *next; | |
87 | } param; | |
88 | ||
89 | typedef struct _TCLinkCon | |
90 | { | |
91 | /* Connection data */ | |
92 | int *ip; | |
93 | int num_ips; | |
94 | int sd; | |
95 | ||
96 | /* SSL encryption */ | |
97 | const SSL_METHOD *meth; | |
98 | long ctx_options; | |
99 | SSL_CTX *ctx; | |
100 | SSL *ssl; | |
101 | ||
102 | /* Transaction parameters, sent and received */ | |
103 | param *send_param_list, *send_param_tail; | |
104 | param *recv_param_list; | |
105 | ||
106 | /* Connection status */ | |
107 | int is_error; | |
108 | int pass; | |
109 | time_t start_time; | |
110 | int dns; | |
111 | ||
112 | char * trusted_ca_pem; | |
113 | int (*validate_cert)(int, void *); | |
114 | int full_ssl_close; | |
115 | ||
116 | } TCLinkCon; | |
117 | ||
118 | /************************************* | |
119 | * Internal functions, not exported. * | |
120 | *************************************/ | |
121 | ||
122 | /* Random number from min to max. */ | |
123 | static int number(int min, int max) | |
124 | { | |
125 | return (rand() % (max - min + 1)) + min; | |
126 | } | |
127 | ||
128 | /* Check if path points to a regular file */ | |
129 | int is_regular_file(const char* path) | |
130 | { | |
131 | struct stat st; | |
132 | stat(path, &st); | |
133 | return S_ISREG(st.st_mode); | |
134 | } | |
135 | ||
136 | /* Safe string copy and append functions. */ | |
137 | #define SAFE_COPY(d, s) safe_copy((d), (s), sizeof(d)); | |
138 | #define SAFE_APPEND(d, s) safe_append((d), (s), sizeof(d)); | |
139 | ||
140 | static void safe_copy(char *dst, const char *src, int size) | |
141 | { | |
142 | int len = strlen(src); | |
143 | if (len < size) | |
144 | strcpy(dst, src); | |
145 | else { | |
146 | strncpy(dst, src, size - 1); | |
147 | dst[size-1] = 0; | |
148 | } | |
149 | } | |
150 | ||
151 | static void safe_append(char *dst, const char *src, int size) | |
152 | { | |
153 | int dlen = strlen(dst); | |
154 | int slen = strlen(src); | |
155 | int avail = size - dlen; | |
156 | if (avail < 1) | |
157 | return; | |
158 | ||
159 | if (slen < avail) | |
160 | strcpy(dst+dlen, src); | |
161 | else { | |
162 | strncpy(dst+dlen, src, avail - 1); | |
163 | dst[size-1] = 0; | |
164 | } | |
165 | } | |
166 | ||
167 | /* Add a parameter-value pair to the recieved list. */ | |
168 | static void AddRecvParam(TCLinkCon *c, const char *name, const char *value) | |
169 | { | |
170 | param *p; | |
171 | ||
172 | if (name[0] == 0 || value[0] == 0) | |
173 | return; | |
174 | ||
175 | p = (param *)malloc(sizeof(param)); | |
176 | p->name = strdup(name); | |
177 | p->value = strdup(value); | |
178 | p->next = c->recv_param_list; | |
179 | c->recv_param_list = p; | |
180 | } | |
181 | ||
182 | /* Add a string to the received list. */ | |
183 | static int AddRecvString(TCLinkCon *c, char *string) | |
184 | { | |
185 | char *ptr = strchr(string, '='); | |
186 | if (ptr == NULL) | |
187 | return 0; | |
188 | ||
189 | *ptr = 0; | |
190 | AddRecvParam(c, string, ptr+1); | |
191 | ||
192 | return 1; | |
193 | } | |
194 | ||
195 | /* Deallocate the send list. */ | |
196 | static void ClearSendList(TCLinkCon *c) | |
197 | { | |
198 | param *p, *next; | |
199 | for (p = c->send_param_list; p; p = next) | |
200 | { | |
201 | next = p->next; | |
202 | free(p->name); | |
203 | free(p->value); | |
204 | free(p); | |
205 | } | |
206 | ||
207 | c->send_param_list = c->send_param_tail = NULL; | |
208 | } | |
209 | ||
210 | /* Deallocate the recv list. */ | |
211 | static void ClearRecvList(TCLinkCon *c) | |
212 | { | |
213 | param *p, *next; | |
214 | for (p = c->recv_param_list; p; p = next) | |
215 | { | |
216 | next = p->next; | |
217 | free(p->name); | |
218 | free(p->value); | |
219 | free(p); | |
220 | } | |
221 | ||
222 | c->recv_param_list = NULL; | |
223 | } | |
224 | ||
225 | /* Open a socket to the host_ip specified. Returns the socket's file | |
226 | * descriptor on success (the open attempt is underway) or -1 for failure | |
227 | * (should never happen in practice). Note that this function DOES NOT block | |
228 | * and wait for the connection; you'll need to select() on the socket later to see | |
229 | * if it opened successfully. | |
230 | */ | |
231 | static int BeginConnection(TCLinkCon *c, int host_ip) | |
232 | { | |
233 | struct sockaddr_in sa; | |
234 | int sd; | |
235 | ||
236 | sd = socket(AF_INET, SOCK_STREAM, 0); | |
237 | if (sd < 0) | |
238 | return -1; | |
239 | ||
240 | #ifdef WIN32 | |
241 | u_long param = 1; | |
242 | ioctlsocket(sd, FIONBIO, ¶m); | |
243 | #else | |
244 | fcntl(sd, F_SETFL, O_NONBLOCK); | |
245 | #endif | |
246 | ||
247 | memset(&sa, 0, sizeof(sa)); | |
248 | sa.sin_family = AF_INET; | |
249 | sa.sin_addr.s_addr = host_ip; | |
250 | sa.sin_port = htons(tclink_port); | |
251 | ||
252 | connect(sd, (struct sockaddr *) &sa, sizeof(sa)); | |
253 | ||
254 | return sd; | |
255 | } | |
256 | ||
257 | /* This function is called on a socket file descriptor once the connection has been | |
258 | * established and we're ready to negotiate SSL. If the SSL handshake fails for some | |
259 | * reason (such as the host on the other end not using SSL), it will return 0 for | |
260 | * failure. Success returns 1. | |
261 | */ | |
262 | static int FinishConnection(TCLinkCon *c, int sd) | |
263 | { | |
264 | int ssl_connected, is_error, errcode, res; | |
265 | X509 *server_cert; | |
266 | time_t start, remaining; | |
267 | fd_set in, out, err; | |
268 | struct timeval tv; | |
269 | ||
270 | /* check if socket has connected successfully */ | |
271 | int val; | |
272 | int /*socklen_t*/ size = 4; | |
273 | getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*)&val, &size); | |
274 | if (val != 0) | |
275 | return 0; | |
276 | ||
277 | SSL_clear(c->ssl); | |
278 | ||
279 | SSL_set_fd(c->ssl, sd); | |
280 | ||
281 | ssl_connected = 0; | |
282 | is_error = 0; | |
283 | start = time(0); | |
284 | ||
285 | while (!ssl_connected && !is_error) | |
286 | { | |
287 | ||
288 | remaining = 5 - (time(0) - start); | |
289 | if (remaining <= 0) { | |
290 | is_error = 1; | |
291 | break; | |
292 | } | |
293 | ||
294 | res = SSL_connect(c->ssl); | |
295 | ||
296 | ssl_connected = ((res == 1) && SSL_is_init_finished(c->ssl)); | |
297 | ||
298 | if (!ssl_connected) | |
299 | { | |
300 | FD_ZERO(&in); FD_SET((unsigned)sd, &in); | |
301 | FD_ZERO(&out); FD_SET((unsigned)sd, &out); | |
302 | FD_ZERO(&err); FD_SET((unsigned)sd, &err); | |
303 | /* the documentation does not suggest that both error types occur at the same time so | |
304 | * the retry logic will consume all the outstanding events | |
305 | * we do not actually use oob data, but if it is sent, it is treated as an error all the | |
306 | * same | |
307 | */ | |
308 | errcode = SSL_get_error(c->ssl, res); | |
309 | switch (errcode) | |
310 | { | |
311 | case SSL_ERROR_NONE: | |
312 | /* no error, we should have a connection, check again */ | |
313 | break; | |
314 | ||
315 | case SSL_ERROR_WANT_READ: | |
316 | /* no error, just wait for more data */ | |
317 | tv.tv_sec = remaining; tv.tv_usec = 0; | |
318 | /* posix-2001 says the function will modify the appropriate descriptors */ | |
319 | if (select(sd+1, &in, NULL, &err, &tv) < 0 || | |
320 | FD_ISSET((unsigned)sd, &err) | |
321 | ) | |
322 | is_error = 1; | |
323 | break; | |
324 | case SSL_ERROR_WANT_WRITE: | |
325 | /* no error, just wait for more data */ | |
326 | tv.tv_sec = remaining; tv.tv_usec = 0; | |
327 | if (select(sd+1, NULL, &out, &err, &tv) < 0 || | |
328 | FD_ISSET((unsigned)sd, &err) | |
329 | ) | |
330 | is_error = 1; | |
331 | break; | |
332 | case SSL_ERROR_ZERO_RETURN: /* peer closed the connection */ | |
333 | case SSL_ERROR_SSL: /* error in SSL handshake */ | |
334 | default: | |
335 | is_error = 1; | |
336 | } | |
337 | } | |
338 | } | |
339 | ||
340 | if (is_error) { | |
341 | return 0; | |
342 | } | |
343 | ||
344 | ||
345 | #ifdef WIN32 | |
346 | u_long param = 0; | |
347 | ioctlsocket(sd, FIONBIO, ¶m); // make the socket blocking again | |
348 | #else | |
349 | fcntl(sd, F_SETFL, 0); /* make the socket blocking again */ | |
350 | #endif | |
351 | ||
352 | /* verify that server certificate is authentic */ | |
353 | server_cert = SSL_get_peer_certificate(c->ssl); | |
354 | if (!server_cert) { | |
355 | return 0; | |
356 | } | |
357 | if (c->validate_cert && c->validate_cert(0, server_cert) != 0) | |
358 | { | |
359 | X509_free(server_cert); | |
360 | return 0; | |
361 | } | |
362 | X509_free(server_cert); | |
363 | ||
364 | return 1; | |
365 | } | |
366 | ||
367 | /* This function should be called on list of socket file descriptors (sd) to determine | |
368 | * if any have opened successfully. If so, it will return which one (index into | |
369 | * the array). Otherwise it returns -1 if none have successfully opened. | |
370 | * This function will block for a maximum of 3 seconds. | |
371 | * As this function calls FinishConnection(), you shouldn't need to do anything special | |
372 | * after it returns success - the socket is set up and ready for use. | |
373 | */ | |
374 | static int CheckConnection(TCLinkCon *c, int *sd, int num_sd) | |
375 | { | |
376 | fd_set wr_set, err_set; | |
377 | struct timeval tv; | |
378 | int max_sd = -1, i; | |
379 | ||
380 | tv.tv_sec = 3; /* wait 3 seconds for soc->mething to happen */ | |
381 | tv.tv_usec = 0; | |
382 | ||
383 | /* build the fd_sets used for select() */ | |
384 | FD_ZERO(&wr_set); | |
385 | FD_ZERO(&err_set); | |
386 | for (i = 0; i < num_sd; i++) | |
387 | { | |
388 | if (sd[i] < 0) continue; | |
389 | FD_SET(sd[i], &wr_set); | |
390 | FD_SET(sd[i], &err_set); | |
391 | if (sd[i] > max_sd) | |
392 | max_sd = sd[i]; | |
393 | } | |
394 | ||
395 | /* run the select and see what we have waiting for us */ | |
396 | if (select(max_sd + 1, NULL, &wr_set, &err_set, &tv) < 1) | |
397 | return -1; /* I hope this never happens */ | |
398 | ||
399 | for (i = 0; i < num_sd; i++) | |
400 | if (sd[i] >= 0) | |
401 | { | |
402 | if (FD_ISSET(sd[i], &err_set)) | |
403 | { | |
404 | /* error - close the socket and mark it defunct */ | |
405 | close(sd[i]); | |
406 | sd[i] = -1; | |
407 | } | |
408 | else if (FD_ISSET(sd[i], &wr_set)) | |
409 | { | |
410 | /* socket has opened! try to negotiate SSL */ | |
411 | if (FinishConnection(c, sd[i])) { | |
412 | /* socket is ready to go, so return success */ | |
413 | c->sd = sd[i]; | |
414 | return i; | |
415 | } | |
416 | else { | |
417 | /* SSL handshake had errors, close the socket and mark it defunct */ | |
418 | close(sd[i]); | |
419 | sd[i] = -1; | |
420 | } | |
421 | } | |
422 | } | |
423 | ||
424 | /* if we get here, nothing much interesting happened during those 3 seconds */ | |
425 | return -1; | |
426 | } | |
427 | ||
428 | void do_SSL_randomize() | |
429 | { | |
430 | enum { RAND_VALS = 32 }; | |
431 | int randbuf[RAND_VALS]; | |
432 | char fname[512]; | |
433 | int use_rand_file; | |
434 | time_t t; | |
435 | int i, c; | |
436 | ||
437 | /* if they have a /dev/urandom we can skip this function */ | |
438 | if (RAND_status() != 0) | |
439 | return; | |
440 | ||
441 | t = time(0); | |
442 | RAND_seed((char *)&t, sizeof(time_t)); | |
443 | ||
444 | /* have they specified a random file with RANDFILE environment variable? */ | |
445 | use_rand_file = RAND_file_name(fname, sizeof(fname)) ? 1 : 0; | |
446 | if (use_rand_file) | |
447 | RAND_load_file(fname, 4096); | |
448 | ||
449 | /* stuff it with packets of random numbers until it is satisfied */ | |
450 | for (i = 0; i < 256 && RAND_status() == 0; i++) | |
451 | { | |
452 | for (c = 0; c < RAND_VALS; c++) | |
453 | randbuf[c] = rand(); | |
454 | RAND_seed((char *)randbuf, sizeof(int) * RAND_VALS); | |
455 | } | |
456 | } | |
457 | ||
458 | /* Open a connection to one of the TrustCommerce gateway servers. */ | |
459 | static int Connect(TCLinkCon *c, int host_hash) | |
460 | { | |
461 | struct hostent default_he; | |
462 | char *addr_list[3]; int addr[2]; | |
463 | struct hostent *he; | |
464 | unsigned int **gw; | |
465 | ||
466 | enum { MAX_HOSTS = 32 }; | |
467 | time_t last_connect[MAX_HOSTS]; | |
468 | int sd[MAX_HOSTS]; | |
469 | int num_sd = 0; | |
470 | int host; | |
471 | ||
472 | int i, j, sort, sort_val; | |
473 | ||
474 | c->sd = -1; | |
475 | c->is_error = 0; | |
476 | ||
477 | srand(time(0)); | |
478 | ||
479 | /* These are used as BACKUP ONLY if the DNS if offline. */ | |
480 | addr[0] = inet_addr("207.38.46.42"); | |
481 | addr[1] = inet_addr("208.42.227.151"); | |
482 | addr_list[0] = (char *)&addr[0]; | |
483 | addr_list[1] = (char *)&addr[1]; | |
484 | addr_list[2] = 0; | |
485 | default_he.h_addr_list = addr_list; | |
486 | ||
487 | /* determine IP addresses of gateway */ | |
488 | if (!c->ip) | |
489 | { | |
490 | he = gethostbyname(tclink_host); | |
491 | if (he) | |
492 | c->dns = 1; | |
493 | else { | |
494 | /* fall back to hardcoded IPs in an emergency */ | |
495 | c->dns = 0; | |
496 | he = &default_he; | |
497 | } | |
498 | ||
499 | for (c->num_ips = 0; he->h_addr_list[c->num_ips]; c->num_ips++) | |
500 | ; | |
501 | ||
502 | c->ip = (int *)malloc(c->num_ips * sizeof(int)); | |
503 | gw = (int unsigned **)he->h_addr_list; | |
504 | ||
505 | /* sort the IP address list before storing it */ | |
506 | for (i = 0; i < c->num_ips; i++) | |
507 | { | |
508 | sort = 0; sort_val = *gw[0]; | |
509 | for (j = 1; j < c->num_ips; j++) | |
510 | if (*gw[j] > sort_val) | |
511 | { | |
512 | sort = j; | |
513 | sort_val = *gw[j]; | |
514 | } | |
515 | ||
516 | c->ip[i] = sort_val; | |
517 | *gw[sort] = 0; | |
518 | } | |
519 | } | |
520 | ||
521 | /* do some SSL setup */ | |
522 | if (!c->meth) | |
523 | { | |
524 | do_SSL_randomize(); /* handle systems without /dev/urandom */ | |
525 | SSLeay_add_ssl_algorithms(); | |
526 | c->meth = SSLv23_client_method(); | |
527 | c->ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; // Disable all known SSL versions | |
528 | } | |
529 | ||
530 | if (!c->ctx) | |
531 | { | |
532 | int val; | |
533 | ||
534 | c->ctx = SSL_CTX_new(c->meth); | |
535 | if (!c->ctx) return 0; | |
536 | /* set options */ | |
537 | if (c->ctx_options) | |
538 | SSL_CTX_set_options(c->ctx, c->ctx_options); | |
539 | ||
540 | if (!c->trusted_ca_pem) | |
541 | { | |
542 | int is_file = is_regular_file(TCLINK_CA_PATH); | |
543 | val = SSL_CTX_load_verify_locations(c->ctx, is_file?TCLINK_CA_PATH:NULL, is_file?NULL:TCLINK_CA_PATH); | |
544 | } | |
545 | else | |
546 | { | |
547 | extern int SSL_CTX_load_verify_locations_mem(SSL_CTX*, const char *); | |
548 | val = SSL_CTX_load_verify_locations_mem(c->ctx, c->trusted_ca_pem); | |
549 | } | |
550 | ||
551 | if (!val) return 0; // failed to populate cert store | |
552 | ||
553 | /* turn on certificate chain validation */ | |
554 | SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL); | |
555 | } | |
556 | ||
557 | if (!c->ssl) | |
558 | { | |
559 | c->ssl = SSL_new(c->ctx); | |
560 | if (!c->ssl) | |
561 | { | |
562 | SSL_CTX_free(c->ctx); | |
563 | return 0; | |
564 | } | |
565 | } | |
566 | ||
567 | /* This loop works as follows: | |
568 | * Grab the first host. Try to open a connection to it. If there was an | |
569 | * error (host down or unreachable) go to the next one. If nothing has happened | |
570 | * after 3 seconds, open a second socket (the first one is still open!) and try | |
571 | * with the next fail-over host. Continue to do this for a maximum of MAX_HOSTS | |
572 | * sockets, or until our TIMEOUT value runs out. We also keep track of how recently | |
573 | * we tried to connect to a given host, so that we avoid saturating the machines | |
574 | * in a heavy-load situation (which could be caused by anything from heavy internet | |
575 | * lag between the local host and the TrustCommerce servers, to heavy load on the | |
576 | * servers themselves due to half a million people trying to run credit card | |
577 | * transactions in the same half second - unlikely, but certainly possible.) | |
578 | */ | |
579 | c->start_time = time(0); | |
580 | c->pass = 1; | |
581 | memset(last_connect, 0, MAX_HOSTS * sizeof(time_t)); | |
582 | host = host_hash % c->num_ips; | |
583 | ||
584 | for ( ; time(0) < (c->start_time + TIMEOUT); c->pass++) | |
585 | { | |
586 | /* retry the first host at least once */ | |
587 | if (c->pass > 2) host += 1; | |
588 | if (host >= c->num_ips) host = 0; | |
589 | ||
590 | /* only connect if we haven't tried this host before, or it's been a little | |
591 | * while (note random modifier to help stagger network traffic) */ | |
592 | if (last_connect[host] == 0 || | |
593 | (time(0) - last_connect[host]) >= number(TIMEOUT / 4, TIMEOUT)) | |
594 | { | |
595 | if (num_sd < MAX_HOSTS) | |
596 | { | |
597 | /* fire up a new connection to this host */ | |
598 | if (c->pass != 1) | |
599 | last_connect[host] = time(0); | |
600 | ||
601 | sd[num_sd] = BeginConnection(c, c->ip[host]); | |
602 | if (sd[num_sd] >= 0) | |
603 | num_sd++; | |
604 | } | |
605 | } | |
606 | ||
607 | /* scan all current sockets and see if we've made a successful connection | |
608 | * somewhere. note that this also includes SSL and all that sort of fun, | |
609 | * so once it returns success, we're all done. */ | |
610 | if (num_sd > 0) | |
611 | { | |
612 | if (CheckConnection(c, sd, num_sd) >= 0) | |
613 | { | |
614 | /* Success: close all other file handles and return */ | |
615 | for (i = 0; i < num_sd; i++) | |
616 | if (sd[i] >= 0 && sd[i] != c->sd) | |
617 | close(sd[i]); | |
618 | ||
619 | return 1; | |
620 | } | |
621 | } | |
622 | ||
623 | usleep(1000); // sleep for 1 millisecond | |
624 | } | |
625 | ||
626 | return 0; | |
627 | } | |
628 | ||
629 | /* Send a chunk of data through a connection previously opened with Connect(). */ | |
630 | static int Send(TCLinkCon *c, const char *string) | |
631 | { | |
632 | if (SSL_write(c->ssl, string, strlen(string)) < 0) | |
633 | return 0; | |
634 | ||
635 | return 1; | |
636 | } | |
637 | ||
638 | /* Peel a line off the current input. Note that this DOESN'T necessarily wait for all | |
639 | * input to come in, only up to a "\n". -1 is returned for a network error, otherwise | |
640 | * it returns the length of the line read. If there is not a complete line pending | |
641 | * for read this will block until there is, or an error occurs. | |
642 | */ | |
643 | static int ReadLine(TCLinkCon *c, char *buffer, char *destbuf) | |
644 | { | |
645 | struct timeval tv; | |
646 | fd_set read; | |
647 | fd_set error; | |
648 | int sel; | |
649 | ||
650 | while (1) /* we wait for a line to come in or an error to occur */ | |
651 | { | |
652 | char *eol = strchr(buffer, '\n'); | |
653 | if (eol != NULL) | |
654 | { | |
655 | /* peel off the line and return it */ | |
656 | *eol++ = 0; | |
657 | safe_copy(destbuf, buffer, TC_LINE_MAX); | |
658 | memmove(buffer, eol, strlen(eol)+1); | |
659 | return strlen(destbuf); | |
660 | } | |
661 | else | |
662 | { | |
663 | if (c->is_error == 1) | |
664 | return -1; | |
665 | ||
666 | /* do socket work to grab the most recent chunk of incoming data */ | |
667 | FD_ZERO(&read); FD_SET(c->sd, &read); | |
668 | FD_ZERO(&error); FD_SET(c->sd, &error); | |
669 | tv.tv_sec = TIMEOUT; | |
670 | tv.tv_usec = 0; | |
671 | ||
672 | sel = select(c->sd+1, &read, NULL, &error, &tv); | |
673 | if (sel < 1) | |
674 | c->is_error = 1; | |
675 | else if (FD_ISSET(c->sd, &error)) | |
676 | c->is_error = 1; | |
677 | else if (FD_ISSET(c->sd, &read)) | |
678 | { | |
679 | int buffer_end = strlen(buffer); | |
680 | int size = SSL_read(c->ssl, buffer + buffer_end, TC_BUFF_MAX-1 - buffer_end); | |
681 | if (size == 0) | |
682 | { | |
683 | int error_type = SSL_get_error(c->ssl, size); | |
684 | switch (error_type) | |
685 | { | |
686 | /* this would never happen in practice */ | |
687 | case SSL_ERROR_NONE: | |
688 | /* this wouldn't happen either because the ssl transport is blocking */ | |
689 | case SSL_ERROR_WANT_READ: | |
690 | case SSL_ERROR_WANT_WRITE: | |
691 | buffer[buffer_end] = 0; | |
692 | break; | |
693 | ||
694 | /* these others should not really happen but if they do, we bail */ | |
695 | /* we would never get any more data and it looks like the callee is expecting something */ | |
696 | case SSL_ERROR_ZERO_RETURN: | |
697 | case SSL_ERROR_WANT_CONNECT: | |
698 | case SSL_ERROR_WANT_ACCEPT: | |
699 | case SSL_ERROR_SYSCALL: | |
700 | case SSL_ERROR_WANT_X509_LOOKUP: | |
701 | case SSL_ERROR_SSL: | |
702 | default: | |
703 | c->is_error = 1; | |
704 | break; | |
705 | } | |
706 | } | |
707 | else if (size < 0) | |
708 | c->is_error = 1; | |
709 | else | |
710 | buffer[buffer_end + size] = 0; | |
711 | } | |
712 | } | |
713 | } | |
714 | } | |
715 | ||
716 | /* Closes a connection opened with Connect() and frees memory associated with it. | |
717 | * You ONLY need to Close() connections which opened successfully; those that don't | |
718 | * clean up after themselves before Connect() returns. | |
719 | */ | |
720 | static int Close(TCLinkCon *c) | |
721 | { | |
722 | if (c->ssl) | |
723 | { | |
724 | /* The full shutdown presented here is more for completeness than necessity; at this point in the | |
725 | * application, we have already received the end trailer (or bust) which is generally accompanied by | |
726 | * a close notify message. If the software chooses to respond to the close notify (per TLS specification) | |
727 | * this would result in at least reading the incoming close notify and issuing our own. Because this entails | |
728 | * an additional round trip that is not needed (the transaction is done after the accompanying END), there | |
729 | * does not appear to be a benefit to it at all. By default though, this configuration is enabled and | |
730 | * can be disabled by the integrator for performance reasons. | |
731 | */ | |
732 | if (c->full_ssl_close) | |
733 | { | |
734 | int status = SSL_shutdown(c->ssl); | |
735 | if (status == 0) status = SSL_shutdown(c->ssl); | |
736 | } | |
737 | else | |
738 | SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); | |
739 | } | |
740 | ||
741 | if (c->sd >= 0) { | |
742 | close(c->sd); | |
743 | c->sd = -1; | |
744 | } | |
745 | ||
746 | if (c->trusted_ca_pem) { | |
747 | free(c->trusted_ca_pem); | |
748 | c->trusted_ca_pem = NULL; | |
749 | } | |
750 | ||
751 | return 1; | |
752 | } | |
753 | ||
754 | static void stuff_string(char *buf, int *len, int size, const char *add) | |
755 | { | |
756 | int newlen = strlen(add); | |
757 | if ((*len + newlen) >= size) | |
758 | newlen = size - *len - 1; | |
759 | if (newlen < 1) return; | |
760 | strncpy(buf + *len, add, newlen); | |
761 | *len += newlen; | |
762 | buf[*len] = 0; | |
763 | } | |
764 | ||
765 | /********************************************** | |
766 | * API functions exported to the user client. * | |
767 | **********************************************/ | |
768 | ||
769 | TCLinkHandle TCLinkCreate() | |
770 | { | |
771 | extern int TCLinkDefaultValidate(int, void *); | |
772 | ||
773 | TCLinkCon *c = (TCLinkCon *)malloc(sizeof(TCLinkCon)); | |
774 | ||
775 | c->ip = NULL; | |
776 | c->num_ips = 0; | |
777 | c->sd = -1; | |
778 | ||
779 | c->meth = NULL; | |
780 | c->ctx = NULL; | |
781 | c->ssl = NULL; | |
782 | ||
783 | c->send_param_list = NULL; | |
784 | c->send_param_tail = NULL; | |
785 | c->recv_param_list = NULL; | |
786 | ||
787 | c->is_error = 0; | |
788 | c->pass = 0; | |
789 | c->start_time = 0; | |
790 | c->dns = -1; | |
791 | ||
792 | c->trusted_ca_pem = NULL; | |
793 | c->validate_cert = TCLinkDefaultValidate; | |
794 | c->full_ssl_close = 1; | |
795 | ||
796 | return (TCLinkHandle)c; | |
797 | } | |
798 | ||
799 | int TCLinkSetFullClose(TCLinkHandle handle, int full_ssl_close) | |
800 | { | |
801 | TCLinkCon *c = (TCLinkCon *)handle; | |
802 | int swap = c->full_ssl_close; | |
803 | c->full_ssl_close = full_ssl_close ? 1 : 0; | |
804 | return swap; | |
805 | } | |
806 | ||
807 | void TCLinkSetTrustedCABundle(TCLinkHandle handle, const char *str, int len) | |
808 | { | |
809 | TCLinkCon *c = (TCLinkCon *)handle; | |
810 | ||
811 | if (c->trusted_ca_pem) | |
812 | free(c->trusted_ca_pem); | |
813 | ||
814 | if (str == NULL) | |
815 | { | |
816 | c->trusted_ca_pem = NULL; | |
817 | return; | |
818 | } | |
819 | ||
820 | c->trusted_ca_pem = malloc(len+1); | |
821 | strncpy(c->trusted_ca_pem,str,len); | |
822 | c->trusted_ca_pem[len] = 0; | |
823 | } | |
824 | ||
825 | void TCLinkSetValidateCallback(TCLinkHandle handle, int (*validate_cert)(int, void *)) | |
826 | { | |
827 | TCLinkCon *c = (TCLinkCon *)handle; | |
828 | if (validate_cert == NULL) | |
829 | { | |
830 | extern int TCLinkDefaultValidate(int, void *); | |
831 | c->validate_cert = TCLinkDefaultValidate; | |
832 | } | |
833 | else | |
834 | c->validate_cert = validate_cert; | |
835 | } | |
836 | ||
837 | void TCLinkPushParam(TCLinkHandle handle, const char *name, const char *value) | |
838 | { | |
839 | param *p; | |
840 | char *ch; | |
841 | ||
842 | TCLinkCon *c = (TCLinkCon *)handle; | |
843 | ||
844 | if (name && value) | |
845 | { | |
846 | p = (param *)malloc(sizeof(param)); | |
847 | p->name = strdup(name); | |
848 | p->value = strdup(value); | |
849 | p->next = NULL; | |
850 | if (c->send_param_tail) | |
851 | c->send_param_tail->next = p; | |
852 | else | |
853 | c->send_param_list = p; | |
854 | c->send_param_tail = p; | |
855 | ||
856 | /* remove newlines and equals signs from the parameter name */ | |
857 | for (ch = p->name; *ch; ch++) | |
858 | if (*ch == '=' || *ch == '\n') *ch = ' '; | |
859 | ||
860 | /* remove newlines from the value */ | |
861 | for (ch = p->value; *ch; ch++) | |
862 | if (*ch == '\n') *ch = ' '; | |
863 | } | |
864 | } | |
865 | ||
866 | void TCLinkSend(TCLinkHandle handle) | |
867 | { | |
868 | param *p, *next; | |
869 | char buf[TC_BUFF_MAX], destbuf[TC_LINE_MAX]; | |
870 | char buf2[1024]; | |
871 | int host_hash = 1; | |
872 | int retval = 0; | |
873 | ||
874 | TCLinkCon *c = (TCLinkCon *)handle; | |
875 | ||
876 | ClearRecvList(c); | |
877 | ||
878 | /* build most of the string we will send to the processor */ | |
879 | sprintf(buf, "BEGIN\nversion=%s\n", tclink_version); | |
880 | ||
881 | for (p = c->send_param_list; p; p = next) | |
882 | { | |
883 | next = p->next; | |
884 | SAFE_COPY(buf2, p->name); | |
885 | SAFE_APPEND(buf2, "="); | |
886 | SAFE_APPEND(buf2, p->value); | |
887 | SAFE_APPEND(buf2, "\n"); | |
888 | SAFE_APPEND(buf, buf2); | |
889 | if (!strcasecmp(p->name, "custid")) { | |
890 | host_hash = atoi(p->value); | |
891 | host_hash = (host_hash / 100) + (host_hash % 100); | |
892 | } | |
893 | free(p->name); | |
894 | free(p->value); | |
895 | free(p); | |
896 | } | |
897 | ||
898 | c->send_param_list = c->send_param_tail = NULL; | |
899 | ||
900 | /* try to make the connection */ | |
901 | if (!Connect(c, host_hash)) | |
902 | { | |
903 | Close(c); /* clean up any memory Connect() may have left lying around */ | |
904 | AddRecvParam(c, "status", "error"); | |
905 | AddRecvParam(c, "errortype", "cantconnect"); | |
906 | return; | |
907 | } | |
908 | ||
909 | /* append some data about the connection */ | |
910 | sprintf(buf+strlen(buf), "pass=%d\ntime=%ld\n", c->pass, time(0) - c->start_time); | |
911 | if (c->dns != 1) SAFE_APPEND(buf, "dns=n\n"); | |
912 | SAFE_APPEND(buf, "END\n"); | |
913 | ||
914 | /* send the data */ | |
915 | if (Send(c, buf)) | |
916 | { | |
917 | int state = 0; | |
918 | buf[0] = destbuf[0] = 0; /* recycle buf */ | |
919 | c->is_error = 0; | |
920 | while (1) | |
921 | { | |
922 | int len = ReadLine(c, buf, destbuf); | |
923 | if (len == 0) continue; | |
924 | if (len < 0) break; | |
925 | if (strcasecmp(destbuf, "BEGIN") == 0) | |
926 | { | |
927 | if (state != 0) | |
928 | { state = -1; break; } | |
929 | state = 1; | |
930 | } | |
931 | else if (strcasecmp(destbuf, "END") == 0) | |
932 | { | |
933 | state = (state != 1) ? -1 : 2; | |
934 | break; | |
935 | } | |
936 | else | |
937 | { | |
938 | if (state != 1 || !AddRecvString(c, destbuf)) | |
939 | { state = -1; break; } | |
940 | } | |
941 | } | |
942 | if (state == 2) | |
943 | retval = 1; | |
944 | } | |
945 | ||
946 | Close(c); | |
947 | ||
948 | if (!retval) | |
949 | { | |
950 | ClearRecvList(c); | |
951 | AddRecvParam(c, "status", "error"); | |
952 | AddRecvParam(c, "errortype", "linkfailure"); | |
953 | } | |
954 | } | |
955 | ||
956 | char *TCLinkGetResponse(TCLinkHandle handle, const char *name, char *value) | |
957 | { | |
958 | param *p; | |
959 | TCLinkCon *c = (TCLinkCon *)handle; | |
960 | ||
961 | for (p = c->recv_param_list; p; p = p->next) | |
962 | if (strcasecmp(name, p->name) == 0) | |
963 | { | |
964 | safe_copy(value, p->value, PARAM_MAX_LEN); | |
965 | return value; | |
966 | } | |
967 | ||
968 | return NULL; | |
969 | } | |
970 | ||
971 | char *TCLinkGetEntireResponse(TCLinkHandle handle, char *buf, int size) | |
972 | { | |
973 | param *p; | |
974 | int len = 0; | |
975 | TCLinkCon *c = (TCLinkCon *)handle; | |
976 | ||
977 | for (p = c->recv_param_list; p; p = p->next) { | |
978 | stuff_string(buf, &len, size, p->name); | |
979 | stuff_string(buf, &len, size, "="); | |
980 | stuff_string(buf, &len, size, p->value); | |
981 | stuff_string(buf, &len, size, "\n"); | |
982 | } | |
983 | ||
984 | return buf; | |
985 | } | |
986 | ||
987 | void TCLinkDestroy(TCLinkHandle handle) | |
988 | { | |
989 | TCLinkCon *c = (TCLinkCon *)handle; | |
990 | if (!c) return; | |
991 | ||
992 | ClearSendList(c); | |
993 | ClearRecvList(c); | |
994 | Close(c); | |
995 | ||
996 | if (c->ip) | |
997 | free(c->ip); | |
998 | ||
999 | if (c->ssl) { | |
1000 | SSL_free(c->ssl); | |
1001 | c->ssl = NULL; | |
1002 | } | |
1003 | ||
1004 | if (c->ctx) { | |
1005 | SSL_CTX_free(c->ctx); | |
1006 | c->ctx = NULL; | |
1007 | } | |
1008 | ||
1009 | free(c); | |
1010 | } | |
1011 | ||
1012 | char *TCLinkGetVersion(char *buf) | |
1013 | { | |
1014 | strcpy(buf, tclink_version); | |
1015 | return buf; | |
1016 | } | |
1017 |