xref: /trueos/usr.bin/csup/auth.c (revision 94d2b7f64912987093f1a98573737a32e4e5d8d1)
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$
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 <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include "auth.h"
44 #include "config.h"
45 #include "misc.h"
46 #include "proto.h"
47 #include "stream.h"
48 
49 #define MD5_BYTES			16
50 
51 /* This should be at least 2 * MD5_BYTES + 6 (length of "$md5$" + 1) */
52 #define MD5_CHARS_MAX		(2*(MD5_BYTES)+6)
53 
54 struct srvrecord {
55 	char server[MAXHOSTNAMELEN];
56 	char client[256];
57 	char password[256];
58 };
59 
60 static int		auth_domd5auth(struct config *);
61 static int		auth_lookuprecord(char *, struct srvrecord *);
62 static int		auth_parsetoken(char **, char *, int);
63 static void		auth_makesecret(struct srvrecord *, char *);
64 static void		auth_makeresponse(char *, char *, char *);
65 static void		auth_readablesum(unsigned char *, char *);
66 static void		auth_makechallenge(struct config *, char *);
67 static int		auth_checkresponse(char *, char *, char *);
68 
auth_login(struct config * config)69 int auth_login(struct config *config)
70 {
71 	struct stream *s;
72 	char hostbuf[MAXHOSTNAMELEN];
73 	char *login, *host;
74 	int error;
75 
76 	s = config->server;
77 	error = gethostname(hostbuf, sizeof(hostbuf));
78 	hostbuf[sizeof(hostbuf) - 1] = '\0';
79 	if (error)
80 		host = NULL;
81 	else
82 		host = hostbuf;
83 	login = getlogin();
84 	proto_printf(s, "USER %s %s\n", login != NULL ? login : "?",
85 	    host != NULL ? host : "?");
86 	stream_flush(s);
87 	error = auth_domd5auth(config);
88 	return (error);
89 }
90 
91 static int
auth_domd5auth(struct config * config)92 auth_domd5auth(struct config *config)
93 {
94 	struct stream *s;
95 	char *line, *cmd, *challenge, *realm, *client, *srvresponse, *msg;
96 	char shrdsecret[MD5_CHARS_MAX], response[MD5_CHARS_MAX];
97 	char clichallenge[MD5_CHARS_MAX];
98 	struct srvrecord auth;
99 	int error;
100 
101 	lprintf(2, "MD5 authentication started\n");
102 	s = config->server;
103 	line = stream_getln(s, NULL);
104 	cmd = proto_get_ascii(&line);
105 	realm = proto_get_ascii(&line);
106 	challenge = proto_get_ascii(&line);
107 	if (challenge == NULL ||
108 	    line != NULL ||
109 	    (strcmp(cmd, "AUTHMD5") != 0)) {
110 		lprintf(-1, "Invalid server reply to USER\n");
111 		return (STATUS_FAILURE);
112 	}
113 
114 	client = NULL;
115 	response[0] = clichallenge[0] = '.';
116 	response[1] = clichallenge[1] = 0;
117 	if (config->reqauth || (strcmp(challenge, ".") != 0)) {
118 		if (strcmp(realm, ".") == 0) {
119 			lprintf(-1, "Authentication required, but not enabled on server\n");
120 			return (STATUS_FAILURE);
121 		}
122 		error = auth_lookuprecord(realm, &auth);
123 		if (error != STATUS_SUCCESS)
124 			return (error);
125 		client = auth.client;
126 		auth_makesecret(&auth, shrdsecret);
127 	}
128 
129 	if (strcmp(challenge, ".") != 0)
130 		auth_makeresponse(challenge, shrdsecret, response);
131 	if (config->reqauth)
132 		auth_makechallenge(config, clichallenge);
133 	proto_printf(s, "AUTHMD5 %s %s %s\n",
134 		client == NULL ? "." : client, response, clichallenge);
135 	stream_flush(s);
136 	line = stream_getln(s, NULL);
137 	cmd = proto_get_ascii(&line);
138 	if (cmd == NULL || line == NULL)
139 		goto bad;
140 	if (strcmp(cmd, "OK") == 0) {
141 		srvresponse = proto_get_ascii(&line);
142 		if (srvresponse == NULL)
143 			goto bad;
144 		if (config->reqauth &&
145 		    !auth_checkresponse(srvresponse, clichallenge, shrdsecret)) {
146 			lprintf(-1, "Server failed to authenticate itself to client\n");
147 			return (STATUS_FAILURE);
148 		}
149 		lprintf(2, "MD5 authentication successful\n");
150 		return (STATUS_SUCCESS);
151 	}
152 	if (strcmp(cmd, "!") == 0) {
153 		msg = proto_get_rest(&line);
154 		if (msg == NULL)
155 			goto bad;
156 		lprintf(-1, "Server error: %s\n", msg);
157 		return (STATUS_FAILURE);
158 	}
159 bad:
160 	lprintf(-1, "Invalid server reply to AUTHMD5\n");
161 	return (STATUS_FAILURE);
162 }
163 
164 static int
auth_lookuprecord(char * server,struct srvrecord * auth)165 auth_lookuprecord(char *server, struct srvrecord *auth)
166 {
167 	char *home, *line, authfile[FILENAME_MAX];
168 	struct stream *s;
169 	int linenum = 0, error;
170 
171 	home = getenv("HOME");
172 	if (home == NULL) {
173 		lprintf(-1, "Environment variable \"HOME\" is not set\n");
174 		return (STATUS_FAILURE);
175 	}
176 	snprintf(authfile, sizeof(authfile), "%s/%s", home, AUTHFILE);
177 	s = stream_open_file(authfile, O_RDONLY);
178 	if (s == NULL) {
179 		lprintf(-1, "Could not open file %s\n", authfile);
180 		return (STATUS_FAILURE);
181 	}
182 
183 	while ((line = stream_getln(s, NULL)) != NULL) {
184 		linenum++;
185 		if (line[0] == '#' || line[0] == '\0')
186 			continue;
187 		error = auth_parsetoken(&line, auth->server,
188 		    sizeof(auth->server));
189 		if (error != STATUS_SUCCESS) {
190 			lprintf(-1, "%s:%d Missing client name\n", authfile, linenum);
191 			goto close;
192 		}
193 		/* Skip the rest of this line, it isn't what we are looking for. */
194 		if (strcasecmp(auth->server, server) != 0)
195 			continue;
196 		error = auth_parsetoken(&line, auth->client,
197 		    sizeof(auth->client));
198 		if (error != STATUS_SUCCESS) {
199 			lprintf(-1, "%s:%d Missing password\n", authfile, linenum);
200 			goto close;
201 		}
202 		error = auth_parsetoken(&line, auth->password,
203 		    sizeof(auth->password));
204 		if (error != STATUS_SUCCESS) {
205 			lprintf(-1, "%s:%d Missing comment\n", authfile, linenum);
206 			goto close;
207 		}
208 		stream_close(s);
209 		lprintf(2, "Found authentication record for server \"%s\"\n",
210 		    server);
211 		return (STATUS_SUCCESS);
212 	}
213 	lprintf(-1, "Unknown server \"%s\". Fix your %s\n", server , authfile);
214 	memset(auth->password, 0, sizeof(auth->password));
215 close:
216 	stream_close(s);
217 	return (STATUS_FAILURE);
218 }
219 
220 static int
auth_parsetoken(char ** line,char * buf,int len)221 auth_parsetoken(char **line, char *buf, int len)
222 {
223 	char *colon;
224 
225 	colon = strchr(*line, ':');
226 	if (colon == NULL)
227 		return (STATUS_FAILURE);
228 	*colon = 0;
229 	buf[len - 1] = 0;
230 	strncpy(buf, *line, len - 1);
231 	*line = colon + 1;
232 	return (STATUS_SUCCESS);
233 }
234 
235 static void
auth_makesecret(struct srvrecord * auth,char * secret)236 auth_makesecret(struct srvrecord *auth, char *secret)
237 {
238 	char *s, ch;
239 	const char *md5salt = "$md5$";
240 	unsigned char md5sum[MD5_BYTES];
241 	MD5_CTX md5;
242 
243 	MD5_Init(&md5);
244 	for (s = auth->client; *s != 0; ++s) {
245 		ch = tolower(*s);
246 		MD5_Update(&md5, &ch, 1);
247 	}
248 	MD5_Update(&md5, ":", 1);
249 	for (s = auth->server; *s != 0; ++s) {
250 		ch = tolower(*s);
251 		MD5_Update(&md5, &ch, 1);
252 	}
253 	MD5_Update(&md5, ":", 1);
254 	MD5_Update(&md5, auth->password, strlen(auth->password));
255 	MD5_Final(md5sum, &md5);
256 	memset(secret, 0, MD5_CHARS_MAX);
257 	strcpy(secret, md5salt);
258 	auth_readablesum(md5sum, secret + strlen(md5salt));
259 }
260 
261 static void
auth_makeresponse(char * challenge,char * sharedsecret,char * response)262 auth_makeresponse(char *challenge, char *sharedsecret, char *response)
263 {
264 	MD5_CTX md5;
265 	unsigned char md5sum[MD5_BYTES];
266 
267 	MD5_Init(&md5);
268 	MD5_Update(&md5, sharedsecret, strlen(sharedsecret));
269 	MD5_Update(&md5, ":", 1);
270 	MD5_Update(&md5, challenge, strlen(challenge));
271 	MD5_Final(md5sum, &md5);
272 	auth_readablesum(md5sum, response);
273 }
274 
275 /*
276  * Generates a challenge string which is an MD5 sum
277  * of a fairly random string. The purpose is to decrease
278  * the possibility of generating the same challenge
279  * string (even by different clients) more then once
280  * for the same server.
281  */
282 static void
auth_makechallenge(struct config * config,char * challenge)283 auth_makechallenge(struct config *config, char *challenge)
284 {
285 	MD5_CTX md5;
286 	unsigned char md5sum[MD5_BYTES];
287 	char buf[128];
288 	struct timeval tv;
289 	struct sockaddr_in laddr;
290 	pid_t pid, ppid;
291 	int error, addrlen;
292 
293 	gettimeofday(&tv, NULL);
294 	pid = getpid();
295 	ppid = getppid();
296 	srandom(tv.tv_usec ^ tv.tv_sec ^ pid);
297 	addrlen = sizeof(laddr);
298 	error = getsockname(config->socket, (struct sockaddr *)&laddr, &addrlen);
299 	if (error < 0) {
300 		memset(&laddr, 0, sizeof(laddr));
301 	}
302 	gettimeofday(&tv, NULL);
303 	MD5_Init(&md5);
304 	snprintf(buf, sizeof(buf), "%s:%jd:%ld:%ld:%d:%d",
305 	    inet_ntoa(laddr.sin_addr), (intmax_t)tv.tv_sec, tv.tv_usec,
306 	    random(), pid, ppid);
307 	MD5_Update(&md5, buf, strlen(buf));
308 	MD5_Final(md5sum, &md5);
309 	auth_readablesum(md5sum, challenge);
310 }
311 
312 static int
auth_checkresponse(char * response,char * challenge,char * secret)313 auth_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 
321 static void
auth_readablesum(unsigned char * md5sum,char * readable)322 auth_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