auth.c revision 203368
1/*-
2 * Copyright (c) 2003-2007, Petar Zhivkov Petrov <pesho.petrov@gmail.com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: head/contrib/csup/auth.c 203368 2010-02-02 05:57:42Z lulf $
27 */
28
29#include <sys/param.h>
30#include <sys/socket.h>
31#include <sys/time.h>
32#include <sys/types.h>
33
34#include <arpa/inet.h>
35#include <netinet/in.h>
36
37#include <ctype.h>
38#include <openssl/md5.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43
44#include "auth.h"
45#include "config.h"
46#include "misc.h"
47#include "proto.h"
48#include "stream.h"
49
50#define MD5_BYTES			16
51
52/* This should be at least 2 * MD5_BYTES + 6 (length of "$md5$" + 1) */
53#define MD5_CHARS_MAX		(2*(MD5_BYTES)+6)
54
55struct srvrecord {
56	char server[MAXHOSTNAMELEN];
57	char client[256];
58	char password[256];
59};
60
61static int		auth_domd5auth(struct config *);
62static int		auth_lookuprecord(char *, struct srvrecord *);
63static int		auth_parsetoken(char **, char *, int);
64static void		auth_makesecret(struct srvrecord *, char *);
65static void		auth_makeresponse(char *, char *, char *);
66static void		auth_readablesum(unsigned char *, char *);
67static void		auth_makechallenge(struct config *, char *);
68static int		auth_checkresponse(char *, char *, char *);
69
70int auth_login(struct config *config)
71{
72	struct stream *s;
73	char hostbuf[MAXHOSTNAMELEN];
74	char *login, *host;
75	int error;
76
77	s = config->server;
78	error = gethostname(hostbuf, sizeof(hostbuf));
79	hostbuf[sizeof(hostbuf) - 1] = '\0';
80	if (error)
81		host = NULL;
82	else
83		host = hostbuf;
84	login = getlogin();
85	proto_printf(s, "USER %s %s\n", login != NULL ? login : "?",
86	    host != NULL ? host : "?");
87	stream_flush(s);
88	error = auth_domd5auth(config);
89	return (error);
90}
91
92static int
93auth_domd5auth(struct config *config)
94{
95	struct stream *s;
96	char *line, *cmd, *challenge, *realm, *client, *srvresponse, *msg;
97	char shrdsecret[MD5_CHARS_MAX], response[MD5_CHARS_MAX];
98	char clichallenge[MD5_CHARS_MAX];
99	struct srvrecord auth;
100	int error;
101
102	lprintf(2, "MD5 authentication started\n");
103	s = config->server;
104	line = stream_getln(s, NULL);
105	cmd = proto_get_ascii(&line);
106	realm = proto_get_ascii(&line);
107	challenge = proto_get_ascii(&line);
108	if (challenge == NULL ||
109	    line != NULL ||
110	    (strcmp(cmd, "AUTHMD5") != 0)) {
111		lprintf(-1, "Invalid server reply to USER\n");
112		return (STATUS_FAILURE);
113	}
114
115	client = NULL;
116	response[0] = clichallenge[0] = '.';
117	response[1] = clichallenge[1] = 0;
118	if (config->reqauth || (strcmp(challenge, ".") != 0)) {
119		if (strcmp(realm, ".") == 0) {
120			lprintf(-1, "Authentication required, but not enabled on server\n");
121			return (STATUS_FAILURE);
122		}
123		error = auth_lookuprecord(realm, &auth);
124		if (error != STATUS_SUCCESS)
125			return (error);
126		client = auth.client;
127		auth_makesecret(&auth, shrdsecret);
128	}
129
130	if (strcmp(challenge, ".") != 0)
131		auth_makeresponse(challenge, shrdsecret, response);
132	if (config->reqauth)
133		auth_makechallenge(config, clichallenge);
134	proto_printf(s, "AUTHMD5 %s %s %s\n",
135		client == NULL ? "." : client, response, clichallenge);
136	stream_flush(s);
137	line = stream_getln(s, NULL);
138	cmd = proto_get_ascii(&line);
139	if (cmd == NULL || line == NULL)
140		goto bad;
141	if (strcmp(cmd, "OK") == 0) {
142		srvresponse = proto_get_ascii(&line);
143		if (srvresponse == NULL)
144			goto bad;
145		if (config->reqauth &&
146		    !auth_checkresponse(srvresponse, clichallenge, shrdsecret)) {
147			lprintf(-1, "Server failed to authenticate itself to client\n");
148			return (STATUS_FAILURE);
149		}
150		lprintf(2, "MD5 authentication successfull\n");
151		return (STATUS_SUCCESS);
152	}
153	if (strcmp(cmd, "!") == 0) {
154		msg = proto_get_rest(&line);
155		if (msg == NULL)
156			goto bad;
157		lprintf(-1, "Server error: %s\n", msg);
158		return (STATUS_FAILURE);
159	}
160bad:
161	lprintf(-1, "Invalid server reply to AUTHMD5\n");
162	return (STATUS_FAILURE);
163}
164
165static int
166auth_lookuprecord(char *server, struct srvrecord *auth)
167{
168	char *home, *line, authfile[FILENAME_MAX];
169	struct stream *s;
170	int linenum = 0, error;
171
172	home = getenv("HOME");
173	if (home == NULL) {
174		lprintf(-1, "Environment variable \"HOME\" is not set\n");
175		return (STATUS_FAILURE);
176	}
177	snprintf(authfile, sizeof(authfile), "%s/%s", home, AUTHFILE);
178	s = stream_open_file(authfile, O_RDONLY);
179	if (s == NULL) {
180		lprintf(-1, "Could not open file %s\n", authfile);
181		return (STATUS_FAILURE);
182	}
183
184	while ((line = stream_getln(s, NULL)) != NULL) {
185		linenum++;
186		if (line[0] == '#' || line[0] == '\0')
187			continue;
188		error = auth_parsetoken(&line, auth->server,
189		    sizeof(auth->server));
190		if (error != STATUS_SUCCESS) {
191			lprintf(-1, "%s:%d Missng client name\n", authfile, linenum);
192			goto close;
193		}
194		/* Skip the rest of this line, it isn't what we are looking for. */
195		if (strcmp(auth->server, server) != 0)
196			continue;
197		error = auth_parsetoken(&line, auth->client,
198		    sizeof(auth->client));
199		if (error != STATUS_SUCCESS) {
200			lprintf(-1, "%s:%d Missng password\n", authfile, linenum);
201			goto close;
202		}
203		error = auth_parsetoken(&line, auth->password,
204		    sizeof(auth->password));
205		if (error != STATUS_SUCCESS) {
206			lprintf(-1, "%s:%d Missng comment\n", authfile, linenum);
207			goto close;
208		}
209		stream_close(s);
210		lprintf(2, "Found authentication record for server \"%s\"\n",
211		    server);
212		return (STATUS_SUCCESS);
213	}
214	lprintf(-1, "Unknown server \"%s\". Fix your %s\n", server , authfile);
215	memset(auth->password, 0, sizeof(auth->password));
216close:
217	stream_close(s);
218	return (STATUS_FAILURE);
219}
220
221static int
222auth_parsetoken(char **line, char *buf, int len)
223{
224	char *colon;
225
226	colon = strchr(*line, ':');
227	if (colon == NULL)
228		return (STATUS_FAILURE);
229	*colon = 0;
230	buf[len - 1] = 0;
231	strncpy(buf, *line, len - 1);
232	*line = colon + 1;
233	return (STATUS_SUCCESS);
234}
235
236static void
237auth_makesecret(struct srvrecord *auth, char *secret)
238{
239	char *s, ch;
240	const char *md5salt = "$md5$";
241	unsigned char md5sum[MD5_BYTES];
242	MD5_CTX md5;
243
244	MD5_Init(&md5);
245	for (s = auth->client; *s != 0; ++s) {
246		ch = tolower(*s);
247		MD5_Update(&md5, &ch, 1);
248	}
249	MD5_Update(&md5, ":", 1);
250	for (s = auth->server; *s != 0; ++s) {
251		ch = tolower(*s);
252		MD5_Update(&md5, &ch, 1);
253	}
254	MD5_Update(&md5, ":", 1);
255	MD5_Update(&md5, auth->password, strlen(auth->password));
256	MD5_Final(md5sum, &md5);
257	memset(secret, 0, sizeof(secret));
258	strcpy(secret, md5salt);
259	auth_readablesum(md5sum, secret + strlen(md5salt));
260}
261
262static void
263auth_makeresponse(char *challenge, char *sharedsecret, char *response)
264{
265	MD5_CTX md5;
266	unsigned char md5sum[MD5_BYTES];
267
268	MD5_Init(&md5);
269	MD5_Update(&md5, sharedsecret, strlen(sharedsecret));
270	MD5_Update(&md5, ":", 1);
271	MD5_Update(&md5, challenge, strlen(challenge));
272	MD5_Final(md5sum, &md5);
273	auth_readablesum(md5sum, response);
274}
275
276/*
277 * Generates a challenge string which is an MD5 sum
278 * of a fairly random string. The purpose is to decrease
279 * the possibility of generating the same challenge
280 * string (even by different clients) more then once
281 * for the same server.
282 */
283static void
284auth_makechallenge(struct config *config, char *challenge)
285{
286	MD5_CTX md5;
287	unsigned char md5sum[MD5_BYTES];
288	char buf[128];
289	struct timeval tv;
290	struct sockaddr_in laddr;
291	pid_t pid, ppid;
292	int error, addrlen;
293
294	gettimeofday(&tv, NULL);
295	pid = getpid();
296	ppid = getppid();
297	srand(tv.tv_usec ^ tv.tv_sec ^ pid);
298	addrlen = sizeof(laddr);
299	error = getsockname(config->socket, (struct sockaddr *)&laddr, &addrlen);
300	if (error < 0) {
301		memset(&laddr, 0, sizeof(laddr));
302	}
303	gettimeofday(&tv, NULL);
304	MD5_Init(&md5);
305	snprintf(buf, sizeof(buf), "%s:%ld:%ld:%ld:%d:%d",
306	    inet_ntoa(laddr.sin_addr), tv.tv_sec, tv.tv_usec, random(), pid, ppid);
307	MD5_Update(&md5, buf, strlen(buf));
308	MD5_Final(md5sum, &md5);
309	auth_readablesum(md5sum, challenge);
310}
311
312static int
313auth_checkresponse(char *response, char *challenge, char *secret)
314{
315	char correctresponse[MD5_CHARS_MAX];
316
317	auth_makeresponse(challenge, secret, correctresponse);
318	return (strcmp(response, correctresponse) == 0);
319}
320
321static void
322auth_readablesum(unsigned char *md5sum, char *readable)
323{
324	unsigned int i;
325	char *s = readable;
326
327	for (i = 0; i < MD5_BYTES; ++i, s+=2) {
328		sprintf(s, "%.2x", md5sum[i]);
329	}
330}
331
332