bincimap

Log | Files | Refs | LICENSE

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 }