2 * Copyright (c) 2003-2007, Petar Zhivkov Petrov <pesho.petrov@gmail.com>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
29 #include <sys/param.h>
30 #include <sys/socket.h>
32 #include <sys/types.h>
34 #include <arpa/inet.h>
35 #include <netinet/in.h>
38 #include <openssl/md5.h>
52 /* This should be at least 2 * MD5_BYTES + 6 (length of "$md5$" + 1) */
53 #define MD5_CHARS_MAX (2*(MD5_BYTES)+6)
56 char server[MAXHOSTNAMELEN];
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 *);
70 int auth_login(struct config *config)
73 char hostbuf[MAXHOSTNAMELEN];
78 error = gethostname(hostbuf, sizeof(hostbuf));
79 hostbuf[sizeof(hostbuf) - 1] = '\0';
85 proto_printf(s, "USER %s %s\n", login != NULL ? login : "?",
86 host != NULL ? host : "?");
88 error = auth_domd5auth(config);
93 auth_domd5auth(struct config *config)
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;
102 lprintf(2, "MD5 authentication started\n");
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 ||
110 (strcmp(cmd, "AUTHMD5") != 0)) {
111 lprintf(-1, "Invalid server reply to USER\n");
112 return (STATUS_FAILURE);
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);
123 error = auth_lookuprecord(realm, &auth);
124 if (error != STATUS_SUCCESS)
126 client = auth.client;
127 auth_makesecret(&auth, shrdsecret);
130 if (strcmp(challenge, ".") != 0)
131 auth_makeresponse(challenge, shrdsecret, response);
133 auth_makechallenge(config, clichallenge);
134 proto_printf(s, "AUTHMD5 %s %s %s\n",
135 client == NULL ? "." : client, response, clichallenge);
137 line = stream_getln(s, NULL);
138 cmd = proto_get_ascii(&line);
139 if (cmd == NULL || line == NULL)
141 if (strcmp(cmd, "OK") == 0) {
142 srvresponse = proto_get_ascii(&line);
143 if (srvresponse == NULL)
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);
150 lprintf(2, "MD5 authentication successful\n");
151 return (STATUS_SUCCESS);
153 if (strcmp(cmd, "!") == 0) {
154 msg = proto_get_rest(&line);
157 lprintf(-1, "Server error: %s\n", msg);
158 return (STATUS_FAILURE);
161 lprintf(-1, "Invalid server reply to AUTHMD5\n");
162 return (STATUS_FAILURE);
166 auth_lookuprecord(char *server, struct srvrecord *auth)
168 char *home, *line, authfile[FILENAME_MAX];
170 int linenum = 0, error;
172 home = getenv("HOME");
174 lprintf(-1, "Environment variable \"HOME\" is not set\n");
175 return (STATUS_FAILURE);
177 snprintf(authfile, sizeof(authfile), "%s/%s", home, AUTHFILE);
178 s = stream_open_file(authfile, O_RDONLY);
180 lprintf(-1, "Could not open file %s\n", authfile);
181 return (STATUS_FAILURE);
184 while ((line = stream_getln(s, NULL)) != NULL) {
186 if (line[0] == '#' || line[0] == '\0')
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);
194 /* Skip the rest of this line, it isn't what we are looking for. */
195 if (strcasecmp(auth->server, server) != 0)
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);
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);
210 lprintf(2, "Found authentication record for server \"%s\"\n",
212 return (STATUS_SUCCESS);
214 lprintf(-1, "Unknown server \"%s\". Fix your %s\n", server , authfile);
215 memset(auth->password, 0, sizeof(auth->password));
218 return (STATUS_FAILURE);
222 auth_parsetoken(char **line, char *buf, int len)
226 colon = strchr(*line, ':');
228 return (STATUS_FAILURE);
231 strncpy(buf, *line, len - 1);
233 return (STATUS_SUCCESS);
237 auth_makesecret(struct srvrecord *auth, char *secret)
240 const char *md5salt = "$md5$";
241 unsigned char md5sum[MD5_BYTES];
245 for (s = auth->client; *s != 0; ++s) {
247 MD5_Update(&md5, &ch, 1);
249 MD5_Update(&md5, ":", 1);
250 for (s = auth->server; *s != 0; ++s) {
252 MD5_Update(&md5, &ch, 1);
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));
263 auth_makeresponse(char *challenge, char *sharedsecret, char *response)
266 unsigned char md5sum[MD5_BYTES];
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);
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.
284 auth_makechallenge(struct config *config, char *challenge)
287 unsigned char md5sum[MD5_BYTES];
290 struct sockaddr_in laddr;
294 gettimeofday(&tv, NULL);
297 srand(tv.tv_usec ^ tv.tv_sec ^ pid);
298 addrlen = sizeof(laddr);
299 error = getsockname(config->socket, (struct sockaddr *)&laddr, &addrlen);
301 memset(&laddr, 0, sizeof(laddr));
303 gettimeofday(&tv, NULL);
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);
313 auth_checkresponse(char *response, char *challenge, char *secret)
315 char correctresponse[MD5_CHARS_MAX];
317 auth_makeresponse(challenge, secret, correctresponse);
318 return (strcmp(response, correctresponse) == 0);
322 auth_readablesum(unsigned char *md5sum, char *readable)
327 for (i = 0; i < MD5_BYTES; ++i, s+=2) {
328 sprintf(s, "%.2x", md5sum[i]);