auth.c revision 232320
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/usr.bin/csup/auth.c 232320 2012-02-29 22:35:09Z cognet $ 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 54struct srvrecord { 55 char server[MAXHOSTNAMELEN]; 56 char client[256]; 57 char password[256]; 58}; 59 60static int auth_domd5auth(struct config *); 61static int auth_lookuprecord(char *, struct srvrecord *); 62static int auth_parsetoken(char **, char *, int); 63static void auth_makesecret(struct srvrecord *, char *); 64static void auth_makeresponse(char *, char *, char *); 65static void auth_readablesum(unsigned char *, char *); 66static void auth_makechallenge(struct config *, char *); 67static int auth_checkresponse(char *, char *, char *); 68 69int 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 91static int 92auth_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 } 159bad: 160 lprintf(-1, "Invalid server reply to AUTHMD5\n"); 161 return (STATUS_FAILURE); 162} 163 164static int 165auth_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)); 215close: 216 stream_close(s); 217 return (STATUS_FAILURE); 218} 219 220static int 221auth_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 235static void 236auth_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 261static void 262auth_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 */ 282static void 283auth_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 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