diff --git a/include/h.h b/include/h.h index cea8ace8f..b94c69767 100644 --- a/include/h.h +++ b/include/h.h @@ -417,7 +417,7 @@ extern void del_whowas_from_clist(WhoWas **, WhoWas *); extern void add_whowas_to_list(WhoWas **, WhoWas *); extern void del_whowas_from_list(WhoWas **, WhoWas *); extern uint64_t hash_whowas_name(const char *name); -extern void create_whowas_entry(Client *client, WhoWas *e); +extern void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event); extern void free_whowas_fields(WhoWas *e); extern int add_to_client_hash_table(const char *, Client *); extern int del_from_client_hash_table(const char *, Client *); @@ -1348,6 +1348,7 @@ extern void procio_post_rehash(int failure); /* end of proc i/o */ extern int minimum_msec_since_last_run(struct timeval *tv_old, long minimum); extern long get_connected_time(Client *client); +extern time_t get_creationtime(Client *client); extern const char *StripControlCodes(const char *text); extern const char *StripControlCodesEx(const char *text, char *output, size_t outputlen, int strip_flags); extern MODVAR Module *Modules; diff --git a/include/struct.h b/include/struct.h index 8e875e72f..f4442d382 100644 --- a/include/struct.h +++ b/include/struct.h @@ -859,7 +859,9 @@ typedef struct Whowas { char *realname; char *account; long umodes; - time_t logoff; + time_t logon; + time_t logoff; + WhoWasEvent event; struct Client *online; /* Pointer to new nickname for chasing or NULL */ struct Whowas *next; /* for hash table... */ struct Whowas *prev; /* for hash table... */ diff --git a/include/whowas.h b/include/whowas.h index ceaefed40..4f4d330d4 100644 --- a/include/whowas.h +++ b/include/whowas.h @@ -35,6 +35,15 @@ #ifndef __whowas_include__ #define __whowas_include__ +/* NOTE: Don't reorder values of these, as they are used in whowasdb */ +typedef enum WhoWasEvent { + WHOWAS_EVENT_QUIT=0, + WHOWAS_EVENT_NICK_CHANGE=1, + WHOWAS_EVENT_SERVER_TERMINATING=2 +} WhoWasEvent; +#define WHOWAS_LOWEST_EVENT 0 +#define WHOWAS_HIGHEST_EVENT 2 + /* ** add_history ** Add the currently defined name of the client to history. @@ -42,7 +51,7 @@ ** Client must be a fully registered user (specifically, ** the user structure must have been allocated). */ -void add_history(Client *, int); +void add_history(Client *, int, WhoWasEvent); /* ** off_history diff --git a/src/list.c b/src/list.c index a43298444..68251c66f 100644 --- a/src/list.c +++ b/src/list.c @@ -344,7 +344,7 @@ void remove_client_from_list(Client *client) if (IsUser(client)) /* Only persons can have been added before */ { - add_history(client, 0); + add_history(client, 0, WHOWAS_EVENT_QUIT); off_history(client); /* Remove all pointers to client */ } diff --git a/src/modules/nick.c b/src/modules/nick.c index 58950c7b4..a006f4088 100644 --- a/src/modules/nick.c +++ b/src/modules/nick.c @@ -205,7 +205,7 @@ CMD_FUNC(cmd_nick_remote) new_message(client, recv_mtags, &mtags); RunHook(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick); client->lastnick = lastnick ? lastnick : TStime(); - add_history(client, 1); + add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE); sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld", client->id, nick, (long long)client->lastnick); sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick); @@ -433,8 +433,8 @@ CMD_FUNC(cmd_nick_local) new_message(client, recv_mtags, &mtags); RunHook(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick); - client->lastnick = TStime(); - add_history(client, 1); + add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE); + client->lastnick = TStime(); /* needs to be done AFTER add_history() */ sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld", client->id, nick, (long long)client->lastnick); sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick); diff --git a/src/modules/rpc/user.c b/src/modules/rpc/user.c index aa00bebfb..44ecb0a36 100644 --- a/src/modules/rpc/user.c +++ b/src/modules/rpc/user.c @@ -708,6 +708,17 @@ RPC_CALL_FUNC(rpc_user_part) extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH]; +const char *whowas_event_to_string(WhoWasEvent event) +{ + if (event == WHOWAS_EVENT_QUIT) + return "quit"; + if (event == WHOWAS_EVENT_NICK_CHANGE) + return "nick-change"; + if (event == WHOWAS_EVENT_SERVER_TERMINATING) + return "server-terminating"; + return "unknown"; +} + void json_expand_whowas(json_t *j, const char *key, WhoWas *e, int detail) { json_t *child; @@ -736,6 +747,8 @@ void json_expand_whowas(json_t *j, const char *key, WhoWas *e, int detail) if (detail < 2) return; + json_object_set_new(child, "event", json_string_unreal(whowas_event_to_string(e->event))); + json_object_set_new(child, "logon_time", json_timestamp(e->logon)); json_object_set_new(child, "logoff_time", json_timestamp(e->logoff)); /* client.user */ diff --git a/src/modules/svsnick.c b/src/modules/svsnick.c index e3cd60d90..aef0bef91 100644 --- a/src/modules/svsnick.c +++ b/src/modules/svsnick.c @@ -66,6 +66,7 @@ CMD_FUNC(cmd_svsnick) MessageTag *mtags = NULL; char nickname[NICKLEN+1]; char oldnickname[NICKLEN+1]; + time_t ts; if (!IsSvsCmdOk(client) || parc < 4 || (strlen(parv[2]) > NICKLEN)) return; /* This looks like an error anyway -Studded */ @@ -96,7 +97,7 @@ CMD_FUNC(cmd_svsnick) if (acptr != ocptr) acptr->umodes &= ~UMODE_REGNICK; - acptr->lastnick = atol(parv[3]); + ts = atol(parv[3]); /* no 'recv_mtags' here, we do not inherit from SVSNICK but generate a new NICK event */ new_message(acptr, NULL, &mtags); @@ -104,9 +105,10 @@ CMD_FUNC(cmd_svsnick) RunHook(HOOKTYPE_LOCAL_NICKCHANGE, acptr, mtags, nickname); sendto_local_common_channels(acptr, acptr, 0, mtags, ":%s NICK :%s", acptr->name, nickname); sendto_one(acptr, mtags, ":%s NICK :%s", acptr->name, nickname); - sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%lld", acptr->id, nickname, (long long)acptr->lastnick); + sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%lld", acptr->id, nickname, (long long)ts); - add_history(acptr, 1); + add_history(acptr, 1, WHOWAS_EVENT_NICK_CHANGE); + acptr->lastnick = ts; /* needs to be done AFTER add_history() */ del_from_client_hash_table(acptr->name, acptr); unreal_log(ULOG_INFO, "nick", "FORCED_NICK_CHANGE", acptr, diff --git a/src/modules/whowasdb.c b/src/modules/whowasdb.c index 874f43175..72b316818 100644 --- a/src/modules/whowasdb.c +++ b/src/modules/whowasdb.c @@ -327,7 +327,7 @@ int write_whowasdb(void) WhoWas *e = safe_alloc(sizeof(WhoWas)); int ret; - create_whowas_entry(client, e); + create_whowas_entry(client, e, WHOWAS_EVENT_SERVER_TERMINATING); ret = write_whowas_entry(db, tmpfname, e); free_whowas_fields(e); safe_free(e); @@ -364,13 +364,19 @@ int write_whowasdb(void) int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e) { + char logontime[64]; char logofftime[64]; + char event[16]; + snprintf(logontime, sizeof(logontime), "%lld", (long long)e->logon); snprintf(logofftime, sizeof(logofftime), "%lld", (long long)e->logoff); + snprintf(event, sizeof(event), "%d", e->event); W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_START)); W_SAFE_PROPERTY(db, "nick", e->name); + W_SAFE_PROPERTY(db, "logontime", logontime); W_SAFE_PROPERTY(db, "logofftime", logofftime); + W_SAFE_PROPERTY(db, "event", event); W_SAFE_PROPERTY(db, "username", e->username); W_SAFE_PROPERTY(db, "hostname", e->hostname); W_SAFE_PROPERTY(db, "ip", e->ip); @@ -393,7 +399,8 @@ int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e) safe_free(hostname); \ safe_free(ip); \ safe_free(realname); \ - logofftime = 0; \ + logontime = logofftime = 0; \ + event = 0; \ safe_free(server); \ safe_free(virthost); \ safe_free(account); \ @@ -424,7 +431,9 @@ int read_whowasdb(void) char *hostname = NULL; char *ip = NULL; char *realname = NULL; + long long logontime = 0; long long logofftime = 0; + int event = 0; char *server = NULL; char *virthost = NULL; char *account = NULL; @@ -482,7 +491,8 @@ int read_whowasdb(void) // Variables key = value = NULL; nick = username = hostname = ip = realname = virthost = account = server = NULL; - logofftime = 0; + logontime = logofftime = 0; + event = 0; R_SAFE(unrealdb_read_int32(db, &magic)); if (magic != MAGIC_WHOWASDB_START) @@ -495,26 +505,55 @@ int read_whowasdb(void) R_SAFE(unrealdb_read_str(db, &key)); R_SAFE(unrealdb_read_str(db, &value)); if (!strcmp(key, "nick")) + { nick = value; - else if (!strcmp(key, "username")) + } else + if (!strcmp(key, "username")) + { username = value; - else if (!strcmp(key, "hostname")) + } else + if (!strcmp(key, "hostname")) + { hostname = value; - else if (!strcmp(key, "ip")) + } else + if (!strcmp(key, "ip")) + { ip = value; - else if (!strcmp(key, "realname")) + } else + if (!strcmp(key, "realname")) + { realname = value; - else if (!strcmp(key, "logofftime")) + } else + if (!strcmp(key, "logontime")) + { + logontime = atoll(value); + safe_free(value); + } else + if (!strcmp(key, "logofftime")) { logofftime = atoll(value); safe_free(value); - } else if (!strcmp(key, "server")) + } else + if (!strcmp(key, "event")) + { + event = atoi(value); + if ((event < WHOWAS_LOWEST_EVENT) || (event > WHOWAS_HIGHEST_EVENT)) + event = WHOWAS_EVENT_QUIT; /* safety */ + safe_free(value); + } else + if (!strcmp(key, "server")) + { server = value; - else if (!strcmp(key, "virthost")) + } else + if (!strcmp(key, "virthost")) + { virthost = value; - else if (!strcmp(key, "account")) + } else + if (!strcmp(key, "account")) + { account = value; - else if (!strcmp(key, "end")) + } else + if (!strcmp(key, "end")) { safe_free(key); safe_free(value); @@ -546,7 +585,9 @@ int read_whowasdb(void) "[whowasdb] Adding '$nick'...", log_data_string("nick", nick)); e->hashv = hash_whowas_name(nick); + e->logon = logontime; e->logoff = logofftime; + e->event = event; safe_strdup(e->name, nick); safe_strdup(e->username, username); safe_strdup(e->hostname, hostname); diff --git a/src/user.c b/src/user.c index 681c01279..65bea1892 100644 --- a/src/user.c +++ b/src/user.c @@ -737,6 +737,25 @@ int hide_idle_time(Client *client, Client *target) } } +/** Get creation time of a client. + * @param client The client to check (user, server, anything) + * @returns the time when the client first connected to IRC, or 0 for unknown. + */ +time_t get_creationtime(Client *client) +{ + const char *str; + + /* Shortcut for local clients */ + if (client->local) + return client->local->creationtime; + + /* Otherwise, hopefully available through this... */ + str = moddata_client_get(client, "creationtime"); + if (!BadPtr(str) && (*str != '0')) + return atoll(str); + return 0; +} + /** Get how long a client is connected to IRC. * @param client The client to check * @returns how long the client is connected to IRC (number of seconds) diff --git a/src/whowas.c b/src/whowas.c index e6344710f..7e5e2e21b 100644 --- a/src/whowas.c +++ b/src/whowas.c @@ -43,15 +43,21 @@ void free_whowas_fields(WhoWas *e) safe_free(e->account); safe_free(e->ip); e->servername = NULL; + e->event = 0; + e->logon = 0; + e->logoff = 0; + /* Remove from lists and reset hashv */ if (e->online) del_whowas_from_clist(&(e->online->user->whowas), e); del_whowas_from_list(&WHOWASHASH[e->hashv], e); + e->hashv = -1; } -void create_whowas_entry(Client *client, WhoWas *e) +void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event) { e->hashv = hash_whowas_name(client->name); + e->logon = client->lastnick; e->logoff = TStime(); e->umodes = client->umodes; safe_strdup(e->name, client->name); @@ -72,9 +78,11 @@ void create_whowas_entry(Client *client, WhoWas *e) */ /* strlcpy(e->servername, client->user->server,HOSTLEN); */ e->servername = client->user->server; + + e->event = event; } -void add_history(Client *client, int online) +void add_history(Client *client, int online, WhoWasEvent event) { WhoWas *new; @@ -83,7 +91,7 @@ void add_history(Client *client, int online) if (new->hashv != -1) free_whowas_fields(new); - create_whowas_entry(client, new); + create_whowas_entry(client, new, event); if (online) {