]> CyberLeo.Net >> Repos - FreeBSD/releng/9.0.git/blob - usr.bin/csup/auth.c
Copy stable/9 to releng/9.0 as part of the FreeBSD 9.0-RELEASE release
[FreeBSD/releng/9.0.git] / usr.bin / csup / auth.c
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 <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
55 struct srvrecord {
56         char server[MAXHOSTNAMELEN];
57         char client[256];
58         char password[256];
59 };
60
61 static int              auth_domd5auth(struct config *);
62 static int              auth_lookuprecord(char *, struct srvrecord *);
63 static int              auth_parsetoken(char **, char *, int);
64 static void             auth_makesecret(struct srvrecord *, char *);
65 static void             auth_makeresponse(char *, char *, char *);
66 static void             auth_readablesum(unsigned char *, char *);
67 static void             auth_makechallenge(struct config *, char *);
68 static int              auth_checkresponse(char *, char *, char *);
69
70 int 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
92 static int
93 auth_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 successful\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         }
160 bad:
161         lprintf(-1, "Invalid server reply to AUTHMD5\n");
162         return (STATUS_FAILURE);
163 }
164
165 static int
166 auth_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 Missing 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 (strcasecmp(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 Missing 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 Missing 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));
216 close:
217         stream_close(s);
218         return (STATUS_FAILURE);
219 }
220
221 static int
222 auth_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
236 static void
237 auth_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
262 static void
263 auth_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  */
283 static void
284 auth_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
312 static int
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
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