summaryrefslogtreecommitdiff
path: root/config.c
blob: 2ee47f55e8f98f5a8296a2b198833677e1251a78 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
/*
 * This file is part of blabouncer (https://www.blatech.co.uk/l_bratch/blabouncer).
 * Copyright (C) 2019 Luke Bratch <luke@bratch.co.uk>.
 *
 * Blabouncer 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, version 3.
 *
 * Blabouncer 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 blabouncer. If not, see <http://www.gnu.org/licenses/>.
*/

#include "config.h"

// Sets 'dest' to the value of the configuration option with name
// 'confname' from configuration file 'filename'.
// Returns 1 for success or 0 for error/failure.
int getconfstr(char *confname, char *filename, char* dest) {
  FILE *fp;
  char str[MAXCHAR];
  int found = 0; // Have we found the configuration option?

  // Set string to zero-length to begin
  dest[0] = '\0';

  // Length of requested configuration option name
  long int namelen = strlen(confname);

  fp = fopen(filename, "r");

  if (fp == NULL) {
    printf("error: could not open configuration file '%s'.\n", filename);
    debugprint(DEBUG_CRIT, "error: could not open configuration file '%s'.\n", filename);
    exit(1);
  }
  // Loop through the whole file, looking for the requested configuration option
  while (fgets(str, MAXCHAR, fp) != NULL) {
    char substr[MAXCHAR];

    // Check if the next character after the length of the requested option
    // name is an equals sign, a space, or a tab
    if (str[namelen] != '=' && str[namelen] != ' ' && str[namelen] != '\t') {
      // If it isn't this can't have been our option
      continue;
    }

    // Copy the number of characters that the requested option name is long
    // to a temporary string
    strncpy(substr, str, namelen);
    substr[namelen] = '\0';

    // If the resulting temporary string contains the requested option name,
    // we have found our configuration option and it is in the current 'str'
    if (strstr(substr, confname)) {
      found = 1;
      break;
    }
  }

  // If we got here, then either we found the configuration option or we ran out of file

  if (!found) {
    debugprint(DEBUG_SOME, "Error reading configuration option '%s', did not find it in configuration file '%s'.\n", confname, filename);
    fclose(fp);
    return 0;
  }

  long int pos = 0;
  char conf[MAXCHAR]; // Temporary string to build configuration value in

  // Starting from the end of the option name, find the position of the start of the configuration value
  // (including its double quotes) by skipping over everything that isn't an equals sign, a space, or a tab
  for (size_t i = namelen; i < strlen(str); i++) {
    if (str[i] == '=' || str[i] == ' ' || str[i] == '\t') {
      continue;
    } else {
      // Record current/final position in string
      pos = i;
      break;
    }
  }

  strncpy(conf, str + pos, strlen(str) - pos - 1); // Copy remainder to new string and lop off the newline
  conf[strlen(str) - pos - 1] = '\0'; // Null terminate

   // Check for start and end quotes
  if (conf[0] != '"' || conf[strlen(conf) - 1] != '"') {
    printf("Error reading configuration option '%s', not enclosed in double quotes in configuration file '%s'!\n", confname, filename);
    debugprint(DEBUG_CRIT, "Error reading configuration option '%s', not enclosed in double quotes in configuration file '%s'!\n", confname, filename);
    exit(1);
  }

  strncpy(dest, conf + 1, strlen(conf) - 2); // Copy result to destination string without quotes
  dest[strlen(conf) - 2] = '\0'; // Null terminate

  debugprint(DEBUG_FULL, "getconfstr(): returning '%s'.\n", dest);

  // Close file and return success
  fclose(fp);
  return 1;
}

// Populates 'dest' with the values of the configuration array option
// with name 'confname' from configuration file 'filename'.
// Returns 1 on success, 0 on reading no values, or -1 on an error.
// On error, dest[0] is set to the error string for later retrieval.
int getconfarr(char *confname, char *filename, char dest[MAXCONFARR][MAXDATASIZE]) {
  debugprint(DEBUG_FULL, "getconfarr(): '%s', '%s' and a dest array.\n", confname, filename);

  FILE *fp;
  char line[MAXCHAR];
  int found = 0; // Have we found the configuration option?
  int valuecount = 0; // Which element in the configuration array we are on

  // Set strings to zero-length to begin
  for (int i = 0; i < MAXCONFARR; i++) {
    dest[i][0] = '\0';
  }

  // Length of requested configuration array name
  long int namelen = strlen(confname);

  fp = fopen(filename, "r");

  if (fp == NULL) {
    debugprint(DEBUG_CRIT, "error: could not open configuration file '%s'.\n", filename);
    exit(1);
  }
  // Loop through the whole file, looking for the requested configuration array
  while (fgets(line, MAXCHAR, fp) != NULL) {
    // Don't bother with any of this if this line is a comment
    int comment = 0;
    for (size_t i = 0; i < strlen(line); i++) {
      // Ignore spaces/tabs
      if (line[i] == ' ' || line[i] == '\t') {
        continue;
      } else if (line[i] == '#') {
        // If it's a comment, ignore the line
        comment = 1;
        break;
      } else {
        // Found something else before a comment, so carry on
        break;
      }
    }

    if (comment) {
      continue;
    }

    // If we haven't found our array yet, try to find the opening line
    if (!found) {
      char substr[MAXCHAR];

      // Check if the next character after the length of the requested array
      // name is an equals sign, a space, or a tab
      if (line[namelen] != '=' && line[namelen] != ' ' && line[namelen] != '\t') {
        // If it isn't this can't have been our array
        continue;
      }

      // Copy the number of characters that the requested array name is long
      // to a temporary string
      strncpy(substr, line, namelen);
      substr[namelen] = '\0';

      // If the resulting temporary string contains the requested array name,
      // we have found our configuration array
      if (strstr(substr, confname)) {
        // Make sure it is a valid start of array line
        for (size_t i = namelen; i < strlen(line); i++) {
          if (line[i] == ' ' || line[i] == '\t' || line[i] == '=') {
            // Ignore spaces, tabs and equals signs
            continue;
          } else if (line[i] == '{') {
            // Success, found an opening brace
            // Ignore anything else on this line
            found = 1;
            break;
          } else {
            // Unexpected character found, return failure
            snprintf(dest[0], MAXDATASIZE, "Unexpected character '%c' found on configuration array opening line for '%s'.\n", line[i], confname);
            fclose(fp);
            return -1;
          }
        }
      }
    // If we have found our array, extract the value from each line in it
    } else {
      int valuelen = 0;
      int inquotes = 0;
      for (size_t i = 0; i < strlen(line); i++) {
        // If we've on the closing brace line, then we're done
        if (line[i] == '}') {
          for (int i = 0; i < valuecount; i++) {
            debugprint(DEBUG_FULL, "getconfarr(): returning '%s'.\n", dest[i]);
          }

          // Close file and return success (or 0 if no values found in an otherwise valid array)
          fclose(fp);
          if (valuecount) {
            return 1;
          } else {
            return 0;
          }
        }

        // If not in the quotes yet
        if (!inquotes) {
          // Skip over initial spaces and tabs
          if (line[i] == ' ' || line[i] == '\t') {
            continue;
          } else if (line[i] == '"') {
            // Quotes found, we're now reading the value
            inquotes = 1;
            continue;
          } else {
            // Unexpected character found, return failure
            snprintf(dest[0], MAXDATASIZE, "Unexpected character '%c' found before opening quotes on array line for '%s'.\n", line[i], confname);
            fclose(fp);
            return -1;
          }
          // If inside the quotes (so, we've got to the actual value)
        } else if (inquotes) {
          // If we're on the last character and it isn't a closing quote, something is wrong
          if (i == strlen(line) - 1 && line[i] != '"') {
            snprintf(dest[0], MAXDATASIZE, "Reached end of line without finding closing quotes on array line for '%s'.\n", confname);
            fclose(fp);
            return -1;
          }

          // If we've found too many values, return an error
          if (valuecount > MAXCONFARR - 1) {
            snprintf(dest[0], MAXDATASIZE, "Too many elements defined for configuration array '%s', maximum number is '%d'.\n", confname, MAXCONFARR);
            fclose(fp);
            return -1;
          }

          // Otherwise, copy everything that isn't the closing quotes to the current element in the dest array
          if (line[i] != '"') {
            dest[valuecount][valuelen] = line[i];
            valuelen++;
            continue;
          } else {
            // When we find the closing quotes, the value is read, so terminate the dest array element string
            dest[valuecount][valuelen] = '\0';
            // We're done with this value, ignore anything that may be after the closing quotes on this line
            valuecount++;
            break;
          }
        }
      }
    }
  }

  // If we get this far, then something went wrong
  snprintf(dest[0], MAXDATASIZE, "getconfarr(): didn't find any configuration array for '%s'.\n", confname);
  fclose(fp);
  return 0;
}

// Returns the value of the configuration option with name
// 'confname' from configuration file 'filename'.
// Sets errno to 0 on success, or ECONFINT if it fails, in which case the return value is undefined.
int getconfint(char *confname, char *filename) {
  errno = 0;
  char result[MAXCHAR];
  if (!getconfstr(confname, filename, result)) {
    debugprint(DEBUG_CRIT, "getconfint(): error getting configuration option '%s' from configuration file '%s'.\n", confname, filename);
    errno = ECONFINT;
  }

  return strtol(result, NULL, 10); // Convert resulting string to an integer, base 10
}

// Create the default configuration file.
// Return 1 on success, 0 on failure.
int createconfigfile(char *filename) {
  char *dirtmp;
  char *dir;
  dirtmp = strdup(filename);
  dir = strdup(dirname(dirtmp));

  // Make sure the parent directory exists
  struct stat st = {0};
  if (stat(dir, &st) == -1) {
    if (mkdir(dir, 0700)) {
      printf("Error creating config directory '%s'.\n", dir);
      debugprint(DEBUG_CRIT, "Error creating config directory '%s'.\n", dir);
      exit(1);
    } else {
      printf("Created config directory '%s'.\n", dir);
    }
  }

  FILE *fp;

  fp = fopen(filename, "a");

  if (fp == NULL) {
    printf("error: could not open default configuration file '%s' for writing.\n", filename);
    debugprint(DEBUG_CRIT, "error: could not open default configuration file '%s' for writing.\n", filename);
    exit(1);
  }

  // Prepare the string
  char *string =
  "# blabouncer configuration file\n"
  "#\n"
  "# Normal entries must be in the form:\n"
  "# option name, space, equals sign, space, double quote, option value, double quote\n"
  "# e.g.\n"
  "# realname = \"Mr Bla Bouncer\"\n"
  "#\n"
  "# Array entries must be in the form:\n"
  "# option name, space, equals sign, space, open brace\n"
  "# (optional indentation,) double quote, element value, double quoute\n"
  "# (optional multiple values to be repeated after the first one(s))\n"
  "# close brace\n"
  "# e.g.\n"
  "# connectcommands = {\n"
  "#   \"PRIVMSG NickServ IDENTIFY bananas\"\n"
  "#   \"PRIVMSG myfriend I'm online!\"\n"
  "# }\n"
  "#\n"
  "# Shell expansion is not supported, so do not try and specify e.g.\n"
  "# \"~/.blabouncer/\" or \"$HOME/.blabouncer/\", instead use \"/home/foo/.blabouncer\"\n"
  "#\n"
  "# Some settings can be reloaded at runtime, please refer to README for details.\n"
  "\n"
  "# Nick(s) to use when connecting - will be cycled through in order in the event of\n"
  "# a nick being in use or invalid\n"
  "nicks = {\n"
  "  \"blabounce\"\n"
  "  \"bbounce2\"\n"
  "  \"bbounce3\"\n"
  "}\n"
  "\n"
  "username = \"bounceusr\"\n"
  "realname = \"Mr Bla Bouncer\"\n"
  "\n"
  "# Channels to automatically join (defaults to none)\n"
  "# Put channel keys/passwords after channel names (separated with a space)\n"
  "channels = {\n"
  "  \"#blabouncer keyword\"\n"
  "  \"#test\"\n"
  "}\n"
  "\n"
  "# Auto replay mode upon a bouncer client connecting\n"
  "# \"none\" = Don't auto replay\n"
  "# \"time\" = Always send the last \"replayseconds\" worth of logs\n"
  "# \"lastspoke\" = All messages since your current nick last spoke\n"
  "# \"noclients\" = All messages since you last had no clients connected\n"
  "# \"lastchange\" = All messages since your last client connection/disconnection\n"
  "# \"perclient\" = All messages since the current client last disconnected (see README)\n"
  "replaymode = \"time\"\n"
  "\n"
  "# How many seconds of replay log should be sent to connecting clients if replaymode = \"time\"\n"
  "replayseconds = \"600\"\n"
  "\n"
  "# Should replay log timestamps include the date when replaying? (\"1\" for yes or \"0\" for no)\n"
  "replaydates = \"0\"\n"
  "\n"
  "# Connect password clients must provided to connect\n"
  "password = \"bananas\"\n"
  "\n"
  "# Port the bouncer should listen on\n"
  "clientport = \"1234\"\n"
  "\n"
  "# Enable TLS for clients connecting to the bouncer (\"1\" for yes or \"0\" for no)\n"
  "# If \"0\" then certfile and keyfile need not be set\n"
  "clienttls = \"1\"\n"
  "\n"
  "# Enable TLS for the bouncer connecting to the IRC server (\"1\" for yes or \"0\" for no)\n"
  "servertls = \"1\"\n"
  "\n"
  "# Real IRC server the bouncer connects to\n"
  "ircserver = \"irc.blatech.net\"\n"
  "\n"
  "# Real IRC server port\n"
  "ircserverport = \"6697\"\n"
  "\n"
  "# Real IRC server password\n"
  "#ircserverpassword = \"apples\"\n"
  "\n"
  "# Command(s) to send to the server upon completing registration (e.g. a NickServ password)\n"
  "#connectcommands = {\n"
  "#  \"PRIVMSG NickServ IDENTIFY bananas\"\n"
  "#  \"PRIVMSG myfriend I'm online!\"\n"
  "#}\n"
  "\n"
  "# Base directory (defaults to $HOME/.blabouncer/)\n"
  "# Things such as the logs directory will be placed below this\n"
  "#basedir = \"/home/foo/.blabouncer/\"\n"
  "\n"
  "# Certificate file (defaults to <basedir>/cert.pem)\n"
  "# If clienttls = \"0\" then this need not be set\n"
  "#certfile = \"/home/foo/.blabouncer/cert.pem\"\n"
  "\n"
  "# Certificate key file (defaults to <basedir>/key.pem)\n"
  "# If clienttls = \"0\" then this need not be set\n"
  "#keyfile = \"/home/foo/.blabouncer/key.pem\"\n"
  "\n"
  "# Enable logging (\"1\" for yes or \"0\" for no)\n"
  "# Logs go to basedir/logs/ with one file per channel/nick\n"
  "logging = \"1\"\n"
  "\n"
  "# Enable replay logging (\"1\" for yes or \"0\" for no)\n"
  "# Replay log goes to basedir/replay.log\n"
  "replaylogging = \"1\"\n"
  "\n"
  "# Debug verbosity (\"0\" for critical only, \"1\" for some extra info, \"2\" for full debug mode)\n"
  "# (All output goes to <basedir>/debug.txt)\n"
  "debug = \"2\"\n"
  "\n"
  "# Number of debug logs to keep\n"
  "debugkeep = \"5\"\n";

  // Write complete string to file
  if ((fprintf(fp, "%s", string)) < 0) {
    debugprint(DEBUG_CRIT, "error: could not write to replay log file.\n");
  }

  fclose(fp);
  free(dirtmp);
  free(dir);
  return 0;
}