psyced/world/net/server.c

279 lines
6.8 KiB
C

// $Id: server.c,v 1.54 2008/05/05 14:31:10 lynx Exp $ // vim:syntax=lpc
//
// THE GENERIC SERVER
// this code is not applicable to a protocol scheme directly
// although it's connection-oriented, base for telnet/applet/irc-schemes
// local debug messages - turn them on by using psyclpc -DDserver=<level>
#ifdef Dserver
# undef DEBUG
# define DEBUG Dserver
#endif
#include <net.h>
#include <person.h>
#include <text.h>
#include <lang.h>
#define NO_INHERIT
#include <server.h>
#undef NO_INHERIT
volatile string nick;
volatile object user;
volatile int guesses;
// just a clean-up to avoid ugly warnings when net/server and
// net/jabber/* use differing flags in input_to calls.
volatile int input_to_settings = INPUT_IGNORE_BANG;
qScheme() { return 0; }
qLayout() { return 0; }
qLang() { return DEFLANG; }
sName(ni) { nick = ni; }
// prototypes
morph();
promptForPassword();
authChecked();
void create() {
if (!clonep()) return;
sTextPath(qLayout(), qLang(), qScheme());
}
// supercede this in inheriting server class
createUser(nick) {
P3(("%O net/server:createUser(%O)\n", ME, nick))
return named_clone(NET_PATH "user", nick);
}
// returns 1 if password prompt is necessary - who needs that?
// see also morph.i
hello(ni, elm, try, method, salt) {
#ifdef LOGIN_BY_EMAIL
if (nick = legal_mailto(ni)) {
elm = 0;
ni = "mailto:"+ nick;
P0(("Login by mailto for %O\n", ni))
} else
#endif
unless(nick = legal_name(ni)) {
w("_error_illegal_name_person", 0, ([ "_nick": ni ]) );
QUIT
}
// this assumes person is not switching schemes (protocols)!
// if it does, we'll attach a port to the wrong protocol!
// apparently it never happens.
unless (user = find_person(nick)) {
// catch returns "1" here, weird!!
//if (catch (user = createUser(nick)) && !user ) {
unless (user = createUser(nick)) {
// pr("_failure_object_creation",
// "Cannot create user object.\n");
QUIT
}
}
return user -> checkPassword(try, method, salt, 0, #'authChecked,
ni, try, elm);
}
#ifdef REGISTERED_USERS_ONLY // TODO: rename into a _flag
ohYeah(whatever) {
input_to(#'ohYeah, input_to_settings);
// input ignore warning? inverting mc's is really a good idea!
w("_warning_ignored_input", "Oh yeah? ");
}
#endif
authChecked(int result, ni, try, elm) {
P3(("authChecked(%O,%O,%O,%O) in %O",
result, ni, try, elm, ME))
unless (result) {
int invalid;
// überzeugt mich nicht so richtig..
// der ip check da unten sollte vorher passieren TODO
if (try && elm) {
string *rc;
unless (user -> isNewbie()) {
w("_error_unavailable_name",
"Sorry, this name is registered to somebody else.\n");
invalid = 1;
}
else if (rc = legal_password(try, nick)) {
pr(rc[0], rc[1]);
invalid = 1;
}
else unless (elm = legal_mailto(elm)) {
w("_error_invalid_mailto",
"Sorry, that doesn't look like a valid email address to me.\n");
invalid = 1;
}
// if (user -> vQuery("email") != elm) {
// w("_error_invalid_mailto",
// "Sorry, that doesn't look like a valid email address to me.\n");
// return;
// }
}
#ifdef REGISTERED_USERS_ONLY
else {
if (user -> isNewbie()) {
#ifdef PSYC_SYNCHRONIZE
synchro_report( // _warning? doesn't get forwarded by @sync :(
"_notice_error_necessary_registration_sign_on",
"Login attempt by unregistered user [_nick].",
([ "_nick": nick ]) );
#endif
w("_error_necessary_registration",
"Sorry, you cannot use this without prior registration.");
// this funny hack intentionally breaks
// the login procedure. the connection
// stays up in the air until the time-out.
input_to(#'ohYeah, input_to_settings);
// would be better to count the login
// attempts per host however.
} else {
unless (try) return promptForPassword(user);
w("_error_invalid_password",
"Invalid password for [_nick].",
([ "_nick": nick ]) );
}
invalid = 1;
}
if (invalid) {
input_to(#'hello, input_to_settings);
return;
}
user -> set("email", elm); // TODO!?!?
user -> set("password", try);
if (ni != nick) user->vSet("longname", ni);
return morph();
#else
return promptForPassword(user);
#endif
}
#ifndef REGISTERED_USERS_ONLY
// added user->isNewbie() check for ircers
if (user->online() && user->isNewbie()
#ifdef _flag_log_hosts
&& user->vQuery("ip") != query_ip_number()
#endif
) {
pr("_error_status_person_connected",
"Sorry. %O is already connected.\n", nick);
QUIT
}
#endif
if (ni != nick) user->vSet("longname", ni);
return morph();
}
password(try, method, salt) {
mixed authCb;
unless (user) {
w("_failure_object_destructed", // never happens
"Huh? Your user object has disappeared!");
QUIT
}
// nick, guesses needed?
authCb = CLOSURE((int result), (nick, guesses),
(string nick, int guesses),
{
unless(result) {
w("_error_invalid_password",
"Invalid password for [_nick].",
([ "_nick": nick ]) );
if (++guesses > 3) { QUIT }
return promptForPassword(user);
}
return morph();
});
user -> checkPassword(try, method, salt, 0, authCb);
}
// supercede this in inheriting server class
promptForPassword() {
// the no-echo option is a negotation within telnet protocol
// therefore unsuited for most other cases
input_to(#'password, input_to_settings);
return 1;
}
morph() {
P2(("morph %O to %O\n", ME, user))
if (! keepUserObject(user)) {
user -> quit(2);
if (user) destruct(user);
unless (user = createUser(nick)) {
w("_failure_object_creation",
"Cannot create new user object.");
QUIT
}
}
if (interactive(user)) remove_interactive(user);
if (exec(user, ME)) userLogon();
else {
pr("_failure_object_switch",
"Eh! Could not switch to object %O.\n", user);
QUIT
}
destruct(ME);
return 1;
}
userLogon() {
P3(("userLogon %O to %O\n", ME, user))
return user -> logon();
}
// supercede this in inheriting server class
keepUserObject(user) {
if (qScheme()) return user->vQuery("scheme") == qScheme();
}
quit() { QUIT } // self-destruct on timeout or external request
// self-destruct when the TCP link gets lost
disconnected(remaining) {
P2(( "%O got disconnected\n", ME ))
QUIT // returns 'unexpected'
}
logon() {
if (nick) {
// authlocal support!
// only auto-login the first instance of nick
user = find_person(nick);
unless (user) {
user = createUser(nick);
morph();
return 0;
}
else unless(interactive(user)) {
morph();
return 0;
}
// otherwise prompt regularely
nick = user = 0;
}
#ifdef LIMIT_USERS
// TODO: admins MUST be able to login!
if (AMOUNT_SOCKETS >= LIMIT_USERS) {
w("_failure_exceeded_limit_users", "Extremely sorry, but "
"the maximum possible amount of people has been reached.");
QUIT
}
#endif
input_to(#'hello, input_to_settings);
call_out(#'quit, TIME_LOGIN_IDLE);
guesses = 0;
return 1;
}