authenticate.cc (11060B)
1 /* -*- Mode: c++; -*- */ 2 /* -------------------------------------------------------------------- 3 * Filename: 4 * authenticate.cc 5 * 6 * Description: 7 * Implementation of the common authentication mechanism. 8 * 9 * Authors: 10 * Andreas Aardal Hanssen <andreas-binc curly bincimap spot org> 11 * 12 * Bugs: 13 * 14 * ChangeLog: 15 * 16 * -------------------------------------------------------------------- 17 * Copyright 2002-2005 Andreas Aardal Hanssen 18 * 19 * This program is free software; you can redistribute it and/or modify 20 * it under the terms of the GNU General Public License as published by 21 * the Free Software Foundation; either version 2 of the License, or 22 * (at your option) any later version. 23 * 24 * This program is distributed in the hope that it will be useful, 25 * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 * GNU General Public License for more details. 28 * 29 * You should have received a copy of the GNU General Public License 30 * along with this program; if not, write to the Free Software 31 * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. 32 * -------------------------------------------------------------------- 33 */ 34 #ifdef HAVE_CONFIG_H 35 #include <config.h> 36 #endif 37 38 #include <string> 39 #include <vector> 40 41 #include <sys/types.h> 42 #include <grp.h> 43 #include <pwd.h> 44 #include <signal.h> 45 46 #ifndef HAVE_SYS_WAIT_H 47 #include <wait.h> 48 #else 49 #include <sys/wait.h> 50 #endif 51 52 #include "authenticate.h" 53 #include "io.h" 54 #include "session.h" 55 #include "storage.h" 56 #include "convert.h" 57 58 using namespace ::std; 59 using namespace Binc; 60 61 namespace { 62 63 bool enteredjail = false; 64 65 void enterJail(void) 66 { 67 Session &session = Session::getInstance(); 68 IO &logger = IOFactory::getInstance().get(2); 69 70 // drop all privileges that we can drop and enter chroot jail 71 const string &jailpath = session.globalconfig["Security"]["jail path"]; 72 const string &jailuser = session.globalconfig["Security"]["jail user"]; 73 const string &jailgroup = session.globalconfig["Security"]["jail group"]; 74 struct group *gr = getgrnam(jailgroup.c_str()); 75 struct passwd *pw = getpwnam(jailuser.c_str()); 76 77 if (jailgroup != "" && !gr) 78 logger << "invalid jail group <" << jailgroup 79 << "> - check the Security section of bincimap.conf" 80 << endl; 81 82 if (jailuser != "" && !pw) 83 logger << "invalid jail user <" << jailuser 84 << "> - check the Security section of bincimap.conf" 85 << endl; 86 87 setgroups(0, 0); 88 89 if (jailpath != "") { 90 if (chroot(jailpath.c_str()) != 0) 91 logger << "unable to enter jail path " 92 << toImapString(jailpath) << endl; 93 else 94 chdir("/"); 95 } 96 97 if (gr) setgid(gr->gr_gid); 98 if (pw) setuid(pw->pw_uid); 99 100 umask(0); 101 } 102 } 103 104 // 0 = ok 105 // 1 = internal error 106 // 2 = failed 107 // 3 = timeout 108 // -1 = abort 109 //------------------------------------------------------------------------ 110 int Binc::authenticate(Depot &depot, const string &username, 111 const string &password) 112 { 113 Session &session = Session::getInstance(); 114 IO &com = IOFactory::getInstance().get(1); 115 IO &logger = IOFactory::getInstance().get(2); 116 117 // read auth penalty from global config 118 string authpenalty = session.globalconfig["Authentication"]["auth penalty"]; 119 120 session.setUserID(username); 121 122 // export the session data 123 session.exportToEnv(); 124 125 // The information supplied on descriptor 3 is a login name 126 // terminated by \0, a password terminated by \0, a timestamp 127 // terminated by \0, and possibly more data. There are no other 128 // restrictions on the form of the login name, password, and 129 // timestamp. 130 int authintercom[2]; 131 int intercomw[2]; 132 int intercomr[2]; 133 bool authenticated = false; 134 135 if (pipe(authintercom) == -1) { 136 session.setLastError("An error occurred when creating pipes: " 137 + string(strerror(errno))); 138 return -1; 139 } 140 141 if (pipe(intercomw) == -1) { 142 session.setLastError("An error occurred when creating pipes: " 143 + string(strerror(errno))); 144 close(authintercom[0]); 145 close(authintercom[1]); 146 return -1; 147 } 148 149 if (pipe(intercomr) == -1) { 150 session.setLastError("An error occurred when creating pipes: " 151 + string(strerror(errno))); 152 close(intercomw[0]); 153 close(intercomr[0]); 154 close(authintercom[0]); 155 close(authintercom[1]); 156 return -1; 157 } 158 159 string timestamp; 160 time_t t; 161 char *c; 162 t = time(0); 163 if ((c = ctime(&t)) != 0) 164 timestamp = c; 165 else 166 timestamp = "unknown timestamp"; 167 168 // execute authentication module 169 int result; 170 int childspid = fork(); 171 if (childspid == -1) { 172 logger << "fork failed: " << strerror(errno) << endl; 173 return 1; 174 } 175 176 if (childspid == 0) { 177 178 close(authintercom[1]); 179 close(intercomr[0]); 180 close(intercomw[1]); 181 182 if (dup2(intercomr[1], 1) == -1) { 183 logger << "[auth module] dup2 failed: " 184 << strerror(errno) << endl; 185 logger.flushContent(); 186 exit(111); 187 } 188 189 if (dup2(intercomw[0], 0) == -1) { 190 logger << "[auth module] dup2 failed: " 191 << strerror(errno) << endl; 192 logger.flushContent(); 193 exit(111); 194 } 195 196 if (dup2(authintercom[0], 3) == -1) { 197 logger << "[auth module] dup2 failed: " 198 << strerror(errno) << endl; 199 logger.flushContent(); 200 exit(111); 201 } 202 203 session.exportToEnv(); 204 205 if (session.unparsedArgs[0] != 0) { 206 execv(session.unparsedArgs[0], &session.unparsedArgs[0]); 207 logger << "[auth module] invocation of " << session.unparsedArgs[0] 208 << " failed: " << strerror(errno) 209 << endl; 210 logger.flushContent(); 211 exit(111); 212 } 213 214 logger << "[auth module] Missing mandatory -- in arg list," 215 " after bincimap-up + arguments, before authenticator." 216 " Please check your run scripts and the man pages for" 217 " more on how to invoke Binc IMAP." << endl; 218 logger.flushContent(); 219 exit(111); 220 } 221 222 if (authintercom[0] != -1) 223 close(authintercom[0]); 224 225 bool error = false; 226 227 // write the userid 228 signal(SIGPIPE, SIG_IGN); 229 if (write(authintercom[1], 230 username.c_str(), 231 username.length()) != (int) username.length()) 232 error = true; 233 234 // terminate with a \0 235 if (!error && write(authintercom[1], "", 1) != 1) 236 error = true; 237 238 // write the password 239 if (!error && write(authintercom[1], 240 password.c_str(), 241 password.length()) != (int) password.length()) 242 error = true; 243 244 // terminate with a \0 245 if (!error && write(authintercom[1], "", 1) != 1) 246 error = true; 247 248 // write the timestamp 249 if (!error && write(authintercom[1], 250 timestamp.c_str(), 251 timestamp.length()) != (int) timestamp.length()) 252 error = true; 253 254 // terminate with a \0 255 if (!error && write(authintercom[1], "", 1) != 1) 256 error = true; 257 258 if (error) { 259 logger << "error writing to authenticator " 260 << session.unparsedArgs[0] << ": " 261 << strerror(errno) << endl; 262 return 1; 263 } 264 265 // close the write channel. this is necessary for the checkpassword 266 // module to see an EOF. 267 close(authintercom[1]); 268 close(intercomr[1]); 269 close(intercomw[0]); 270 271 fd_set rmask; 272 FD_ZERO(&rmask); 273 FD_SET(fileno(stdin), &rmask); 274 FD_SET(intercomr[0], &rmask); 275 276 int maxfd = intercomr[0]; 277 bool disconnected = false; 278 bool timedout = false; 279 com.disableInputLimit(); 280 281 bool eof = false; 282 while (!eof) { 283 fd_set rtmp = rmask; 284 struct timeval timeout; 285 286 // time out 5 minutes after the idle timeout. we expect the main 287 // server to time out at the right time, but will shut down at 288 // T+5m in case of a server lockup. 289 timeout.tv_sec = session.idletimeout + 5*60; 290 timeout.tv_usec = 0; 291 292 int n = select(maxfd + 1, &rtmp, 0, 0, &timeout); 293 if (n < 0) { 294 if (errno == EINTR) { 295 logger << "warning: zero data from select: " 296 << strerror(errno) << endl; 297 break; 298 } 299 300 logger << "error: invalid exit from select, " 301 << strerror(errno) << endl; 302 break; 303 } 304 305 if (n == 0) { 306 logger << "lock-up: server timed out after " 307 << session.idletimeout << " seconds" << endl; 308 timedout = true; 309 break; 310 } 311 312 if (FD_ISSET(fileno(stdin), &rtmp)) { 313 authenticated = true; 314 315 do { 316 string data; 317 int ret = com.readStr(data); 318 if (ret == 0 || ret == -1) { 319 session.setLastError("client disconnected"); 320 eof = true; 321 disconnected = true; 322 break; 323 } 324 325 if (ret == -2) { 326 // Fall through. Triggered when there was no data 327 // to read, even though no error has occurred 328 continue; 329 } 330 331 for (;;) { 332 signal(SIGPIPE, SIG_IGN); 333 int w = write(intercomw[1], data.c_str(), data.length()); 334 if (w > 0) 335 Session::getInstance().addReadBytes(w); 336 337 if (w == (int) data.length()) 338 break; 339 340 if (w == -1 && (errno == EINTR)) 341 continue; 342 343 logger << "error writing to server: " 344 << strerror(errno) << endl; 345 eof = true; 346 break; 347 } 348 } while (com.pending()); 349 } 350 351 if (FD_ISSET(intercomr[0], &rtmp)) { 352 char buf[8192]; 353 int ret = read(intercomr[0], buf, sizeof(buf)); 354 if (ret == 0) { 355 // Main server has shut down 356 eof = true; 357 break; 358 } else if (ret == -1) { 359 logger << "error reading from server: " << strerror(errno) 360 << endl; 361 eof = true; 362 break; 363 } else { 364 if (enteredjail == false) { 365 enterJail(); 366 enteredjail = true; 367 } 368 369 Session::getInstance().addWriteBytes(ret); 370 371 com << string(buf, ret); 372 com.flushContent(); 373 } 374 } 375 } 376 377 close(intercomr[0]); 378 close(intercomw[1]); 379 380 // catch the dead baby 381 if (waitpid(childspid, &result, 0) != childspid) { 382 logger << "<" << username << "> authentication failed: " 383 << (authenticated ? "server " : session.unparsedArgs[0]) 384 << " waitpid returned unexpected value" << endl; 385 string tmp = strerror(errno); 386 387 return -1; 388 } 389 390 // if the server died because we closed the sockets after a timeout, 391 // exit 3. 392 if (timedout) 393 return 3; 394 395 if (disconnected) 396 return 0; 397 398 if (WIFSIGNALED(result)) { 399 logger << "<" << username << "> authentication failed: " 400 << (authenticated ? "server" : session.unparsedArgs[0]) 401 << " died by signal " 402 << WTERMSIG(result) << endl; 403 404 sleep(atoi(authpenalty)); 405 session.setState(Session::LOGOUT); 406 return -1; 407 } 408 409 switch (WEXITSTATUS(result)) { 410 case 0: break; 411 case 2: 412 // internal error 413 logger << "<" << username << "> authentication failed: " 414 << (authenticated ? "authenticator " : "server ") 415 << " returned 2 (invalid use of authenticator)" << endl; 416 return -1; 417 case 111: 418 // internal error 419 logger << "<" << username << "> authentication failed: " 420 << (authenticated ? "authenticator " : "server ") 421 << " returned " 422 << WEXITSTATUS(result) << " (internal error)" << endl; 423 return -1; 424 case 1: 425 default: 426 // authentication failed - sleep. 427 logger << "<" << username 428 << "> authentication failed: wrong userid or password" << endl; 429 sleep(atoi(authpenalty)); 430 return 2; 431 } 432 433 return 0; 434 }