//
// aegis - project change supervisor
// Copyright (C) 2004-2006, 2008 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include // for umask
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct server_password_ty
{
server_ty inherited;
server_ty *simple;
};
static void
destructor(server_ty *sp)
{
server_password_ty *spp;
spp = (server_password_ty *)sp;
if (spp->simple)
{
server_delete(spp->simple);
spp->simple = 0;
}
}
static struct passwd *
check_system_password(const char *username, const char *password)
{
struct passwd *pw;
char *cp;
//
// Make sure the user exists.
//
pw = getpwnam(username);
if (!pw)
return 0;
//
// Don't even think about allowing root.
//
if (pw->pw_uid == 0)
return 0;
//
// If this systejm has shadow passwords, grab the shadow password.
//
#ifdef HAVE_GETSPNAM
{
struct spwd *spw;
spw = getspnam(username);
if (spw)
pw->pw_passwd = spw->sp_pwdp;
}
#endif
//
// Allow for dain bramaged HPUX passwd aging
// - Basically, HPUX adds a comma and some data
// about whether the passwd has expired or not
// on the end of the passwd field.
// - This code replaces the ',' with '\0'.
//
// I'm guessing that HPUX WANTED other systems to think the password
// was wrong so logins would fail if the system didn't handle expired
// passwds and the passwd might be expired.
//
cp = pw->pw_passwd;
while (*cp)
{
if (*cp == ',')
{
*cp = 0;
break;
}
++cp;
}
//
// If the user has no password, we are done.
//
if (pw->pw_passwd[0] == 0)
{
if (password[0])
return 0;
return pw;
}
//
// Check the password.
//
#if HAVE_CRYPT
if (0 != strcmp(pw->pw_passwd, crypt(password, pw->pw_passwd)))
return 0;
//
// Report success by return the user data.
//
return pw;
#else
return 0;
#endif
}
static void
run(server_ty *sp)
{
server_password_ty *spp = (server_password_ty *)sp;
//
// cvsclient.texi:
// The client connects, and sends the following:
//
// + the string BEGIN AUTH REQUEST, a linefeed,
// + the cvs root, a linefeed,
// + the username, a linefeed,
// + the password trivially encoded (see Password scrambling), a
// linefeed,
// + the string END AUTH REQUEST, and a linefeed.
//
bool verify = false;
bool ok = false;
nstring s;
if (!server_getline(sp, s))
{
protocol_failure:
server_e(sp, "authentication protocol error");
auth_failure:
server_response_queue(sp, new response_hate());
server_response_flush(sp);
return;
}
if (!strcmp(s.c_str(), "BEGIN GSSAPI REQUEST"))
{
server_error(sp, "GSSAPI authentication not supported by this server");
goto auth_failure;
}
if (!strcmp(s.c_str(), "BEGIN VERIFICATION REQUEST"))
{
ok = true;
verify = true;
}
else
ok = !strcmp(s.c_str(), "BEGIN AUTH REQUEST");
if (!ok)
goto protocol_failure;
if (!server_getline(sp, s))
goto protocol_failure;
ok = !strcmp(s.c_str(), ROOT_PATH);
if (!ok)
{
//
// cvsclient.texi:
// "The client must send the identical string for cvs root both
// here and later in the Root request of the cvs protocol itself.
// Servers are encouraged to enforce this restriction."
//
// We only allow one Root specification, exactly ROOT_PATH,
// and we check it in both places.
//
server_e(sp, "%s: no such repository", s.c_str());
goto auth_failure;
}
nstring user_name;
if (!server_getline(sp, user_name))
goto protocol_failure;
nstring scrambled_password;
if (!server_getline(sp, scrambled_password))
goto protocol_failure;
if (!server_getline(sp, s))
goto protocol_failure;
if (verify)
ok = !strcmp(s.c_str(), "END VERIFICATION REQUEST");
else
ok = !strcmp(s.c_str(), "END AUTH REQUEST");
if (!ok)
goto protocol_failure;
//
// Check that the user name and password are acceptable.
//
nstring password = descramble(scrambled_password);
struct passwd *pw =
check_system_password(user_name.c_str(), password.c_str());
if (!pw)
goto auth_failure;
//
// Report success.
//
server_response_queue(sp, new response_love());
server_response_flush(sp);
if (!verify)
{
#if HAVE_INITGROUPS
if
(
initgroups (pw->pw_name, pw->pw_gid) < 0
#ifdef EPERM
&&
//
// Note that initgroups() only works as root. But we do
// still want to report ENOMEM and whatever other errors
// initgroups() might dish up.
//
errno != EPERM
#endif
)
{
int err;
//
// This could be a warning, but I'm not sure I see the point
// in doing that instead of an error given that it would happen
// on every connection. We could log it somewhere and not tell
// the user. But at least for now make it an error.
//
err = errno;
server_e(sp, "initgroups failed: %s", strerror(err));
goto auth_failure;
}
#endif // HAVE_INITGROUPS
//
// Drop user privilege level.
//
if (setuid(pw->pw_uid) < 0)
{
int err;
err = errno;
server_e(sp, "setuid failed: %s", strerror(err));
goto auth_failure;
}
//
// Let libaegis.a know we have changed uid.
//
os_become_reinit_mortal();
//
// Set a sensable umask.
//
umask(DEFAULT_UMASK);
//
// Set some environment variables.
//
env_set("LOGNAME", user_name.c_str());
env_set("USER", user_name.c_str());
//
// Now run the simple server.
//
if (!spp->simple)
spp->simple = server_simple_new(sp->np);
server_run(spp->simple);
}
}
static server_method_ty vtbl =
{
sizeof(server_password_ty),
destructor,
run,
"password",
};
server_ty *
server_password_new(net_ty *np)
{
server_ty *sp;
server_password_ty *spp;
sp = server_new(&vtbl, np);
spp = (server_password_ty *)sp;
spp->simple = 0;
return sp;
}