fix possible crash on user deletion
[srvx.git] / src / helpfile.c
index 2529408e72a7cde2b1e694baaf9f3dee6bea6bf4..aaf0d80ab961eed5048225f3d241f536ff4a106b 100644 (file)
@@ -23,6 +23,7 @@
 #include "log.h"
 #include "modcmd.h"
 #include "nickserv.h"
+#include "spamserv.h"
 
 #if defined(HAVE_DIRENT_H)
 #include <dirent.h>
@@ -38,10 +39,10 @@ static const struct message_entry msgtab[] = {
     { NULL, NULL }
 };
 
-#define DEFAULT_LINE_SIZE      MAX_LINE_SIZE
+#define DEFAULT_LINE_SIZE       MAX_LINE_SIZE
 #define DEFAULT_TABLE_SIZE      80
 
-extern struct userNode *global, *chanserv, *opserv, *nickserv;
+extern struct userNode *global, *chanserv, *opserv, *nickserv, *spamserv;
 struct userNode *message_dest;
 struct userNode *message_source;
 struct language *lang_C;
@@ -84,7 +85,7 @@ static struct language *language_alloc(const char *name)
  * lang is a two-letter code according to ISO-639-1 (or three-letter
  * code according to ISO-639-2 for languages not in ISO-639-1), and
  * COUNTRY is the ISO 3166 country code in all upper case.
- * 
+ *
  * See also:
  * http://www.loc.gov/standards/iso639-2/
  * http://www.loc.gov/standards/iso639-2/langhome.html
@@ -171,8 +172,10 @@ static struct language *language_read(const char *name)
 
     /* Open the directory stream; if we can't, fail. */
     snprintf(filename, sizeof(filename), "languages/%s", name);
-    if (!(dir = opendir(filename)))
+    if (!(dir = opendir(filename))) {
+        log_module(MAIN_LOG, LOG_ERROR, "Unable to open language directory languages/%s: %s", name, strerror(errno));
         return NULL;
+    }
     if (!(lang = dict_find(languages, name, NULL)))
         lang = language_alloc(name);
 
@@ -203,7 +206,7 @@ static struct language *language_read(const char *name)
     /* Read all the translations from the directory. */
     while ((dirent = readdir(dir))) {
         snprintf(filename, sizeof(filename), "languages/%s/%s", name, dirent->d_name);
-        if (!strcmp(dirent->d_name,"parent")) {
+        if (!strcmp(dirent->d_name, "parent")) {
             continue;
         } else if (!strcmp(dirent->d_name, "strings.db")) {
             dict = parse_database(filename);
@@ -222,29 +225,30 @@ static struct language *language_read(const char *name)
 
 static void language_read_list(void)
 {
+    struct stat sbuf;
     struct dirent *dirent;
     DIR *dir;
+    char namebuf[MAXLEN];
 
     if (!(dir = opendir("languages")))
         return;
     while ((dirent = readdir(dir))) {
         if (dirent->d_name[0] == '.')
             continue;
-#ifdef HAVE_DIRENT_D_TYPE
-        if (dirent->d_type != DT_DIR)
+        snprintf(namebuf, sizeof(namebuf), "languages/%s", dirent->d_name);
+        if (!strcmp(dirent->d_name, "strings.db")) {
             continue;
-#else
-        {
-            char namebuf[MAXLEN];
-            struct stat sbuf;
-            snprintf(namebuf, sizeof(namebuf), "languages/%s", dirent->d_name);
-            if (stat(namebuf, &sbuf) < 0)
-                continue;
-            if (!S_ISDIR(sbuf.st_mode))
-                continue;
         }
-#endif
-        language_alloc(dirent->d_name);
+        if (stat(namebuf, &sbuf) < 0) {
+            log_module(MAIN_LOG, LOG_INFO, "Skipping language entry '%s' (unable to stat).", dirent->d_name);
+            continue;
+        }
+        if (!S_ISDIR(sbuf.st_mode)) {
+            log_module(MAIN_LOG, LOG_INFO, "Skipping language entry '%s' (not directory).", dirent->d_name);
+            continue;
+        }
+        if (!dict_find(languages, dirent->d_name, NULL))
+            language_alloc(dirent->d_name);
     }
     closedir(dir);
 }
@@ -390,7 +394,7 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
     void (*irc_send)(struct userNode *from, const char *to, const char *msg);
     static struct string_buffer input;
     unsigned int size, ipos, pos, length, chars_sent, use_color;
-    unsigned int expand_pos, expand_ipos, newline_ipos;
+    unsigned int expand_ipos, newline_ipos;
     char line[MAX_LINE_SIZE];
 
     if (IsChannelName(dest) || *dest == '$') {
@@ -416,8 +420,8 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
 
     /* figure out how to send the messages */
     if (handle) {
-       msg_type |= (HANDLE_FLAGGED(handle, USE_PRIVMSG) ? 1 : 0);
-       use_color = HANDLE_FLAGGED(handle, MIRC_COLOR);
+        msg_type |= (HANDLE_FLAGGED(handle, USE_PRIVMSG) ? 1 : 0);
+        use_color = HANDLE_FLAGGED(handle, MIRC_COLOR);
         size = handle->screen_width;
         if (size > sizeof(line))
             size = sizeof(line);
@@ -425,7 +429,7 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
         size = sizeof(line);
         use_color = 1;
     }
-    if (!size)
+    if (!size || !(msg_type & MSG_TYPE_MULTILINE))
         size = DEFAULT_LINE_SIZE;
     switch (msg_type & 3) {
         case 0:
@@ -444,23 +448,23 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
      * that requires a very big intermediate buffer.
      */
     expand_ipos = newline_ipos = ipos = 0;
-    expand_pos = pos = 0;
+    pos = 0;
     chars_sent = 0;
     while (input.list[ipos]) {
-       char ch, *value, *free_value;
+        char ch, *value, *free_value;
 
         while ((ch = input.list[ipos]) && (ch != '$') && (ch != '\n') && (pos < size)) {
-           line[pos++] = ch;
+            line[pos++] = ch;
             ipos++;
-       }
+        }
 
-       if (!input.list[ipos])
+        if (!input.list[ipos])
             goto send_line;
         if (input.list[ipos] == '\n') {
             ipos++;
             goto send_line;
         }
-       if (pos == size) {
+        if (pos == size) {
             unsigned int new_ipos;
             /* Scan backwards for a space in the input, until we hit
              * either the last newline or the last variable expansion.
@@ -485,53 +489,65 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
             ipos = new_ipos;
             while (input.list[ipos] == ' ')
                 ipos++;
-           goto send_line;
-       }
+            goto send_line;
+        }
 
         free_value = 0;
-       switch (input.list[++ipos]) {
+        switch (input.list[++ipos]) {
         /* Literal '$' or end of string. */
-       case 0:
-           ipos--;
-       case '$':
-           value = "$";
-           break;
-       /* The following two expand to mIRC color codes if enabled
-          by the user. */
-       case 'b':
-           value = use_color ? "\002" : "";
-           break;
-       case 'o':
-           value = use_color ? "\017" : "";
-           break;
+        case 0:
+            ipos--;
+        case '$':
+            value = "$";
+            break;
+        /* The following two expand to mIRC color codes if enabled
+           by the user. */
+        case 'b':
+            value = use_color ? "\002" : "";
+            break;
+        case 'o':
+            value = use_color ? "\017" : "";
+            break;
         case 'r':
             value = use_color ? "\026" : "";
             break;
-       case 'u':
-           value = use_color ? "\037" : "";
-           break;
-       /* Service nicks. */
+        case 'u':
+            value = use_color ? "\037" : "";
+            break;
+        /* Service nicks. */
         case 'S':
             value = src->nick;
             break;
-       case 'G':
-           value = global ? global->nick : "Global";
-           break;
-       case 'C':
-           value = chanserv ? chanserv->nick : "ChanServ";
-           break;
-       case 'O':
-           value = opserv ? opserv->nick : "OpServ";
-           break;
-       case 'N':
+        case 'G':
+            value = global ? global->nick : "Global";
+            break;
+        case 'C':
+            value = chanserv ? chanserv->nick : "ChanServ";
+            break;
+        case 'O':
+            value = opserv ? opserv->nick : "OpServ";
+            break;
+        case 'N':
             value = nickserv ? nickserv->nick : "NickServ";
             break;
+        case 'X':
+            value = spamserv ? spamserv->nick : "SpamServ";
+            break;
         case 's':
             value = self->name;
             break;
-       case 'H':
-           value = handle ? handle->handle : "Account";
-           break;
+        case 'A':
+            value = handle ? handle->handle : "Account";
+            break;
+        case 'U':
+            value = message_dest ? message_dest->nick : "Nick";
+            break;
+        case 'I':
+            value = message_dest ? (IsFakeIdent(message_dest) ? message_dest->fakeident : message_dest->ident) : "Ident";
+            break;
+        case 'H':
+            value = message_dest ? (IsFakeHost(message_dest) ? message_dest->fakehost : message_dest->hostname) : "Hostname";
+            break;
 #define SEND_LINE(TRUNCED) do { \
     line[pos] = 0; \
     if (pos > 0) { \
@@ -544,9 +560,9 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
     newline_ipos = ipos; \
     if (!(msg_type & MSG_TYPE_MULTILINE)) return chars_sent; \
 } while (0)
-       /* Custom expansion handled by helpfile-specific function. */
-       case '{':
-       case '(': {
+        /* Custom expansion handled by helpfile-specific function. */
+        case '{':
+        case '(': {
             struct helpfile_expansion exp;
             char *name_end = input.list + ipos + 1, *colon = NULL;
 
@@ -586,20 +602,20 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
                 break;
             default:
                 value = "";
-                log_module(MAIN_LOG, LOG_ERROR, "Invalid exp.type %d from expansion function %p.", exp.type, expand_f);
+                log_module(MAIN_LOG, LOG_ERROR, "Invalid exp.type %d from expansion function %p.", exp.type, (void*)expand_f);
                 break;
             }
             ipos = name_end - input.list;
             break;
         }
-       default:
+        default:
         fallthrough:
             value = alloca(3);
             value[0] = '$';
             value[1] = input.list[ipos];
             value[2] = 0;
-       }
-       ipos++;
+        }
+        ipos++;
         while ((pos + strlen(value) > size) || strchr(value, '\n')) {
             unsigned int avail;
             avail = size - pos - 1;
@@ -636,17 +652,15 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
             }
         }
         length = strlen(value);
-       memcpy(line + pos, value, length);
+        memcpy(line + pos, value, length);
         if (free_value)
             free(free_value);
-       pos += length;
+        pos += length;
         if ((pos < size-1) && input.list[ipos]) {
-            expand_pos = pos;
             expand_ipos = ipos;
             continue;
         }
       send_line:
-        expand_pos = pos;
         expand_ipos = ipos;
         SEND_LINE(0);
 #undef SEND_LINE
@@ -660,7 +674,7 @@ send_message(struct userNode *dest, struct userNode *src, const char *format, ..
     int res;
     va_list ap;
 
-    if (IsLocal(dest)) return 0;
+    if (IsLocal(dest) && !IsDummy(dest)) return 0;
     va_start(ap, format);
     res = vsend_message(dest->nick, src, dest->handle_info, 0, NULL, format, ap);
     va_end(ap);
@@ -672,7 +686,7 @@ send_message_type(int msg_type, struct userNode *dest, struct userNode *src, con
     int res;
     va_list ap;
 
-    if (IsLocal(dest)) return 0;
+    if (IsLocal(dest) && !IsDummy(dest)) return 0;
     va_start(ap, format);
     res = vsend_message(dest->nick, src, dest->handle_info, msg_type, NULL, format, ap);
     va_end(ap);
@@ -730,7 +744,7 @@ send_help(struct userNode *dest, struct userNode *src, struct helpfile *hf, cons
     if (!rec)
         return send_message(dest, src, "MSG_TOPIC_UNKNOWN");
     if (rec->type != RECDB_QSTRING)
-       return send_message(dest, src, "HFMSG_HELP_NOT_STRING");
+        return send_message(dest, src, "HFMSG_HELP_NOT_STRING");
     return _send_help(dest, src, hf->expand, rec->d.qstring);
 }
 
@@ -806,7 +820,7 @@ helpfile_eval_identifier(const char *start, const char **end)
         *end = start + 5;
         return 0;
     } else {
-        log_module(MAIN_LOG, LOG_FATAL, "Unexpected helpfile identifier '%.*s'.", *end-start, start);
+        log_module(MAIN_LOG, LOG_FATAL, "Unexpected helpfile identifier '%.*s'.", (int)(*end-start), start);
         return -1;
     }
 }
@@ -838,7 +852,7 @@ helpfile_eval_atomicexpr(const char *start, const char **end)
     while (isspace(*sep) && (sep < *end))
         sep++;
     if ((sep == *end) || (sep[0] != ')')) {
-        log_module(MAIN_LOG, LOG_FATAL, "Expected close parenthesis at '%.*s'.", *end-sep, sep);
+        log_module(MAIN_LOG, LOG_FATAL, "Expected close parenthesis at '%.*s'.", (int)(*end-sep), sep);
         return -1;
     }
 
@@ -875,7 +889,7 @@ helpfile_eval_expr(const char *start, const char **end)
         sep += len;
     }
     if (op == OP_INVALID) {
-        log_module(MAIN_LOG, LOG_FATAL, "Unrecognized helpfile operator at '%.*s'.", *end-sep, sep);
+        log_module(MAIN_LOG, LOG_FATAL, "Unrecognized helpfile operator at '%.*s'.", (int)(*end-sep), sep);
         return -1;
     }
 
@@ -889,7 +903,7 @@ helpfile_eval_expr(const char *start, const char **end)
     while (isspace(*sep2) && (sep2 < *end))
         sep2++;
     if (sep2 != *end) {
-        log_module(MAIN_LOG, LOG_FATAL, "Trailing garbage in helpfile expression: '%.*s'.", *end-sep2, sep2);
+        log_module(MAIN_LOG, LOG_FATAL, "Trailing garbage in helpfile expression: '%.*s'.", (int)(*end-sep2), sep2);
         return -1;
     }
 
@@ -916,7 +930,7 @@ helpfile_eval_condition(const char *start, const char **end)
     for (term = start; isalnum(*term) && (term < *end); ++term) ;
     if (term != start) {
         if ((term + 2 >= *end) || (term[0] != ':') || (term[1] != ' ')) {
-            log_module(MAIN_LOG, LOG_FATAL, "In helpfile condition '%.*s' expected prefix to end with ': '.", *end-start, start);
+            log_module(MAIN_LOG, LOG_FATAL, "In helpfile condition '%.*s' expected prefix to end with ': '.", (int)(*end-start), start);
             return -1;
         }
         start = term + 2;
@@ -934,25 +948,25 @@ unlistify_help(const char *key, void *data, void *extra)
 
     switch (rd->type) {
     case RECDB_QSTRING:
-       dict_insert(newdb, strdup(key), alloc_record_data_qstring(GET_RECORD_QSTRING(rd)));
-       return 0;
+        dict_insert(newdb, strdup(key), alloc_record_data_qstring(GET_RECORD_QSTRING(rd)));
+        return 0;
     case RECDB_STRING_LIST: {
-       struct string_list *slist = GET_RECORD_STRING_LIST(rd);
-       char *dest;
-       unsigned int totlen, len, i;
-
-       for (i=totlen=0; i<slist->used; i++)
-           totlen = totlen + strlen(slist->list[i]) + 1;
-       dest = alloca(totlen+1);
-       for (i=totlen=0; i<slist->used; i++) {
-           len = strlen(slist->list[i]);
-           memcpy(dest+totlen, slist->list[i], len);
-           dest[totlen+len] = '\n';
-           totlen = totlen + len + 1;
-       }
-       dest[totlen] = 0;
-       dict_insert(newdb, strdup(key), alloc_record_data_qstring(dest));
-       return 0;
+        struct string_list *slist = GET_RECORD_STRING_LIST(rd);
+        char *dest;
+        unsigned int totlen, len, i;
+
+        for (i=totlen=0; i<slist->used; i++)
+            totlen = totlen + strlen(slist->list[i]) + 1;
+        dest = alloca(totlen+1);
+        for (i=totlen=0; i<slist->used; i++) {
+            len = strlen(slist->list[i]);
+            memcpy(dest+totlen, slist->list[i], len);
+            dest[totlen+len] = '\n';
+            totlen = totlen + len + 1;
+        }
+        dest[totlen] = 0;
+        dict_insert(newdb, strdup(key), alloc_record_data_qstring(dest));
+        return 0;
     }
     case RECDB_OBJECT: {
         dict_iterator_t it;
@@ -982,7 +996,7 @@ unlistify_help(const char *key, void *data, void *extra)
         return 0;
     }
     default:
-       return 1;
+        return 1;
     }
 }
 
@@ -1003,8 +1017,8 @@ open_helpfile(const char *fname, expand_func_t expand)
         dict_insert(language_find("C")->helpfiles, hf->name, hf);
     }
     if (db) {
-       dict_foreach(db, unlistify_help, hf->db);
-       free_database(db);
+        dict_foreach(db, unlistify_help, hf->db);
+        free_database(db);
     }
     return hf;
 }
@@ -1034,10 +1048,27 @@ void helpfile_init(void)
     language_read_list();
 }
 
-void helpfile_finalize(void)
+static void helpfile_read_languages(void)
 {
     dict_iterator_t it;
+    dict_t dict;
+
+    language_read_list();
     for (it = dict_first(languages); it; it = iter_next(it))
         language_read(iter_key(it));
+
+    /* If the user has a strings.db in their languages directory,
+     * allow that to override C language strings.
+     */
+    dict = parse_database("languages/strings.db");
+    if (dict) {
+        language_set_messages(lang_C, dict);
+        free_database(dict);
+    }
+}
+
+void helpfile_finalize(void)
+{
+    conf_register_reload(helpfile_read_languages);
     reg_exit_func(language_cleanup);
 }