1/* 2 * Support rsync daemon authentication. 3 * 4 * Copyright (C) 1998-2000 Andrew Tridgell 5 * Copyright (C) 2002, 2004, 2005, 2006 Wayne Davison 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. 20 */ 21 22#include "rsync.h" 23 24extern char *password_file; 25 26/*************************************************************************** 27encode a buffer using base64 - simple and slow algorithm. null terminates 28the result. 29 ***************************************************************************/ 30void base64_encode(char *buf, int len, char *out, int pad) 31{ 32 char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 33 int bit_offset, byte_offset, idx, i; 34 unsigned char *d = (unsigned char *)buf; 35 int bytes = (len*8 + 5)/6; 36 37 for (i = 0; i < bytes; i++) { 38 byte_offset = (i*6)/8; 39 bit_offset = (i*6)%8; 40 if (bit_offset < 3) { 41 idx = (d[byte_offset] >> (2-bit_offset)) & 0x3F; 42 } else { 43 idx = (d[byte_offset] << (bit_offset-2)) & 0x3F; 44 if (byte_offset+1 < len) { 45 idx |= (d[byte_offset+1] >> (8-(bit_offset-2))); 46 } 47 } 48 out[i] = b64[idx]; 49 } 50 51 while (pad && (i % 4)) 52 out[i++] = '='; 53 54 out[i] = '\0'; 55} 56 57/* Generate a challenge buffer and return it base64-encoded. */ 58static void gen_challenge(char *addr, char *challenge) 59{ 60 char input[32]; 61 char md4_out[MD4_SUM_LENGTH]; 62 struct timeval tv; 63 64 memset(input, 0, sizeof input); 65 66 strlcpy((char *)input, addr, 17); 67 sys_gettimeofday(&tv); 68 SIVAL(input, 16, tv.tv_sec); 69 SIVAL(input, 20, tv.tv_usec); 70 SIVAL(input, 24, getpid()); 71 72 sum_init(0); 73 sum_update(input, sizeof input); 74 sum_end(md4_out); 75 76 base64_encode(md4_out, MD4_SUM_LENGTH, challenge, 0); 77} 78 79 80/* Return the secret for a user from the secret file, null terminated. 81 * Maximum length is len (not counting the null). */ 82static int get_secret(int module, char *user, char *secret, int len) 83{ 84 char *fname = lp_secrets_file(module); 85 STRUCT_STAT st; 86 int fd, ok = 1; 87 char ch, *p; 88 89 if (!fname || !*fname) 90 return 0; 91 92 if ((fd = open(fname, O_RDONLY)) < 0) 93 return 0; 94 95 if (do_stat(fname, &st) == -1) { 96 rsyserr(FLOG, errno, "stat(%s)", fname); 97 ok = 0; 98 } else if (lp_strict_modes(module)) { 99 if ((st.st_mode & 06) != 0) { 100 rprintf(FLOG, "secrets file must not be other-accessible (see strict modes option)\n"); 101 ok = 0; 102 } else if (MY_UID() == 0 && st.st_uid != 0) { 103 rprintf(FLOG, "secrets file must be owned by root when running as root (see strict modes)\n"); 104 ok = 0; 105 } 106 } 107 if (!ok) { 108 rprintf(FLOG, "continuing without secrets file\n"); 109 close(fd); 110 return 0; 111 } 112 113 if (*user == '#') { 114 /* Reject attempt to match a comment. */ 115 close(fd); 116 return 0; 117 } 118 119 /* Try to find a line that starts with the user name and a ':'. */ 120 p = user; 121 while (1) { 122 if (read(fd, &ch, 1) != 1) { 123 close(fd); 124 return 0; 125 } 126 if (ch == '\n') 127 p = user; 128 else if (p) { 129 if (*p == ch) 130 p++; 131 else if (!*p && ch == ':') 132 break; 133 else 134 p = NULL; 135 } 136 } 137 138 /* Slurp the secret into the "secret" buffer. */ 139 p = secret; 140 while (len > 0) { 141 if (read(fd, p, 1) != 1 || *p == '\n') 142 break; 143 if (*p == '\r') 144 continue; 145 p++; 146 len--; 147 } 148 *p = '\0'; 149 close(fd); 150 151 return 1; 152} 153 154static char *getpassf(char *filename) 155{ 156 STRUCT_STAT st; 157 char buffer[512], *p; 158 int fd, n, ok = 1; 159 char *envpw = getenv("RSYNC_PASSWORD"); 160 161 if (!filename) 162 return NULL; 163 164 if ((fd = open(filename,O_RDONLY)) < 0) { 165 rsyserr(FERROR, errno, "could not open password file \"%s\"", 166 filename); 167 if (envpw) 168 rprintf(FERROR, "falling back to RSYNC_PASSWORD environment variable.\n"); 169 return NULL; 170 } 171 172 if (do_stat(filename, &st) == -1) { 173 rsyserr(FERROR, errno, "stat(%s)", filename); 174 ok = 0; 175 } else if ((st.st_mode & 06) != 0) { 176 rprintf(FERROR,"password file must not be other-accessible\n"); 177 ok = 0; 178 } else if (MY_UID() == 0 && st.st_uid != 0) { 179 rprintf(FERROR,"password file must be owned by root when running as root\n"); 180 ok = 0; 181 } 182 if (!ok) { 183 rprintf(FERROR,"continuing without password file\n"); 184 if (envpw) 185 rprintf(FERROR, "using RSYNC_PASSWORD environment variable.\n"); 186 close(fd); 187 return NULL; 188 } 189 190 if (envpw) 191 rprintf(FERROR, "RSYNC_PASSWORD environment variable ignored\n"); 192 193 n = read(fd, buffer, sizeof buffer - 1); 194 close(fd); 195 if (n > 0) { 196 buffer[n] = '\0'; 197 if ((p = strtok(buffer, "\n\r")) != NULL) 198 return strdup(p); 199 } 200 201 return NULL; 202} 203 204/* Generate an MD4 hash created from the combination of the password 205 * and the challenge string and return it base64-encoded. */ 206static void generate_hash(char *in, char *challenge, char *out) 207{ 208 char buf[MD4_SUM_LENGTH]; 209 210 sum_init(0); 211 sum_update(in, strlen(in)); 212 sum_update(challenge, strlen(challenge)); 213 sum_end(buf); 214 215 base64_encode(buf, MD4_SUM_LENGTH, out, 0); 216} 217 218/* Possibly negotiate authentication with the client. Use "leader" to 219 * start off the auth if necessary. 220 * 221 * Return NULL if authentication failed. Return "" if anonymous access. 222 * Otherwise return username. 223 */ 224char *auth_server(int f_in, int f_out, int module, char *host, char *addr, 225 char *leader) 226{ 227 char *users = lp_auth_users(module); 228 char challenge[MD4_SUM_LENGTH*2]; 229 char line[BIGPATHBUFLEN]; 230 char secret[512]; 231 char pass2[MD4_SUM_LENGTH*2]; 232 char *tok, *pass; 233 234 /* if no auth list then allow anyone in! */ 235 if (!users || !*users) 236 return ""; 237 238 gen_challenge(addr, challenge); 239 240 io_printf(f_out, "%s%s\n", leader, challenge); 241 242 if (!read_line(f_in, line, sizeof line - 1) 243 || (pass = strchr(line, ' ')) == NULL) { 244 rprintf(FLOG, "auth failed on module %s from %s (%s): " 245 "invalid challenge response\n", 246 lp_name(module), host, addr); 247 return NULL; 248 } 249 *pass++ = '\0'; 250 251 if (!(users = strdup(users))) 252 out_of_memory("auth_server"); 253 254 for (tok = strtok(users, " ,\t"); tok; tok = strtok(NULL, " ,\t")) { 255 if (wildmatch(tok, line)) 256 break; 257 } 258 free(users); 259 260 if (!tok) { 261 rprintf(FLOG, "auth failed on module %s from %s (%s): " 262 "unauthorized user\n", 263 lp_name(module), host, addr); 264 return NULL; 265 } 266 267 memset(secret, 0, sizeof secret); 268 if (!get_secret(module, line, secret, sizeof secret - 1)) { 269 memset(secret, 0, sizeof secret); 270 rprintf(FLOG, "auth failed on module %s from %s (%s): " 271 "missing secret for user \"%s\"\n", 272 lp_name(module), host, addr, line); 273 return NULL; 274 } 275 276 generate_hash(secret, challenge, pass2); 277 memset(secret, 0, sizeof secret); 278 279 if (strcmp(pass, pass2) != 0) { 280 rprintf(FLOG, "auth failed on module %s from %s (%s): " 281 "password mismatch\n", 282 lp_name(module), host, addr); 283 return NULL; 284 } 285 286 return strdup(line); 287} 288 289 290void auth_client(int fd, char *user, char *challenge) 291{ 292 char *pass; 293 char pass2[MD4_SUM_LENGTH*2]; 294 295 if (!user || !*user) 296 user = "nobody"; 297 298 if (!(pass = getpassf(password_file)) 299 && !(pass = getenv("RSYNC_PASSWORD"))) { 300 /* XXX: cyeoh says that getpass is deprecated, because 301 * it may return a truncated password on some systems, 302 * and it is not in the LSB. 303 * 304 * Andrew Klein says that getpassphrase() is present 305 * on Solaris and reads up to 256 characters. 306 * 307 * OpenBSD has a readpassphrase() that might be more suitable. 308 */ 309 pass = getpass("Password: "); 310 } 311 312 if (!pass) 313 pass = ""; 314 315 generate_hash(pass, challenge, pass2); 316 io_printf(fd, "%s %s\n", user, pass2); 317} 318