1/*
2 * Cheesy HTTP 1.1 client used for testing HTTP Digest (RFC 2617)
3 * variant of DIGEST-MD5 plugin
4 *
5 * XXX  This client REQUIRES a persistent connection and
6 * WILL NOT accept a body in any HTTP response
7 */
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13#include <pwd.h>
14
15#include <sys/types.h>
16#include <sys/socket.h>
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#include <netdb.h>
20
21#include <sasl/sasl.h>
22
23#define SUCCESS 0
24#define ERROR   1
25
26#define BUFFER_SIZE 8192
27
28#define DIGEST_AUTH_HEADER "\r\nWWW-Authenticate: Digest "
29#define DIGEST_OK_HEADER "\r\nAuthentication-Info: "
30
31void interact(sasl_interact_t *ilist)
32{
33    while (ilist->id != SASL_CB_LIST_END) {
34	switch (ilist->id) {
35
36	case SASL_CB_AUTHNAME:			/* auth as current uid */
37	    ilist->result = strdup(getpwuid(getuid())->pw_name);
38	    break;
39
40	case SASL_CB_PASS:			/* prompt for password */
41	    printf("%s: ", ilist->prompt);
42	    ilist->result = strdup(getpass(""));
43	    break;
44	}
45	ilist->len = strlen(ilist->result);
46
47	ilist++;
48    }
49}
50
51int main(int argc __attribute__((unused)), char *argv[])
52{
53    const char *hostname = "localhost";
54    int port = 80;
55
56    int sd, rc, status;
57    struct sockaddr_in localAddr, servAddr;
58    struct hostent *h;
59
60    const char *sasl_impl, *sasl_ver;
61    sasl_conn_t *saslconn;
62    sasl_interact_t *interactions = NULL;
63    sasl_security_properties_t secprops = { 0,		/* min SSF ("auth") */
64					    1,		/* max SSF ("auth-int") */
65					    0,		/* don't need maxbuf */
66					    0,		/* security flags */
67					    NULL,
68					    NULL };
69    sasl_http_request_t httpreq = { "HEAD",		/* Method */
70				    "/",		/* URI */
71				    (u_char *) "",	/* Empty body */
72				    0,			/* Zero-length body */
73				    0 };		/* Persistent cxn */
74    sasl_callback_t callbacks[] = {
75	{ SASL_CB_AUTHNAME, NULL, NULL },
76	{ SASL_CB_PASS, NULL, NULL },
77	{ SASL_CB_LIST_END, NULL, NULL }
78    };
79
80    const char *response = NULL;
81    unsigned int resplen = 0;
82    char buffer[BUFFER_SIZE+1], *request, *challenge, *p;
83    int i, code;
84
85    printf("\n-- Hostname = %s , Port = %d , URI = %s\n",
86	   hostname, port, httpreq.uri);
87
88    h = gethostbyname(hostname);
89    if(h == NULL) {
90	printf("unknown host: %s \n ", hostname);
91	exit(ERROR);
92    }
93
94    servAddr.sin_family = h->h_addrtype;
95    memcpy((char *) &servAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);
96    servAddr.sin_port = htons(port);
97
98    /* create socket */
99    printf("-- Create socket...    ");
100    sd = socket(AF_INET, SOCK_STREAM, 0);
101    if (sd < 0) {
102	perror("cannot open socket ");
103	exit(ERROR);
104    }
105
106    /*  bind port number */
107    printf("Bind port number...  ");
108
109    localAddr.sin_family = AF_INET;
110    localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
111    localAddr.sin_port = htons(0);
112
113    rc = bind(sd, (struct sockaddr *) &localAddr, sizeof(localAddr));
114    if (rc < 0) {
115	printf("%s: cannot bind port TCP %u\n",argv[0],port);
116	perror("error ");
117	exit(ERROR);
118    }
119
120    /* connect to server */
121    printf("Connect to server...\n");
122    rc = connect(sd, (struct sockaddr *) &servAddr, sizeof(servAddr));
123    if (rc < 0) {
124	perror("cannot connect ");
125	exit(ERROR);
126    }
127
128    /* get SASL version info */
129    sasl_version_info(&sasl_impl, &sasl_ver, NULL, NULL, NULL, NULL);
130
131    /* initialize client-side of SASL */
132    status = sasl_client_init(callbacks);
133
134    /* request the URI twice, so we test both initial auth and reauth */
135    for (i = 0; i < 2; i++) {
136	/* initialize a client exchange
137	 *
138	 * SASL_NEED_HTTP:    forces HTTP Digest mode (REQUIRED)
139	 * SASL_SUCCESS_DATA: HTTP supports success data in
140	 *                    Authentication-Info header (REQUIRED)
141	 */
142	status = sasl_client_new("http", hostname, NULL, NULL, NULL,
143			     SASL_NEED_HTTP | SASL_SUCCESS_DATA, &saslconn);
144	if (status != SASL_OK) {
145	    perror("sasl_client_new() failed ");
146	    exit(ERROR);
147	}
148
149	/* Set security peoperties as specified above */
150	sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops);
151
152	/* Set HTTP request as specified above (REQUIRED) */
153	sasl_setprop(saslconn, SASL_HTTP_REQUEST, &httpreq);
154
155	do {
156	    /* start the Digest exchange */
157	    status = sasl_client_start(saslconn, "DIGEST-MD5", &interactions,
158				       &response, &resplen, NULL);
159	    if (status == SASL_INTERACT) interact(interactions);
160	} while (status == SASL_INTERACT);
161
162	if ((status != SASL_OK) && (status != SASL_CONTINUE)) {
163	    perror("sasl_client_start() failed ");
164	    exit(ERROR);
165	}
166
167	do {
168	    /* send request (with Auth data if we have it ) */
169	    request = buffer;
170	    request += sprintf(request, "%s %s HTTP/1.1\r\n",
171			       httpreq.method, httpreq.uri);
172	    request += sprintf(request, "Host: %s\r\n", hostname);
173	    request += sprintf(request, "User-Agent: HTTP Digest Test Client"
174			       " (%s/%s)\r\n", sasl_impl, sasl_ver);
175	    request += sprintf(request, "Connection: keep-alive\r\n");
176	    request += sprintf(request, "Keep-Alive: 300\r\n");
177	    if (response) {
178		request += sprintf(request, "Authorization: Digest %s\r\n",
179				   response);
180	    }
181	    request += sprintf(request, "\r\n");
182	    request = buffer;
183
184	    printf("\n-- Send HTTP request:\n\n%s", request);
185	    rc = write(sd, request, strlen(request));
186	    if (rc < 0) {
187		perror("cannot send data ");
188		close(sd);
189		exit(ERROR);
190	    }
191
192	    /* display response */
193	    printf("-- Received response:\n\tfrom server: http://%s%s, IP = %s,\n\n",
194		   hostname, httpreq.uri, inet_ntoa(servAddr.sin_addr));
195	    rc = read(sd, buffer, BUFFER_SIZE);
196	    if (rc <= 0) {
197		perror("cannot read data ");
198		close(sd);
199		exit(ERROR);
200	    }
201	    buffer[rc] = '\0';
202
203	    printf("%s", buffer);
204
205	    /* get response code */
206	    sscanf(buffer, "HTTP/1.1 %d ", &code);
207
208	    if (code == 401) {
209		/* find Digest challenge */
210		challenge = strstr(buffer, DIGEST_AUTH_HEADER);
211		if (!challenge) break;
212		challenge += strlen(DIGEST_AUTH_HEADER);
213		p = strchr(challenge, '\r');
214		*p = '\0';
215
216		do {
217		    /* do the next step in the exchange */
218		    status = sasl_client_step(saslconn,
219					      challenge, strlen(challenge),
220					      &interactions,
221					      &response, &resplen);
222		    if (status == SASL_INTERACT) interact(interactions);
223		} while (status == SASL_INTERACT);
224
225		if ((status != SASL_OK) && (status != SASL_CONTINUE)) {
226		    perror("sasl_client_step failed ");
227		    exit(ERROR);
228		}
229	    }
230	} while (code == 401);
231
232	if ((code == 200) && (status == SASL_CONTINUE)) {
233	    /* find Digest response */
234	    challenge = strstr(buffer, DIGEST_OK_HEADER);
235	    if (challenge) {
236		challenge += strlen(DIGEST_OK_HEADER);
237		p = strchr(challenge, '\r');
238		*p = '\0';
239
240		/* do the final step in the exchange (server auth) */
241		status = sasl_client_step(saslconn,
242					  challenge, strlen(challenge),
243					  &interactions, &response, &resplen);
244	    }
245	}
246
247	sasl_dispose(&saslconn);
248    }
249
250    sasl_client_done();
251
252    close(sd);
253    return SUCCESS;
254}
255