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