/** * @file authenticate.cc * @brief Implementation of the common (C/R) authentication mechanism. * @author Andreas Aardal Hanssen, Erwin Hoffmann * @date 2002-2005, 2023 */ #include #include #include #include #include #include #include #include // #ifndef HAVE_SYS_WAIT_H // #include // #else #include // #endif #include "authenticate.h" #include "convert.h" #include "globals.h" #include "iodevice.h" #include "iofactory.h" #include "session.h" using namespace Binc; using std::endl; using std::string; // 0 = ok // 1 = internal error // 2 = failed // 3 = timeout // -1 = abort int Binc::authenticate(Depot &depot, const string &username, const string &password, const string &challenge) { Session &session = Session::getInstance(); session.setUserID(username); // check if checkpassword is present if (access(session.unparsedArgs[0], X_OK) != 0) { // x is enough bincError << "unable to start authenticator " << session.unparsedArgs[0] << ": " << strerror(errno) << endl; return 1; } // The information supplied on descriptor 3 is a login name // terminated by \0, a password terminated by \0, a timestamp // terminated by \0, and possibly more data. There are no other // restrictions on the form of the login name, password, and // timestamp. int authintercom[2]; int intercomw[2]; int intercomr[2]; bool authenticated = false; if (pipe(authintercom) == -1) { session.setLastError("An error occurred when creating pipes: " + string(strerror(errno))); return -1; } if (pipe(intercomw) == -1) { session.setLastError("An error occurred when creating pipes: " + string(strerror(errno))); close(authintercom[0]); close(authintercom[1]); return -1; } if (pipe(intercomr) == -1) { session.setLastError("An error occurred when creating pipes: " + string(strerror(errno))); close(intercomw[0]); close(intercomr[0]); close(authintercom[0]); close(authintercom[1]); return -1; } string timestamp; time_t t = time(nullptr); char *c; if ((c = ctime(&t)) != nullptr) { timestamp = c; trim(timestamp); } else { timestamp = "unknown timestamp"; } string pid = std::to_string(session.getPid()); // execute authentication module int result; int childspid = fork(); if (childspid == -1) { bincLog << "bincimap-up: pid " << pid << " failed to start main server: " << strerror(errno) << endl; return 1; } if (childspid == 0) { close(authintercom[1]); close(intercomr[0]); close(intercomw[1]); if (dup2(intercomr[1], 1) == -1) { bincDebug << "bincimap-up: pid " << pid << " authenticate(), [auth module] dup2 failed: " << strerror(errno) << endl; bincDebug.flush(); exit(111); } if (dup2(intercomw[0], 0) == -1) { bincDebug << "bincimap-up: pid " << pid << " authenticate(), [auth module] dup2 failed: " << strerror(errno) << endl; bincDebug.flush(); exit(111); } if (dup2(authintercom[0], 3) == -1) { bincDebug << "bincimap-up: pid " << pid << " authenticate(), [auth module] dup2 failed: " << strerror(errno) << endl; bincDebug.flush(); exit(111); } if (session.unparsedArgs[0] != nullptr) { execvp(session.unparsedArgs[0], &session.unparsedArgs[0]); bincDebug << "bincimap-up: pid " << pid << " authenticate(), [auth module] invocation of " << session.unparsedArgs[0] << " failed: " << strerror(errno) << endl; bincDebug.flush(); exit(111); } bincLog << "bincimap-up: pid " << pid << " missing mandatory -- in argument list," " after bincimap-up + arguments, before authenticator." " Please check your run scripts and the man page bincimap(1) for" " more on how to invoke Binc IMAP." << endl; bincDebug.flush(); exit(111); } close(authintercom[0]); // create the string of data to be passed to the checkpassword stub int dataSize = username.length() + password.length() + challenge.length() + timestamp.length(); dataSize += 4; char *checkpasswordData = new char[dataSize]; char *cpTmp = checkpasswordData; strcpy(cpTmp, username.c_str()); cpTmp += username.length(); *cpTmp++ = '\0'; strcpy(cpTmp, password.c_str()); cpTmp += password.length(); *cpTmp++ = '\0'; // add challenge strcpy(cpTmp, challenge.c_str()); cpTmp += challenge.length(); *cpTmp++ = '\0'; strcpy(cpTmp, timestamp.c_str()); cpTmp += timestamp.length(); *cpTmp++ = '\0'; bincDebug << "bincimap-up: pid " << pid << " authenticate(), writing username/password to " << session.unparsedArgs[0] << endl; // write the userid signal(SIGPIPE, SIG_IGN); int res = write(authintercom[1], checkpasswordData, dataSize); delete[] checkpasswordData; if (res != dataSize) { bincWarning << "bincimap-up: pid " << pid << " error writing to authenticator " << session.unparsedArgs[0] << ": " << strerror(errno) << endl; return 1; } // close the write channel. this is necessary for the checkpassword // module to see an EOF. close(authintercom[1]); close(intercomr[1]); close(intercomw[0]); fd_set rmask; FD_ZERO(&rmask); FD_SET(fileno(stdin), &rmask); FD_SET(intercomr[0], &rmask); int maxfd = intercomr[0]; bool disconnected = false; bool timedout = false; bincClient.clearFlags(IODevice::HasInputLimit); bool eof = false; while (!eof) { fd_set rtmp = rmask; struct timeval timeout; // time out 5 minutes after the idle timeout. we expect the main // server to time out at the right time, but will shut down at // T+5m in case of a server lockup. timeout.tv_sec = IDLE_TIMEOUT + AUTH_PENALTY * AUTH_TIMEOUT; timeout.tv_usec = 0; // select sometimes returns when we attach to the process with // tracing tools such as ktrace and strace, setting errno to // EINTR. int n; do { n = select(maxfd + 1, &rtmp, nullptr, nullptr, &timeout); } while (n < 0 && errno == EINTR); if (n < 0) { bincWarning << "bincimpa-up: pid " << pid << " error: invalid exit from select, " << strerror(errno) << endl; break; } if (n == 0) { bincLog << "bincimap-up: pid " << pid << " server timed out after " << IDLE_TIMEOUT << " seconds" << endl; timedout = true; break; } if (FD_ISSET(fileno(stdin), &rtmp)) { authenticated = true; do { string data; int ret = bincClient.readStr(&data); if (ret == 0 || ret == -1) { session.setLastError("client disconnected"); eof = true; disconnected = true; break; } // Fall through. Triggered when there was no data // to read, even though no error has occurred if (ret == -2) continue; int w; do { w = write(intercomw[1], data.c_str(), data.length()); } while (w < 0 && errno == EINTR); if (w > 0) Session::getInstance().addReadBytes(w); if (w < 0) { bincDebug << "bincimap-up: pid " << pid << " error writing to server: " << strerror(errno) << endl; eof = true; } } while (bincClient.canRead()); } if (FD_ISSET(intercomr[0], &rtmp)) { char buf[8192]; int ret = read(intercomr[0], buf, sizeof(buf)); if (ret == 0) { // Main server has shut down eof = true; break; } else if (ret == -1) { bincDebug << "bincimap-up: pid " << pid << " error reading from server: " << strerror(errno) << endl; eof = true; break; } else { // umask(0); Session::getInstance().addWriteBytes(ret); bincClient << string(buf, ret); bincClient.flush(); } } } close(intercomr[0]); close(intercomw[1]); // catch the dead baby if (waitpid(childspid, &result, 0) != childspid) { bincLog << "bincimap-up: pid " << pid << " <" << username << "> authentication failed: " << (authenticated ? "server " : session.unparsedArgs[0]) << " waitpid returned unexpected value" << endl; string tmp = strerror(errno); return -1; } // if the server died because we closed the sockets after a timeout, exit 3. if (timedout) return 3; if (disconnected) return 0; if (WIFSIGNALED(result)) { bincLog << "bincimap-up: pid " << pid << " <" << username << "> authentication failed: " << (authenticated ? "server" : session.unparsedArgs[0]) << " died by signal " << WTERMSIG(result) << endl; sleep(AUTH_PENALTY); session.setState(Session::LOGOUT); return -1; } bincDebug << "bincimap-up: pid " << pid << " authenticate() ," << (authenticated ? "authenticator" : "server") << " exited with code " << WEXITSTATUS(result) << endl; switch (WEXITSTATUS(result)) { case 0: break; case 1: // authentication failed - sleep bincLog << "bincimap-up: pid " << pid << " <" << username << "> failed to log in" << endl; sleep(AUTH_PENALTY); return 2; case 2: case 111: // wrong call or missing auth data // abused bincLog << "bincimap-up: pid " << pid << " <" << username << "> authentication failed: " << (authenticated ? "authenticator" : "server") << " reports wrong usage" << endl; sleep(AUTH_PENALTY); return 2; default: // internal error -- or authenticator fooled us bincLog << "bincimap-up: pid " << pid << " <" << username << "> authentication failed: " << (authenticated ? "authenticator" : "server") << " returned " << WEXITSTATUS(result) << endl; return -1; } return 0; }