]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - tests/client.c
tests/client.c: Clarify usage message.
[tlsproxy/tlsproxy.git] / tests / client.c
1 /*
2  * Simple GnuTLS client used for testing.
3  *
4  * Copyright (C) 2011-2014  Simon Ruderich
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21
22 #include <arpa/inet.h>
23 #include <assert.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <netdb.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/socket.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33
34 #include <gnutls/gnutls.h>
35 #include <gnutls/x509.h>
36
37
38 #define MAX_REQUEST_LINE 4096
39
40 static int fdopen_read_write(int socket, FILE **read_fd, FILE **write_fd);
41 static int connect_to_host(const char *hostname, const char *port);
42 static int read_http_request(FILE *client_fd, char *request, size_t length);
43
44 #if 0
45 static void log_function_gnutls(int level, const char *string) {
46     (void)level;
47     fprintf(stderr, "    => %s", string);
48 }
49 #endif
50
51 int main (int argc, char *argv[]) {
52     int result, response;
53     unsigned int status;
54     char buffer[MAX_REQUEST_LINE];
55     int server;
56     FILE *fd_read, *fd_write;
57
58     gnutls_session_t session;
59     gnutls_certificate_credentials_t xcred;
60
61     gnutls_x509_crt_t cert;
62     const gnutls_datum_t *cert_list;
63     unsigned int cert_list_size;
64
65     if (argc != 5 && argc != 6) {
66         fprintf(stderr,
67                 "Usage: %s <ca-file> <hostname> <port> <hostname-verify> "
68                           "[<digest-authentication>]\n"
69                 "\n"
70                 "tlsproxy must be running on port 4711, "
71                 "<port> is the <hostname> port.\n",
72                 argv[0]);
73         return EXIT_FAILURE;
74     }
75
76     gnutls_global_init();
77     gnutls_certificate_allocate_credentials(&xcred);
78
79 #if 0
80     gnutls_global_set_log_level(10);
81     gnutls_global_set_log_function(log_function_gnutls);
82 #endif
83
84     gnutls_certificate_set_x509_trust_file(xcred,
85                                            argv[1], GNUTLS_X509_FMT_PEM);
86
87     gnutls_init(&session, GNUTLS_CLIENT);
88     gnutls_priority_set_direct(session, "NORMAL", NULL);
89     gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
90
91     server = connect_to_host("localhost", "4711");
92     if (server == -1) {
93         return EXIT_FAILURE;
94     }
95     if (fdopen_read_write(server, &fd_read, &fd_write) != 0) {
96         return EXIT_FAILURE;
97     }
98
99     /* Talk to tlsproxy. */
100     fprintf(fd_write, "CONNECT %s:%s HTTP/1.0\r\n", argv[2], argv[3]);
101     if (argc == 6) {
102         fprintf(fd_write, "Proxy-Authorization: Basic %s\r\n", argv[5]);
103     }
104     fprintf(fd_write, "\r\n");
105     fflush(fd_write);
106     if (read_http_request(fd_read, buffer, sizeof(buffer)) != 0) {
107         fprintf(stderr, "invalid proxy response\n");
108         return EXIT_FAILURE;
109     }
110
111     printf("response: %s\n", buffer);
112
113     if (sscanf(buffer, "HTTP/1.0 %d", &response) != 1) {
114         fprintf(stderr, "invalid proxy response: %s\n", buffer);
115         return EXIT_FAILURE;
116     }
117
118     if (response != 200) {
119         fprintf(stderr, "proxy failure\n");
120         return EXIT_FAILURE;
121     }
122
123 #ifdef HAVE_GNUTLS_TRANSPORT_SET_INT2
124     /* gnutls_transport_set_int() is a macro. */
125     gnutls_transport_set_int(session, server);
126 #else
127     gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)server);
128 #endif
129
130     result = gnutls_handshake(session);
131     if (result != GNUTLS_E_SUCCESS) {
132         fprintf(stderr, "gnutls_handshake() failed\n");
133         gnutls_perror(result);
134         return EXIT_FAILURE;
135     }
136
137     /* Verify the proxy certificate. */
138     result = gnutls_certificate_verify_peers2(session, &status);
139     if (result < 0) {
140         fprintf(stderr, "gnutls_certificate_verify_peers2() failed\n");
141         gnutls_perror(result);
142         return EXIT_FAILURE;
143     }
144
145     if (status & GNUTLS_CERT_INVALID) {
146         fprintf(stderr, "certificate invalid\n");
147     }
148
149     /* Get proxy certificate. */
150     if ((result = gnutls_x509_crt_init(&cert)) < 0) {
151         fprintf(stderr, "gnutls_x509_crt_init() failed");
152         gnutls_perror(result);
153         return EXIT_FAILURE;
154     }
155
156     cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
157     if (cert_list == NULL) {
158         fprintf(stderr, "gnutls_certificate_get_peers() failed");
159         return EXIT_FAILURE;
160     }
161
162     if ((result = gnutls_x509_crt_import(cert, &cert_list[0],
163                                          GNUTLS_X509_FMT_DER)) < 0) {
164         fprintf(stderr, "gnutls_x509_crt_import() failed");
165         gnutls_perror(result);
166         return EXIT_FAILURE;
167     }
168
169     /* Check hostname. */
170     if (!gnutls_x509_crt_check_hostname(cert, argv[4])) {
171         fprintf(stderr, "hostname didn't match '%s'\n", argv[4]);
172         return EXIT_FAILURE;
173     }
174
175     gnutls_x509_crt_deinit(cert);
176
177     /* Send a bogus request to the server. Otherwise recent gnutls-serv won't
178      * terminate the connection when gnutls_bye() is used. */
179     gnutls_record_send(session, "GET / HTTP/1.0\r\n\r\n",
180                                 strlen("GET / HTTP/1.0\r\n\r\n"));
181
182     gnutls_bye(session, GNUTLS_SHUT_RDWR);
183     fclose(fd_read);
184     fclose(fd_write);
185
186     gnutls_deinit(session);
187     gnutls_certificate_free_credentials(xcred);
188     gnutls_global_deinit();
189
190     return EXIT_SUCCESS;
191 }
192
193
194 /* Copied from src/connection.c (and removed LOG_* stuff)! Don't modify. */
195
196 static int fdopen_read_write(int socket, FILE **read_fd, FILE **write_fd) {
197     *read_fd = fdopen(socket, "r");
198     if (*read_fd == NULL) {
199         perror("fdopen_read_write(): fdopen(\"r\") failed");
200         return -1;
201     }
202
203     *write_fd = fdopen(dup(socket), "w");
204     if (*write_fd == NULL) {
205         perror("fdopen_read_write(): fdopen(\"w\") failed");
206         fclose(*read_fd);
207         *read_fd = NULL; /* "tell" caller read_fd is already closed */
208         return -1;
209     }
210
211     return 0;
212 }
213
214 static int connect_to_host(const char *hostname, const char *port) {
215     struct addrinfo gai_hints;
216     struct addrinfo *gai_result;
217     int gai_return;
218
219     int server_socket;
220     struct addrinfo *server;
221
222     if (hostname == NULL || port == NULL) {
223         return -1;
224     }
225
226     /* Get IP of hostname server. */
227     memset(&gai_hints, 0, sizeof(gai_hints));
228     gai_hints.ai_family   = AF_UNSPEC;
229     gai_hints.ai_socktype = SOCK_STREAM;
230     gai_hints.ai_protocol = 0;
231     gai_hints.ai_flags    = AI_NUMERICSERV /* given port is numeric */
232 #ifdef AI_ADDRCONFIG
233                           | AI_ADDRCONFIG  /* supported by this computer */
234 #endif
235                           ;
236     gai_return = getaddrinfo(hostname, port, &gai_hints, &gai_result);
237     if (gai_return != 0) {
238         if (gai_return == EAI_SYSTEM) {
239             perror("connect_to_host(): getaddrinfo()");
240         } else {
241             fprintf(stderr, "connect_to_host(): getaddrinfo(): %s",
242                             gai_strerror(gai_return));
243         }
244         return -1;
245     }
246
247     /* Now try to connect to each server returned by getaddrinfo(), use the
248      * first successful connect. */
249     for (server = gai_result; server != NULL; server = server->ai_next) {
250         server_socket = socket(server->ai_family,
251                                server->ai_socktype,
252                                server->ai_protocol);
253         if (server_socket < 0) {
254             perror("connect_to_host(): socket(), trying next");
255             continue;
256         }
257
258         if (connect(server_socket, server->ai_addr, server->ai_addrlen) == 0) {
259             break;
260         }
261         perror("connect_to_host(): connect(), trying next");
262
263         close(server_socket);
264     }
265     /* Make sure we free the result from getaddrinfo(). */
266     freeaddrinfo(gai_result);
267
268     if (server == NULL) {
269         perror("connect_to_host(): no server found, abort");
270         return -1;
271     }
272
273     return server_socket;
274 }
275
276 static int read_http_request(FILE *client_fd, char *request, size_t length) {
277     char buffer[MAX_REQUEST_LINE];
278
279     assert(length <= INT_MAX);
280     if (fgets(request, (int)length, client_fd) == NULL) {
281         if (ferror(client_fd)) {
282             perror("read_http_request(): fgets()");
283             return -1;
284         }
285         /* EOF */
286         return -2;
287     }
288
289     while (fgets(buffer, sizeof(buffer), client_fd) != NULL) {
290         /* End of header. */
291         if (!strcmp(buffer, "\n") || !strcmp(buffer, "\r\n")) {
292             break;
293         }
294     }
295     if (ferror(client_fd)) {
296         perror("read_http_request(): fgets()");
297         return -1;
298     } else if (feof(client_fd)) {
299         return -2;
300     }
301
302     return 0;
303 }