1203368Slulf/*-
2203368Slulf * Copyright (c) 2003-2007, Petar Zhivkov Petrov <pesho.petrov@gmail.com>
3203368Slulf * All rights reserved.
4203368Slulf *
5203368Slulf * Redistribution and use in source and binary forms, with or without
6203368Slulf * modification, are permitted provided that the following conditions
7203368Slulf * are met:
8203368Slulf * 1. Redistributions of source code must retain the above copyright
9203368Slulf *    notice, this list of conditions and the following disclaimer.
10203368Slulf * 2. Redistributions in binary form must reproduce the above copyright
11203368Slulf *    notice, this list of conditions and the following disclaimer in the
12203368Slulf *    documentation and/or other materials provided with the distribution.
13203368Slulf *
14203368Slulf * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15203368Slulf * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16203368Slulf * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17203368Slulf * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18203368Slulf * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19203368Slulf * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20203368Slulf * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21203368Slulf * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22203368Slulf * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23203368Slulf * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24203368Slulf * SUCH DAMAGE.
25203368Slulf *
26203368Slulf * $FreeBSD$
27203368Slulf */
28203368Slulf
29203368Slulf#include <sys/param.h>
30203368Slulf#include <sys/socket.h>
31203368Slulf#include <sys/time.h>
32203368Slulf#include <sys/types.h>
33203368Slulf
34203368Slulf#include <arpa/inet.h>
35203368Slulf#include <netinet/in.h>
36203368Slulf
37203368Slulf#include <ctype.h>
38203368Slulf#include <stdio.h>
39203368Slulf#include <stdlib.h>
40203368Slulf#include <string.h>
41203368Slulf#include <unistd.h>
42203368Slulf
43203368Slulf#include "auth.h"
44203368Slulf#include "config.h"
45203368Slulf#include "misc.h"
46203368Slulf#include "proto.h"
47203368Slulf#include "stream.h"
48203368Slulf
49203368Slulf#define MD5_BYTES			16
50203368Slulf
51203368Slulf/* This should be at least 2 * MD5_BYTES + 6 (length of "$md5$" + 1) */
52203368Slulf#define MD5_CHARS_MAX		(2*(MD5_BYTES)+6)
53203368Slulf
54203368Slulfstruct srvrecord {
55203368Slulf	char server[MAXHOSTNAMELEN];
56203368Slulf	char client[256];
57203368Slulf	char password[256];
58203368Slulf};
59203368Slulf
60203368Slulfstatic int		auth_domd5auth(struct config *);
61203368Slulfstatic int		auth_lookuprecord(char *, struct srvrecord *);
62203368Slulfstatic int		auth_parsetoken(char **, char *, int);
63203368Slulfstatic void		auth_makesecret(struct srvrecord *, char *);
64203368Slulfstatic void		auth_makeresponse(char *, char *, char *);
65203368Slulfstatic void		auth_readablesum(unsigned char *, char *);
66203368Slulfstatic void		auth_makechallenge(struct config *, char *);
67203368Slulfstatic int		auth_checkresponse(char *, char *, char *);
68203368Slulf
69203368Slulfint auth_login(struct config *config)
70203368Slulf{
71203368Slulf	struct stream *s;
72203368Slulf	char hostbuf[MAXHOSTNAMELEN];
73203368Slulf	char *login, *host;
74203368Slulf	int error;
75203368Slulf
76203368Slulf	s = config->server;
77203368Slulf	error = gethostname(hostbuf, sizeof(hostbuf));
78203368Slulf	hostbuf[sizeof(hostbuf) - 1] = '\0';
79203368Slulf	if (error)
80203368Slulf		host = NULL;
81203368Slulf	else
82203368Slulf		host = hostbuf;
83203368Slulf	login = getlogin();
84203368Slulf	proto_printf(s, "USER %s %s\n", login != NULL ? login : "?",
85203368Slulf	    host != NULL ? host : "?");
86203368Slulf	stream_flush(s);
87203368Slulf	error = auth_domd5auth(config);
88203368Slulf	return (error);
89203368Slulf}
90203368Slulf
91203368Slulfstatic int
92203368Slulfauth_domd5auth(struct config *config)
93203368Slulf{
94203368Slulf	struct stream *s;
95203368Slulf	char *line, *cmd, *challenge, *realm, *client, *srvresponse, *msg;
96203368Slulf	char shrdsecret[MD5_CHARS_MAX], response[MD5_CHARS_MAX];
97203368Slulf	char clichallenge[MD5_CHARS_MAX];
98203368Slulf	struct srvrecord auth;
99203368Slulf	int error;
100203368Slulf
101203368Slulf	lprintf(2, "MD5 authentication started\n");
102203368Slulf	s = config->server;
103203368Slulf	line = stream_getln(s, NULL);
104203368Slulf	cmd = proto_get_ascii(&line);
105203368Slulf	realm = proto_get_ascii(&line);
106203368Slulf	challenge = proto_get_ascii(&line);
107203368Slulf	if (challenge == NULL ||
108203368Slulf	    line != NULL ||
109203368Slulf	    (strcmp(cmd, "AUTHMD5") != 0)) {
110203368Slulf		lprintf(-1, "Invalid server reply to USER\n");
111203368Slulf		return (STATUS_FAILURE);
112203368Slulf	}
113203368Slulf
114203368Slulf	client = NULL;
115203368Slulf	response[0] = clichallenge[0] = '.';
116203368Slulf	response[1] = clichallenge[1] = 0;
117203368Slulf	if (config->reqauth || (strcmp(challenge, ".") != 0)) {
118203368Slulf		if (strcmp(realm, ".") == 0) {
119203368Slulf			lprintf(-1, "Authentication required, but not enabled on server\n");
120203368Slulf			return (STATUS_FAILURE);
121203368Slulf		}
122203368Slulf		error = auth_lookuprecord(realm, &auth);
123203368Slulf		if (error != STATUS_SUCCESS)
124203368Slulf			return (error);
125203368Slulf		client = auth.client;
126203368Slulf		auth_makesecret(&auth, shrdsecret);
127203368Slulf	}
128203368Slulf
129203368Slulf	if (strcmp(challenge, ".") != 0)
130203368Slulf		auth_makeresponse(challenge, shrdsecret, response);
131203368Slulf	if (config->reqauth)
132203368Slulf		auth_makechallenge(config, clichallenge);
133203368Slulf	proto_printf(s, "AUTHMD5 %s %s %s\n",
134203368Slulf		client == NULL ? "." : client, response, clichallenge);
135203368Slulf	stream_flush(s);
136203368Slulf	line = stream_getln(s, NULL);
137203368Slulf	cmd = proto_get_ascii(&line);
138203368Slulf	if (cmd == NULL || line == NULL)
139203368Slulf		goto bad;
140203368Slulf	if (strcmp(cmd, "OK") == 0) {
141203368Slulf		srvresponse = proto_get_ascii(&line);
142203368Slulf		if (srvresponse == NULL)
143203368Slulf			goto bad;
144203368Slulf		if (config->reqauth &&
145203368Slulf		    !auth_checkresponse(srvresponse, clichallenge, shrdsecret)) {
146203368Slulf			lprintf(-1, "Server failed to authenticate itself to client\n");
147203368Slulf			return (STATUS_FAILURE);
148203368Slulf		}
149204664Slulf		lprintf(2, "MD5 authentication successful\n");
150203368Slulf		return (STATUS_SUCCESS);
151203368Slulf	}
152203368Slulf	if (strcmp(cmd, "!") == 0) {
153203368Slulf		msg = proto_get_rest(&line);
154203368Slulf		if (msg == NULL)
155203368Slulf			goto bad;
156203368Slulf		lprintf(-1, "Server error: %s\n", msg);
157203368Slulf		return (STATUS_FAILURE);
158203368Slulf	}
159203368Slulfbad:
160203368Slulf	lprintf(-1, "Invalid server reply to AUTHMD5\n");
161203368Slulf	return (STATUS_FAILURE);
162203368Slulf}
163203368Slulf
164203368Slulfstatic int
165203368Slulfauth_lookuprecord(char *server, struct srvrecord *auth)
166203368Slulf{
167203368Slulf	char *home, *line, authfile[FILENAME_MAX];
168203368Slulf	struct stream *s;
169203368Slulf	int linenum = 0, error;
170203368Slulf
171203368Slulf	home = getenv("HOME");
172203368Slulf	if (home == NULL) {
173203368Slulf		lprintf(-1, "Environment variable \"HOME\" is not set\n");
174203368Slulf		return (STATUS_FAILURE);
175203368Slulf	}
176203368Slulf	snprintf(authfile, sizeof(authfile), "%s/%s", home, AUTHFILE);
177203368Slulf	s = stream_open_file(authfile, O_RDONLY);
178203368Slulf	if (s == NULL) {
179203368Slulf		lprintf(-1, "Could not open file %s\n", authfile);
180203368Slulf		return (STATUS_FAILURE);
181203368Slulf	}
182203368Slulf
183203368Slulf	while ((line = stream_getln(s, NULL)) != NULL) {
184203368Slulf		linenum++;
185203368Slulf		if (line[0] == '#' || line[0] == '\0')
186203368Slulf			continue;
187203368Slulf		error = auth_parsetoken(&line, auth->server,
188203368Slulf		    sizeof(auth->server));
189203368Slulf		if (error != STATUS_SUCCESS) {
190225536Sbrueffer			lprintf(-1, "%s:%d Missing client name\n", authfile, linenum);
191203368Slulf			goto close;
192203368Slulf		}
193203368Slulf		/* Skip the rest of this line, it isn't what we are looking for. */
194225535Sbrueffer		if (strcasecmp(auth->server, server) != 0)
195203368Slulf			continue;
196203368Slulf		error = auth_parsetoken(&line, auth->client,
197203368Slulf		    sizeof(auth->client));
198203368Slulf		if (error != STATUS_SUCCESS) {
199225536Sbrueffer			lprintf(-1, "%s:%d Missing password\n", authfile, linenum);
200203368Slulf			goto close;
201203368Slulf		}
202203368Slulf		error = auth_parsetoken(&line, auth->password,
203203368Slulf		    sizeof(auth->password));
204203368Slulf		if (error != STATUS_SUCCESS) {
205225536Sbrueffer			lprintf(-1, "%s:%d Missing comment\n", authfile, linenum);
206203368Slulf			goto close;
207203368Slulf		}
208203368Slulf		stream_close(s);
209203368Slulf		lprintf(2, "Found authentication record for server \"%s\"\n",
210203368Slulf		    server);
211203368Slulf		return (STATUS_SUCCESS);
212203368Slulf	}
213203368Slulf	lprintf(-1, "Unknown server \"%s\". Fix your %s\n", server , authfile);
214203368Slulf	memset(auth->password, 0, sizeof(auth->password));
215203368Slulfclose:
216203368Slulf	stream_close(s);
217203368Slulf	return (STATUS_FAILURE);
218203368Slulf}
219203368Slulf
220203368Slulfstatic int
221203368Slulfauth_parsetoken(char **line, char *buf, int len)
222203368Slulf{
223203368Slulf	char *colon;
224203368Slulf
225203368Slulf	colon = strchr(*line, ':');
226203368Slulf	if (colon == NULL)
227203368Slulf		return (STATUS_FAILURE);
228203368Slulf	*colon = 0;
229203368Slulf	buf[len - 1] = 0;
230203368Slulf	strncpy(buf, *line, len - 1);
231203368Slulf	*line = colon + 1;
232203368Slulf	return (STATUS_SUCCESS);
233203368Slulf}
234203368Slulf
235203368Slulfstatic void
236203368Slulfauth_makesecret(struct srvrecord *auth, char *secret)
237203368Slulf{
238203368Slulf	char *s, ch;
239203368Slulf	const char *md5salt = "$md5$";
240203368Slulf	unsigned char md5sum[MD5_BYTES];
241203368Slulf	MD5_CTX md5;
242203368Slulf
243203368Slulf	MD5_Init(&md5);
244203368Slulf	for (s = auth->client; *s != 0; ++s) {
245203368Slulf		ch = tolower(*s);
246203368Slulf		MD5_Update(&md5, &ch, 1);
247203368Slulf	}
248203368Slulf	MD5_Update(&md5, ":", 1);
249203368Slulf	for (s = auth->server; *s != 0; ++s) {
250203368Slulf		ch = tolower(*s);
251203368Slulf		MD5_Update(&md5, &ch, 1);
252203368Slulf	}
253203368Slulf	MD5_Update(&md5, ":", 1);
254203368Slulf	MD5_Update(&md5, auth->password, strlen(auth->password));
255203368Slulf	MD5_Final(md5sum, &md5);
256228625Sdim	memset(secret, 0, MD5_CHARS_MAX);
257203368Slulf	strcpy(secret, md5salt);
258203368Slulf	auth_readablesum(md5sum, secret + strlen(md5salt));
259203368Slulf}
260203368Slulf
261203368Slulfstatic void
262203368Slulfauth_makeresponse(char *challenge, char *sharedsecret, char *response)
263203368Slulf{
264203368Slulf	MD5_CTX md5;
265203368Slulf	unsigned char md5sum[MD5_BYTES];
266203368Slulf
267203368Slulf	MD5_Init(&md5);
268203368Slulf	MD5_Update(&md5, sharedsecret, strlen(sharedsecret));
269203368Slulf	MD5_Update(&md5, ":", 1);
270203368Slulf	MD5_Update(&md5, challenge, strlen(challenge));
271203368Slulf	MD5_Final(md5sum, &md5);
272203368Slulf	auth_readablesum(md5sum, response);
273203368Slulf}
274203368Slulf
275203368Slulf/*
276203368Slulf * Generates a challenge string which is an MD5 sum
277203368Slulf * of a fairly random string. The purpose is to decrease
278203368Slulf * the possibility of generating the same challenge
279203368Slulf * string (even by different clients) more then once
280203368Slulf * for the same server.
281203368Slulf */
282203368Slulfstatic void
283203368Slulfauth_makechallenge(struct config *config, char *challenge)
284203368Slulf{
285203368Slulf	MD5_CTX md5;
286203368Slulf	unsigned char md5sum[MD5_BYTES];
287203368Slulf	char buf[128];
288203368Slulf	struct timeval tv;
289203368Slulf	struct sockaddr_in laddr;
290203368Slulf	pid_t pid, ppid;
291203368Slulf	int error, addrlen;
292203368Slulf
293203368Slulf	gettimeofday(&tv, NULL);
294203368Slulf	pid = getpid();
295203368Slulf	ppid = getppid();
296232320Scognet	srandom(tv.tv_usec ^ tv.tv_sec ^ pid);
297203368Slulf	addrlen = sizeof(laddr);
298203368Slulf	error = getsockname(config->socket, (struct sockaddr *)&laddr, &addrlen);
299203368Slulf	if (error < 0) {
300203368Slulf		memset(&laddr, 0, sizeof(laddr));
301203368Slulf	}
302203368Slulf	gettimeofday(&tv, NULL);
303203368Slulf	MD5_Init(&md5);
304228667Sdim	snprintf(buf, sizeof(buf), "%s:%jd:%ld:%ld:%d:%d",
305228667Sdim	    inet_ntoa(laddr.sin_addr), (intmax_t)tv.tv_sec, tv.tv_usec,
306228625Sdim	    random(), pid, ppid);
307203368Slulf	MD5_Update(&md5, buf, strlen(buf));
308203368Slulf	MD5_Final(md5sum, &md5);
309203368Slulf	auth_readablesum(md5sum, challenge);
310203368Slulf}
311203368Slulf
312203368Slulfstatic int
313203368Slulfauth_checkresponse(char *response, char *challenge, char *secret)
314203368Slulf{
315203368Slulf	char correctresponse[MD5_CHARS_MAX];
316203368Slulf
317203368Slulf	auth_makeresponse(challenge, secret, correctresponse);
318203368Slulf	return (strcmp(response, correctresponse) == 0);
319203368Slulf}
320203368Slulf
321203368Slulfstatic void
322203368Slulfauth_readablesum(unsigned char *md5sum, char *readable)
323203368Slulf{
324203368Slulf	unsigned int i;
325203368Slulf	char *s = readable;
326203368Slulf
327203368Slulf	for (i = 0; i < MD5_BYTES; ++i, s+=2) {
328203368Slulf		sprintf(s, "%.2x", md5sum[i]);
329203368Slulf	}
330203368Slulf}
331203368Slulf
332