1203368Slulf/*- 2203368Slulf * Copyright (c) 2003-2007, Petar Zhivkov Petrov <pesho.petrov@gmail.com> 3203368Slulf * All rights reserved. 4203368Slulf * 5203368Slulf * Redistribution and use in source and binary forms, with or without 6203368Slulf * modification, are permitted provided that the following conditions 7203368Slulf * are met: 8203368Slulf * 1. Redistributions of source code must retain the above copyright 9203368Slulf * notice, this list of conditions and the following disclaimer. 10203368Slulf * 2. Redistributions in binary form must reproduce the above copyright 11203368Slulf * notice, this list of conditions and the following disclaimer in the 12203368Slulf * documentation and/or other materials provided with the distribution. 13203368Slulf * 14203368Slulf * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15203368Slulf * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16203368Slulf * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17203368Slulf * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18203368Slulf * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19203368Slulf * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20203368Slulf * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21203368Slulf * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22203368Slulf * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23203368Slulf * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24203368Slulf * SUCH DAMAGE. 25203368Slulf * 26203368Slulf * $FreeBSD$ 27203368Slulf */ 28203368Slulf 29203368Slulf#include <sys/param.h> 30203368Slulf#include <sys/socket.h> 31203368Slulf#include <sys/time.h> 32203368Slulf#include <sys/types.h> 33203368Slulf 34203368Slulf#include <arpa/inet.h> 35203368Slulf#include <netinet/in.h> 36203368Slulf 37203368Slulf#include <ctype.h> 38203368Slulf#include <stdio.h> 39203368Slulf#include <stdlib.h> 40203368Slulf#include <string.h> 41203368Slulf#include <unistd.h> 42203368Slulf 43203368Slulf#include "auth.h" 44203368Slulf#include "config.h" 45203368Slulf#include "misc.h" 46203368Slulf#include "proto.h" 47203368Slulf#include "stream.h" 48203368Slulf 49203368Slulf#define MD5_BYTES 16 50203368Slulf 51203368Slulf/* This should be at least 2 * MD5_BYTES + 6 (length of "$md5$" + 1) */ 52203368Slulf#define MD5_CHARS_MAX (2*(MD5_BYTES)+6) 53203368Slulf 54203368Slulfstruct srvrecord { 55203368Slulf char server[MAXHOSTNAMELEN]; 56203368Slulf char client[256]; 57203368Slulf char password[256]; 58203368Slulf}; 59203368Slulf 60203368Slulfstatic int auth_domd5auth(struct config *); 61203368Slulfstatic int auth_lookuprecord(char *, struct srvrecord *); 62203368Slulfstatic int auth_parsetoken(char **, char *, int); 63203368Slulfstatic void auth_makesecret(struct srvrecord *, char *); 64203368Slulfstatic void auth_makeresponse(char *, char *, char *); 65203368Slulfstatic void auth_readablesum(unsigned char *, char *); 66203368Slulfstatic void auth_makechallenge(struct config *, char *); 67203368Slulfstatic int auth_checkresponse(char *, char *, char *); 68203368Slulf 69203368Slulfint auth_login(struct config *config) 70203368Slulf{ 71203368Slulf struct stream *s; 72203368Slulf char hostbuf[MAXHOSTNAMELEN]; 73203368Slulf char *login, *host; 74203368Slulf int error; 75203368Slulf 76203368Slulf s = config->server; 77203368Slulf error = gethostname(hostbuf, sizeof(hostbuf)); 78203368Slulf hostbuf[sizeof(hostbuf) - 1] = '\0'; 79203368Slulf if (error) 80203368Slulf host = NULL; 81203368Slulf else 82203368Slulf host = hostbuf; 83203368Slulf login = getlogin(); 84203368Slulf proto_printf(s, "USER %s %s\n", login != NULL ? login : "?", 85203368Slulf host != NULL ? host : "?"); 86203368Slulf stream_flush(s); 87203368Slulf error = auth_domd5auth(config); 88203368Slulf return (error); 89203368Slulf} 90203368Slulf 91203368Slulfstatic int 92203368Slulfauth_domd5auth(struct config *config) 93203368Slulf{ 94203368Slulf struct stream *s; 95203368Slulf char *line, *cmd, *challenge, *realm, *client, *srvresponse, *msg; 96203368Slulf char shrdsecret[MD5_CHARS_MAX], response[MD5_CHARS_MAX]; 97203368Slulf char clichallenge[MD5_CHARS_MAX]; 98203368Slulf struct srvrecord auth; 99203368Slulf int error; 100203368Slulf 101203368Slulf lprintf(2, "MD5 authentication started\n"); 102203368Slulf s = config->server; 103203368Slulf line = stream_getln(s, NULL); 104203368Slulf cmd = proto_get_ascii(&line); 105203368Slulf realm = proto_get_ascii(&line); 106203368Slulf challenge = proto_get_ascii(&line); 107203368Slulf if (challenge == NULL || 108203368Slulf line != NULL || 109203368Slulf (strcmp(cmd, "AUTHMD5") != 0)) { 110203368Slulf lprintf(-1, "Invalid server reply to USER\n"); 111203368Slulf return (STATUS_FAILURE); 112203368Slulf } 113203368Slulf 114203368Slulf client = NULL; 115203368Slulf response[0] = clichallenge[0] = '.'; 116203368Slulf response[1] = clichallenge[1] = 0; 117203368Slulf if (config->reqauth || (strcmp(challenge, ".") != 0)) { 118203368Slulf if (strcmp(realm, ".") == 0) { 119203368Slulf lprintf(-1, "Authentication required, but not enabled on server\n"); 120203368Slulf return (STATUS_FAILURE); 121203368Slulf } 122203368Slulf error = auth_lookuprecord(realm, &auth); 123203368Slulf if (error != STATUS_SUCCESS) 124203368Slulf return (error); 125203368Slulf client = auth.client; 126203368Slulf auth_makesecret(&auth, shrdsecret); 127203368Slulf } 128203368Slulf 129203368Slulf if (strcmp(challenge, ".") != 0) 130203368Slulf auth_makeresponse(challenge, shrdsecret, response); 131203368Slulf if (config->reqauth) 132203368Slulf auth_makechallenge(config, clichallenge); 133203368Slulf proto_printf(s, "AUTHMD5 %s %s %s\n", 134203368Slulf client == NULL ? "." : client, response, clichallenge); 135203368Slulf stream_flush(s); 136203368Slulf line = stream_getln(s, NULL); 137203368Slulf cmd = proto_get_ascii(&line); 138203368Slulf if (cmd == NULL || line == NULL) 139203368Slulf goto bad; 140203368Slulf if (strcmp(cmd, "OK") == 0) { 141203368Slulf srvresponse = proto_get_ascii(&line); 142203368Slulf if (srvresponse == NULL) 143203368Slulf goto bad; 144203368Slulf if (config->reqauth && 145203368Slulf !auth_checkresponse(srvresponse, clichallenge, shrdsecret)) { 146203368Slulf lprintf(-1, "Server failed to authenticate itself to client\n"); 147203368Slulf return (STATUS_FAILURE); 148203368Slulf } 149204664Slulf lprintf(2, "MD5 authentication successful\n"); 150203368Slulf return (STATUS_SUCCESS); 151203368Slulf } 152203368Slulf if (strcmp(cmd, "!") == 0) { 153203368Slulf msg = proto_get_rest(&line); 154203368Slulf if (msg == NULL) 155203368Slulf goto bad; 156203368Slulf lprintf(-1, "Server error: %s\n", msg); 157203368Slulf return (STATUS_FAILURE); 158203368Slulf } 159203368Slulfbad: 160203368Slulf lprintf(-1, "Invalid server reply to AUTHMD5\n"); 161203368Slulf return (STATUS_FAILURE); 162203368Slulf} 163203368Slulf 164203368Slulfstatic int 165203368Slulfauth_lookuprecord(char *server, struct srvrecord *auth) 166203368Slulf{ 167203368Slulf char *home, *line, authfile[FILENAME_MAX]; 168203368Slulf struct stream *s; 169203368Slulf int linenum = 0, error; 170203368Slulf 171203368Slulf home = getenv("HOME"); 172203368Slulf if (home == NULL) { 173203368Slulf lprintf(-1, "Environment variable \"HOME\" is not set\n"); 174203368Slulf return (STATUS_FAILURE); 175203368Slulf } 176203368Slulf snprintf(authfile, sizeof(authfile), "%s/%s", home, AUTHFILE); 177203368Slulf s = stream_open_file(authfile, O_RDONLY); 178203368Slulf if (s == NULL) { 179203368Slulf lprintf(-1, "Could not open file %s\n", authfile); 180203368Slulf return (STATUS_FAILURE); 181203368Slulf } 182203368Slulf 183203368Slulf while ((line = stream_getln(s, NULL)) != NULL) { 184203368Slulf linenum++; 185203368Slulf if (line[0] == '#' || line[0] == '\0') 186203368Slulf continue; 187203368Slulf error = auth_parsetoken(&line, auth->server, 188203368Slulf sizeof(auth->server)); 189203368Slulf if (error != STATUS_SUCCESS) { 190225536Sbrueffer lprintf(-1, "%s:%d Missing client name\n", authfile, linenum); 191203368Slulf goto close; 192203368Slulf } 193203368Slulf /* Skip the rest of this line, it isn't what we are looking for. */ 194225535Sbrueffer if (strcasecmp(auth->server, server) != 0) 195203368Slulf continue; 196203368Slulf error = auth_parsetoken(&line, auth->client, 197203368Slulf sizeof(auth->client)); 198203368Slulf if (error != STATUS_SUCCESS) { 199225536Sbrueffer lprintf(-1, "%s:%d Missing password\n", authfile, linenum); 200203368Slulf goto close; 201203368Slulf } 202203368Slulf error = auth_parsetoken(&line, auth->password, 203203368Slulf sizeof(auth->password)); 204203368Slulf if (error != STATUS_SUCCESS) { 205225536Sbrueffer lprintf(-1, "%s:%d Missing comment\n", authfile, linenum); 206203368Slulf goto close; 207203368Slulf } 208203368Slulf stream_close(s); 209203368Slulf lprintf(2, "Found authentication record for server \"%s\"\n", 210203368Slulf server); 211203368Slulf return (STATUS_SUCCESS); 212203368Slulf } 213203368Slulf lprintf(-1, "Unknown server \"%s\". Fix your %s\n", server , authfile); 214203368Slulf memset(auth->password, 0, sizeof(auth->password)); 215203368Slulfclose: 216203368Slulf stream_close(s); 217203368Slulf return (STATUS_FAILURE); 218203368Slulf} 219203368Slulf 220203368Slulfstatic int 221203368Slulfauth_parsetoken(char **line, char *buf, int len) 222203368Slulf{ 223203368Slulf char *colon; 224203368Slulf 225203368Slulf colon = strchr(*line, ':'); 226203368Slulf if (colon == NULL) 227203368Slulf return (STATUS_FAILURE); 228203368Slulf *colon = 0; 229203368Slulf buf[len - 1] = 0; 230203368Slulf strncpy(buf, *line, len - 1); 231203368Slulf *line = colon + 1; 232203368Slulf return (STATUS_SUCCESS); 233203368Slulf} 234203368Slulf 235203368Slulfstatic void 236203368Slulfauth_makesecret(struct srvrecord *auth, char *secret) 237203368Slulf{ 238203368Slulf char *s, ch; 239203368Slulf const char *md5salt = "$md5$"; 240203368Slulf unsigned char md5sum[MD5_BYTES]; 241203368Slulf MD5_CTX md5; 242203368Slulf 243203368Slulf MD5_Init(&md5); 244203368Slulf for (s = auth->client; *s != 0; ++s) { 245203368Slulf ch = tolower(*s); 246203368Slulf MD5_Update(&md5, &ch, 1); 247203368Slulf } 248203368Slulf MD5_Update(&md5, ":", 1); 249203368Slulf for (s = auth->server; *s != 0; ++s) { 250203368Slulf ch = tolower(*s); 251203368Slulf MD5_Update(&md5, &ch, 1); 252203368Slulf } 253203368Slulf MD5_Update(&md5, ":", 1); 254203368Slulf MD5_Update(&md5, auth->password, strlen(auth->password)); 255203368Slulf MD5_Final(md5sum, &md5); 256228625Sdim memset(secret, 0, MD5_CHARS_MAX); 257203368Slulf strcpy(secret, md5salt); 258203368Slulf auth_readablesum(md5sum, secret + strlen(md5salt)); 259203368Slulf} 260203368Slulf 261203368Slulfstatic void 262203368Slulfauth_makeresponse(char *challenge, char *sharedsecret, char *response) 263203368Slulf{ 264203368Slulf MD5_CTX md5; 265203368Slulf unsigned char md5sum[MD5_BYTES]; 266203368Slulf 267203368Slulf MD5_Init(&md5); 268203368Slulf MD5_Update(&md5, sharedsecret, strlen(sharedsecret)); 269203368Slulf MD5_Update(&md5, ":", 1); 270203368Slulf MD5_Update(&md5, challenge, strlen(challenge)); 271203368Slulf MD5_Final(md5sum, &md5); 272203368Slulf auth_readablesum(md5sum, response); 273203368Slulf} 274203368Slulf 275203368Slulf/* 276203368Slulf * Generates a challenge string which is an MD5 sum 277203368Slulf * of a fairly random string. The purpose is to decrease 278203368Slulf * the possibility of generating the same challenge 279203368Slulf * string (even by different clients) more then once 280203368Slulf * for the same server. 281203368Slulf */ 282203368Slulfstatic void 283203368Slulfauth_makechallenge(struct config *config, char *challenge) 284203368Slulf{ 285203368Slulf MD5_CTX md5; 286203368Slulf unsigned char md5sum[MD5_BYTES]; 287203368Slulf char buf[128]; 288203368Slulf struct timeval tv; 289203368Slulf struct sockaddr_in laddr; 290203368Slulf pid_t pid, ppid; 291203368Slulf int error, addrlen; 292203368Slulf 293203368Slulf gettimeofday(&tv, NULL); 294203368Slulf pid = getpid(); 295203368Slulf ppid = getppid(); 296232320Scognet srandom(tv.tv_usec ^ tv.tv_sec ^ pid); 297203368Slulf addrlen = sizeof(laddr); 298203368Slulf error = getsockname(config->socket, (struct sockaddr *)&laddr, &addrlen); 299203368Slulf if (error < 0) { 300203368Slulf memset(&laddr, 0, sizeof(laddr)); 301203368Slulf } 302203368Slulf gettimeofday(&tv, NULL); 303203368Slulf MD5_Init(&md5); 304228667Sdim snprintf(buf, sizeof(buf), "%s:%jd:%ld:%ld:%d:%d", 305228667Sdim inet_ntoa(laddr.sin_addr), (intmax_t)tv.tv_sec, tv.tv_usec, 306228625Sdim random(), pid, ppid); 307203368Slulf MD5_Update(&md5, buf, strlen(buf)); 308203368Slulf MD5_Final(md5sum, &md5); 309203368Slulf auth_readablesum(md5sum, challenge); 310203368Slulf} 311203368Slulf 312203368Slulfstatic int 313203368Slulfauth_checkresponse(char *response, char *challenge, char *secret) 314203368Slulf{ 315203368Slulf char correctresponse[MD5_CHARS_MAX]; 316203368Slulf 317203368Slulf auth_makeresponse(challenge, secret, correctresponse); 318203368Slulf return (strcmp(response, correctresponse) == 0); 319203368Slulf} 320203368Slulf 321203368Slulfstatic void 322203368Slulfauth_readablesum(unsigned char *md5sum, char *readable) 323203368Slulf{ 324203368Slulf unsigned int i; 325203368Slulf char *s = readable; 326203368Slulf 327203368Slulf for (i = 0; i < MD5_BYTES; ++i, s+=2) { 328203368Slulf sprintf(s, "%.2x", md5sum[i]); 329203368Slulf } 330203368Slulf} 331203368Slulf 332