| 1 | /** |
|---|
| 2 | * \file |
|---|
| 3 | * |
|---|
| 4 | * \brief Access control lists for PennMUSH. |
|---|
| 5 | * \verbatim |
|---|
| 6 | * |
|---|
| 7 | * The file access.cnf in the game directory will control all |
|---|
| 8 | * access-related directives, replacing lockout.cnf and sites.cnf |
|---|
| 9 | * |
|---|
| 10 | * The format of entries in the file will be: |
|---|
| 11 | * |
|---|
| 12 | * wild-host-name [!]option [!]option [!]option ... # comment |
|---|
| 13 | * |
|---|
| 14 | * A wild-host-name is a wildcard pattern to match hostnames with. |
|---|
| 15 | * The wildcard "*" will work like UNIX filename globbing, so |
|---|
| 16 | * *.edu will match all sites with names ending in .edu, and |
|---|
| 17 | * *.*.*.*.* will match all sites with 4 periods in their name. |
|---|
| 18 | * 128.32.*.* will match all sites starting with 128.32 (UC Berkeley). |
|---|
| 19 | * You can also use user@host to match specific users if you know that |
|---|
| 20 | * the host is running ident and you trust its responses (nontrivial). |
|---|
| 21 | * |
|---|
| 22 | * The options that can be specified are: |
|---|
| 23 | * *CONNECT Allow connections to non-guest players |
|---|
| 24 | * *GUEST Allow connection to guests |
|---|
| 25 | * *CREATE Allow player creation at login screen |
|---|
| 26 | * DEFAULT All of the above |
|---|
| 27 | * NONE None of the above |
|---|
| 28 | * SUSPECT Set all players connecting from the site suspect |
|---|
| 29 | * REGISTER Allow players to use the "register" connect command |
|---|
| 30 | * DENY_SILENT Don't log when someone's denied access from here |
|---|
| 31 | * REGEXP Treat the hostname pattern as a regular expression |
|---|
| 32 | * *GOD God can connect from this pattern. |
|---|
| 33 | * *WIZARD Wizards can connect from this pattern. |
|---|
| 34 | * *ADMIN Admins can connect from this pattern. |
|---|
| 35 | * |
|---|
| 36 | * Options that are *'d can be prefaced by a !, meaning "Don't allow". |
|---|
| 37 | * |
|---|
| 38 | * The file is parsed line-by-line in order. This makes it possible |
|---|
| 39 | * to explicitly allow only certain sites to connect and deny all others, |
|---|
| 40 | * or vice versa. Sites can only do the options that are specified |
|---|
| 41 | * in the first line they match. |
|---|
| 42 | * |
|---|
| 43 | * If a site is listed in the file with no options at all, it is |
|---|
| 44 | * disallowed from any access (treated as !CONNECT, basically) |
|---|
| 45 | * |
|---|
| 46 | * If a site doesn't match any line in the file, it is allowed any |
|---|
| 47 | * toggleable access (treated as DEFAULT) but isn't SUSPECT or REGISTER. |
|---|
| 48 | * |
|---|
| 49 | * "make access" produces access.cnf from lockout.cnf/sites.cnf |
|---|
| 50 | * |
|---|
| 51 | * @sitelock'd sites appear after the line "@sitelock" in the file |
|---|
| 52 | * Using @sitelock writes out the file. |
|---|
| 53 | * |
|---|
| 54 | * \endverbatim |
|---|
| 55 | */ |
|---|
| 56 | |
|---|
| 57 | #include "config.h" |
|---|
| 58 | #include "copyrite.h" |
|---|
| 59 | #include <stdio.h> |
|---|
| 60 | #include <stdlib.h> |
|---|
| 61 | #include <string.h> |
|---|
| 62 | #include <ctype.h> |
|---|
| 63 | #ifdef I_SYS_TYPES |
|---|
| 64 | #include <sys/types.h> |
|---|
| 65 | #endif |
|---|
| 66 | #include <fcntl.h> |
|---|
| 67 | #ifdef I_SYS_TIME |
|---|
| 68 | #include <sys/time.h> |
|---|
| 69 | #ifdef TIME_WITH_SYS_TIME |
|---|
| 70 | #include <time.h> |
|---|
| 71 | #endif |
|---|
| 72 | #else |
|---|
| 73 | #include <time.h> |
|---|
| 74 | #endif |
|---|
| 75 | #ifdef I_UNISTD |
|---|
| 76 | #include <unistd.h> |
|---|
| 77 | #endif |
|---|
| 78 | #include "conf.h" |
|---|
| 79 | #include "externs.h" |
|---|
| 80 | #include "mypcre.h" |
|---|
| 81 | #include "access.h" |
|---|
| 82 | #include "mymalloc.h" |
|---|
| 83 | #include "match.h" |
|---|
| 84 | #include "parse.h" |
|---|
| 85 | #include "log.h" |
|---|
| 86 | #include "mushdb.h" |
|---|
| 87 | #include "dbdefs.h" |
|---|
| 88 | #include "flags.h" |
|---|
| 89 | #include "confmagic.h" |
|---|
| 90 | |
|---|
| 91 | /** An access flag. */ |
|---|
| 92 | typedef struct a_acsflag acsflag; |
|---|
| 93 | /** An access flag. |
|---|
| 94 | * This structure is used to build a table of access control flags. |
|---|
| 95 | */ |
|---|
| 96 | struct a_acsflag { |
|---|
| 97 | const char *name; /**< Name of the access flag */ |
|---|
| 98 | bool toggle; /**< Is this a negatable flag? */ |
|---|
| 99 | uint32_t flag; /**< Bitmask of the flag */ |
|---|
| 100 | }; |
|---|
| 101 | static acsflag acslist[] = { |
|---|
| 102 | {"connect", 1, ACS_CONNECT}, |
|---|
| 103 | {"create", 1, ACS_CREATE}, |
|---|
| 104 | {"guest", 1, ACS_GUEST}, |
|---|
| 105 | {"default", 0, ACS_DEFAULT}, |
|---|
| 106 | {"register", 0, ACS_REGISTER}, |
|---|
| 107 | {"suspect", 0, ACS_SUSPECT}, |
|---|
| 108 | {"deny_silent", 0, ACS_DENY_SILENT}, |
|---|
| 109 | {"regexp", 0, ACS_REGEXP}, |
|---|
| 110 | {"god", 1, ACS_GOD}, |
|---|
| 111 | {"wizard", 1, ACS_WIZARD}, |
|---|
| 112 | {"admin", 1, ACS_ADMIN}, |
|---|
| 113 | {NULL, 0, 0} |
|---|
| 114 | }; |
|---|
| 115 | |
|---|
| 116 | static struct access *access_top; |
|---|
| 117 | static void free_access_list(void); |
|---|
| 118 | |
|---|
| 119 | extern const unsigned char *tables; |
|---|
| 120 | |
|---|
| 121 | static struct access * |
|---|
| 122 | sitelock_alloc(const char *host, dbref who, |
|---|
| 123 | uint32_t can, uint32_t cant, |
|---|
| 124 | const char *comment, const char **errptr) |
|---|
| 125 | __attribute_malloc__; |
|---|
| 126 | |
|---|
| 127 | static struct access *sitelock_alloc(const char *host, dbref who, |
|---|
| 128 | uint32_t can, uint32_t cant, |
|---|
| 129 | const char *comment, |
|---|
| 130 | const char **errptr) |
|---|
| 131 | { |
|---|
| 132 | struct access *tmp; |
|---|
| 133 | tmp = mush_malloc(sizeof(struct access), "sitelock.rule"); |
|---|
| 134 | if (!tmp) { |
|---|
| 135 | static const char *memerr = "unable to allocate memory"; |
|---|
| 136 | if (errptr) |
|---|
| 137 | *errptr = memerr; |
|---|
| 138 | return NULL; |
|---|
| 139 | } |
|---|
| 140 | tmp->who = who; |
|---|
| 141 | tmp->can = can; |
|---|
| 142 | tmp->cant = cant; |
|---|
| 143 | mush_strncpy(tmp->host, host, BUFFER_LEN); |
|---|
| 144 | if (comment) |
|---|
| 145 | mush_strncpy(tmp->comment, comment, BUFFER_LEN); |
|---|
| 146 | else |
|---|
| 147 | tmp->comment[0] = '\0'; |
|---|
| 148 | tmp->next = NULL; |
|---|
| 149 | |
|---|
| 150 | if (can & ACS_REGEXP) { |
|---|
| 151 | int erroffset = 0; |
|---|
| 152 | tmp->re = pcre_compile(host, 0, errptr, &erroffset, tables); |
|---|
| 153 | if (!tmp->re) { |
|---|
| 154 | mush_free(tmp, "sitelock.rule"); |
|---|
| 155 | return NULL; |
|---|
| 156 | } |
|---|
| 157 | } else |
|---|
| 158 | tmp->re = NULL; |
|---|
| 159 | |
|---|
| 160 | return tmp; |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | static bool |
|---|
| 164 | add_access_node(const char *host, dbref who, uint32_t can, |
|---|
| 165 | uint32_t cant, const char *comment, const char **errptr) |
|---|
| 166 | { |
|---|
| 167 | struct access *end, *tmp; |
|---|
| 168 | |
|---|
| 169 | tmp = sitelock_alloc(host, who, can, cant, comment, errptr); |
|---|
| 170 | if (!tmp) |
|---|
| 171 | return false; |
|---|
| 172 | |
|---|
| 173 | if (!access_top) { |
|---|
| 174 | /* Add to the beginning */ |
|---|
| 175 | access_top = tmp; |
|---|
| 176 | } else { |
|---|
| 177 | end = access_top; |
|---|
| 178 | while (end->next) |
|---|
| 179 | end = end->next; |
|---|
| 180 | end->next = tmp; |
|---|
| 181 | } |
|---|
| 182 | return true; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | |
|---|
| 186 | /** Read the access.cnf file. |
|---|
| 187 | * Initialize the access rules linked list and read in the access.cnf file. |
|---|
| 188 | * \return true if successful, false if not |
|---|
| 189 | */ |
|---|
| 190 | bool |
|---|
| 191 | read_access_file(void) |
|---|
| 192 | { |
|---|
| 193 | FILE *fp; |
|---|
| 194 | char buf[BUFFER_LEN]; |
|---|
| 195 | char *p; |
|---|
| 196 | uint32_t can, cant; |
|---|
| 197 | int retval; |
|---|
| 198 | dbref who; |
|---|
| 199 | char *comment; |
|---|
| 200 | const char *errptr = NULL; |
|---|
| 201 | |
|---|
| 202 | if (access_top) { |
|---|
| 203 | /* We're reloading the file, so we've got to delete any current |
|---|
| 204 | * entries |
|---|
| 205 | */ |
|---|
| 206 | free_access_list(); |
|---|
| 207 | } |
|---|
| 208 | access_top = NULL; |
|---|
| 209 | /* Be sure we have a file descriptor */ |
|---|
| 210 | release_fd(); |
|---|
| 211 | fp = fopen(ACCESS_FILE, FOPEN_READ); |
|---|
| 212 | if (!fp) { |
|---|
| 213 | do_rawlog(LT_ERR, T("Access file %s not found."), ACCESS_FILE); |
|---|
| 214 | retval = 0; |
|---|
| 215 | } else { |
|---|
| 216 | do_rawlog(LT_ERR, "Reading %s", ACCESS_FILE); |
|---|
| 217 | while (fgets(buf, BUFFER_LEN, fp)) { |
|---|
| 218 | /* Strip end of line if it's \r\n or \n */ |
|---|
| 219 | if ((p = strchr(buf, '\r'))) |
|---|
| 220 | *p = '\0'; |
|---|
| 221 | else if ((p = strchr(buf, '\n'))) |
|---|
| 222 | *p = '\0'; |
|---|
| 223 | /* Find beginning of line; ignore blank lines */ |
|---|
| 224 | p = buf; |
|---|
| 225 | if (*p && isspace((unsigned char) *p)) |
|---|
| 226 | p++; |
|---|
| 227 | if (*p && *p != '#') { |
|---|
| 228 | can = cant = 0; |
|---|
| 229 | comment = NULL; |
|---|
| 230 | /* Is this the @sitelock entry? */ |
|---|
| 231 | if (!strncasecmp(p, "@sitelock", 9)) { |
|---|
| 232 | if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", |
|---|
| 233 | &errptr)) |
|---|
| 234 | do_log(LT_ERR, GOD, GOD, T("Failed to add sitelock node: %s"), |
|---|
| 235 | errptr); |
|---|
| 236 | } else { |
|---|
| 237 | if ((comment = strchr(p, '#'))) { |
|---|
| 238 | *comment++ = '\0'; |
|---|
| 239 | while (*comment && isspace((unsigned char) *comment)) |
|---|
| 240 | comment++; |
|---|
| 241 | } |
|---|
| 242 | /* Move past the host name */ |
|---|
| 243 | while (*p && !isspace((unsigned char) *p)) |
|---|
| 244 | p++; |
|---|
| 245 | if (*p) |
|---|
| 246 | *p++ = '\0'; |
|---|
| 247 | if (!parse_access_options(p, &who, &can, &cant, NOTHING)) |
|---|
| 248 | /* Nothing listed, so assume we can't do anything! */ |
|---|
| 249 | cant = ACS_DEFAULT; |
|---|
| 250 | if (!add_access_node(buf, who, can, cant, comment, &errptr)) |
|---|
| 251 | do_log(LT_ERR, GOD, GOD, T("Failed to add access node: %s"), |
|---|
| 252 | errptr); |
|---|
| 253 | } |
|---|
| 254 | } |
|---|
| 255 | } |
|---|
| 256 | retval = 1; |
|---|
| 257 | fclose(fp); |
|---|
| 258 | } |
|---|
| 259 | reserve_fd(); |
|---|
| 260 | return retval; |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | /** Write the access.cnf file. |
|---|
| 264 | * Writes out the access.cnf file from the linked list |
|---|
| 265 | */ |
|---|
| 266 | void |
|---|
| 267 | write_access_file(void) |
|---|
| 268 | { |
|---|
| 269 | FILE *fp; |
|---|
| 270 | char tmpf[BUFFER_LEN]; |
|---|
| 271 | struct access *ap; |
|---|
| 272 | acsflag *c; |
|---|
| 273 | |
|---|
| 274 | snprintf(tmpf, BUFFER_LEN, "%s.tmp", ACCESS_FILE); |
|---|
| 275 | /* Be sure we have a file descriptor */ |
|---|
| 276 | release_fd(); |
|---|
| 277 | fp = fopen(tmpf, FOPEN_WRITE); |
|---|
| 278 | if (!fp) { |
|---|
| 279 | do_log(LT_ERR, GOD, GOD, T("Unable to open %s."), tmpf); |
|---|
| 280 | } else { |
|---|
| 281 | for (ap = access_top; ap; ap = ap->next) { |
|---|
| 282 | if (strcmp(ap->host, "@sitelock") == 0) { |
|---|
| 283 | fprintf(fp, "@sitelock\n"); |
|---|
| 284 | continue; |
|---|
| 285 | } |
|---|
| 286 | fprintf(fp, "%s %d ", ap->host, ap->who); |
|---|
| 287 | switch (ap->can) { |
|---|
| 288 | case ACS_SITELOCK: |
|---|
| 289 | break; |
|---|
| 290 | case ACS_DEFAULT: |
|---|
| 291 | fprintf(fp, "DEFAULT "); |
|---|
| 292 | break; |
|---|
| 293 | default: |
|---|
| 294 | for (c = acslist; c->name; c++) |
|---|
| 295 | if (ap->can & c->flag) |
|---|
| 296 | fprintf(fp, "%s ", c->name); |
|---|
| 297 | break; |
|---|
| 298 | } |
|---|
| 299 | switch (ap->cant) { |
|---|
| 300 | case ACS_DEFAULT: |
|---|
| 301 | fprintf(fp, "NONE "); |
|---|
| 302 | break; |
|---|
| 303 | default: |
|---|
| 304 | for (c = acslist; c->name; c++) |
|---|
| 305 | if (c->toggle && (ap->cant & c->flag)) |
|---|
| 306 | fprintf(fp, "!%s ", c->name); |
|---|
| 307 | break; |
|---|
| 308 | } |
|---|
| 309 | if (ap->comment && *ap->comment) |
|---|
| 310 | fprintf(fp, "# %s\n", ap->comment); |
|---|
| 311 | else |
|---|
| 312 | fprintf(fp, "\n"); |
|---|
| 313 | } |
|---|
| 314 | fclose(fp); |
|---|
| 315 | rename_file(tmpf, ACCESS_FILE); |
|---|
| 316 | } |
|---|
| 317 | reserve_fd(); |
|---|
| 318 | return; |
|---|
| 319 | } |
|---|
| 320 | |
|---|
| 321 | #ifdef FORCE_IPV4 |
|---|
| 322 | static char * |
|---|
| 323 | ip4_to_ip6(const char *addr) |
|---|
| 324 | { |
|---|
| 325 | static char tbuf1[BUFFER_LEN]; |
|---|
| 326 | char *bp; |
|---|
| 327 | bp = tbuf1; |
|---|
| 328 | safe_format(tbuf1, &bp, "::ffff:%s", addr); |
|---|
| 329 | *bp = '\0'; |
|---|
| 330 | return tbuf1; |
|---|
| 331 | } |
|---|
| 332 | #endif |
|---|
| 333 | |
|---|
| 334 | |
|---|
| 335 | /** Decide if a host can access someway. |
|---|
| 336 | * \param hname a host or user+host pattern. |
|---|
| 337 | * \param flag the access type we're testing. |
|---|
| 338 | * \param who the player attempting access. |
|---|
| 339 | * \retval 1 access permitted. |
|---|
| 340 | * \retval 0 access denied. |
|---|
| 341 | * \verbatim |
|---|
| 342 | * Given a hostname and a flag decide if the host can do it. |
|---|
| 343 | * Here's how it works: |
|---|
| 344 | * We run the linked list and take the first match. |
|---|
| 345 | * (If the hostname is user@host, we try to match both user@host |
|---|
| 346 | * and just host to each line in the file.) |
|---|
| 347 | * If we make a match, and the line tells us whether the site can/can't |
|---|
| 348 | * do the action, we're done. |
|---|
| 349 | * Otherwise, we assume that the host can do any toggleable option |
|---|
| 350 | * (can create, connect, guest), and don't have any special |
|---|
| 351 | * flags (can't register, isn't suspect) |
|---|
| 352 | * \endverbatim |
|---|
| 353 | */ |
|---|
| 354 | bool |
|---|
| 355 | site_can_access(const char *hname, uint32_t flag, dbref who) |
|---|
| 356 | { |
|---|
| 357 | struct access *ap; |
|---|
| 358 | acsflag *c; |
|---|
| 359 | const char *p; |
|---|
| 360 | |
|---|
| 361 | if (!hname || !*hname) |
|---|
| 362 | return 0; |
|---|
| 363 | |
|---|
| 364 | if ((p = strchr(hname, '@'))) |
|---|
| 365 | p++; |
|---|
| 366 | |
|---|
| 367 | for (ap = access_top; ap; ap = ap->next) { |
|---|
| 368 | if (!(ap->can & ACS_SITELOCK) |
|---|
| 369 | && ((ap->can & ACS_REGEXP) |
|---|
| 370 | ? (qcomp_regexp_match(ap->re, hname) |
|---|
| 371 | || (p && qcomp_regexp_match(ap->re, p)) |
|---|
| 372 | #ifdef FORCE_IPV4 |
|---|
| 373 | || qcomp_regexp_match(ip4_to_ip6(ap->re), hname) |
|---|
| 374 | || (p && qcomp_regexp_match(ip4_to_ip6(ap->re), p)) |
|---|
| 375 | #endif |
|---|
| 376 | ) |
|---|
| 377 | : (quick_wild(ap->host, hname) |
|---|
| 378 | || (p && quick_wild(ap->host, p)) |
|---|
| 379 | #ifdef FORCE_IPV4 |
|---|
| 380 | || quick_wild(ip4_to_ip6(ap->host), hname) |
|---|
| 381 | || (p && quick_wild(ip4_to_ip6(ap->host), p)) |
|---|
| 382 | #endif |
|---|
| 383 | )) |
|---|
| 384 | && (ap->who == AMBIGUOUS || ap->who == who)) { |
|---|
| 385 | /* Got one */ |
|---|
| 386 | if (flag & ACS_CONNECT) { |
|---|
| 387 | if ((ap->cant & ACS_GOD) && God(who)) /* God can't connect from here */ |
|---|
| 388 | return 0; |
|---|
| 389 | else if ((ap->cant & ACS_WIZARD) && Wizard(who)) |
|---|
| 390 | /* Wiz can't connect from here */ |
|---|
| 391 | return 0; |
|---|
| 392 | else if ((ap->cant & ACS_ADMIN) && Hasprivs(who)) |
|---|
| 393 | /* Wiz and roy can't connect from here */ |
|---|
| 394 | return 0; |
|---|
| 395 | } |
|---|
| 396 | if (ap->cant && ((ap->cant & flag) == flag)) |
|---|
| 397 | return 0; |
|---|
| 398 | if (ap->can && (ap->can & flag)) |
|---|
| 399 | return 1; |
|---|
| 400 | |
|---|
| 401 | /* Hmm. We don't know if we can or not, so continue */ |
|---|
| 402 | break; |
|---|
| 403 | } |
|---|
| 404 | } |
|---|
| 405 | |
|---|
| 406 | /* Flag was neither set nor unset. If the flag was a toggle, |
|---|
| 407 | * then the host can do it. If not, the host can't */ |
|---|
| 408 | for (c = acslist; c->name; c++) { |
|---|
| 409 | if (flag & c->flag) |
|---|
| 410 | return c->toggle ? 1 : 0; |
|---|
| 411 | } |
|---|
| 412 | /* Should never reach here, but just in case */ |
|---|
| 413 | return 1; |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | |
|---|
| 417 | /** Return the first access rule that matches a host. |
|---|
| 418 | * \param hname a host or user+host pattern. |
|---|
| 419 | * \param who the player attempting access. |
|---|
| 420 | * \param rulenum pointer to rule position. |
|---|
| 421 | * \return pointer to first matching access rule or NULL. |
|---|
| 422 | */ |
|---|
| 423 | struct access * |
|---|
| 424 | site_check_access(const char *hname, dbref who, int *rulenum) |
|---|
| 425 | { |
|---|
| 426 | struct access *ap; |
|---|
| 427 | const char *p; |
|---|
| 428 | |
|---|
| 429 | *rulenum = 0; |
|---|
| 430 | if (!hname || !*hname) |
|---|
| 431 | return 0; |
|---|
| 432 | |
|---|
| 433 | if ((p = strchr(hname, '@'))) |
|---|
| 434 | p++; |
|---|
| 435 | |
|---|
| 436 | for (ap = access_top; ap; ap = ap->next) { |
|---|
| 437 | (*rulenum)++; |
|---|
| 438 | if (!(ap->can & ACS_SITELOCK) |
|---|
| 439 | && ((ap->can & ACS_REGEXP) |
|---|
| 440 | ? (qcomp_regexp_match(ap->re, hname) |
|---|
| 441 | || (p && qcomp_regexp_match(ap->re, p)) |
|---|
| 442 | #ifdef FORCE_IPV4 |
|---|
| 443 | || qcomp_regexp_match(ip4_to_ip6(ap->host), hname) |
|---|
| 444 | || (p && qcomp_regexp_match(ip4_to_ip6(ap->host), p)) |
|---|
| 445 | #endif |
|---|
| 446 | ) |
|---|
| 447 | : (quick_wild(ap->host, hname) |
|---|
| 448 | || (p && quick_wild(ap->host, p)) |
|---|
| 449 | #ifdef FORCE_IPV4 |
|---|
| 450 | || quick_wild(ip4_to_ip6(ap->host), hname) |
|---|
| 451 | || (p && quick_wild(ip4_to_ip6(ap->host), p)) |
|---|
| 452 | #endif |
|---|
| 453 | )) |
|---|
| 454 | && (ap->who == AMBIGUOUS || ap->who == who)) { |
|---|
| 455 | /* Got one */ |
|---|
| 456 | return ap; |
|---|
| 457 | } |
|---|
| 458 | } |
|---|
| 459 | return NULL; |
|---|
| 460 | } |
|---|
| 461 | |
|---|
| 462 | /** Display an access rule. |
|---|
| 463 | * \param ap pointer to access rule. |
|---|
| 464 | * \param rulenum access rule's number in the list. |
|---|
| 465 | * \param who unused. |
|---|
| 466 | * \param buff buffer to store output. |
|---|
| 467 | * \param bp pointer into buff. |
|---|
| 468 | * This function provides an appealing display of an access rule |
|---|
| 469 | * in the list. |
|---|
| 470 | */ |
|---|
| 471 | void |
|---|
| 472 | format_access(struct access *ap, int rulenum, |
|---|
| 473 | dbref who __attribute__ ((__unused__)), char *buff, char **bp) |
|---|
| 474 | { |
|---|
| 475 | if (ap) { |
|---|
| 476 | safe_format(buff, bp, T("Matched line %d: %s %s"), rulenum, ap->host, |
|---|
| 477 | (ap->can & ACS_REGEXP) ? "(regexp)" : ""); |
|---|
| 478 | safe_chr('\n', buff, bp); |
|---|
| 479 | safe_format(buff, bp, T("Comment: %s"), ap->comment); |
|---|
| 480 | safe_chr('\n', buff, bp); |
|---|
| 481 | safe_str(T("Connections allowed by: "), buff, bp); |
|---|
| 482 | if (ap->cant & ACS_CONNECT) |
|---|
| 483 | safe_str(T("No one"), buff, bp); |
|---|
| 484 | else if (ap->cant & ACS_ADMIN) |
|---|
| 485 | safe_str(T("All but admin"), buff, bp); |
|---|
| 486 | else if (ap->cant & ACS_WIZARD) |
|---|
| 487 | safe_str(T("All but wizards"), buff, bp); |
|---|
| 488 | else if (ap->cant & ACS_GOD) |
|---|
| 489 | safe_str(T("All but God"), buff, bp); |
|---|
| 490 | else |
|---|
| 491 | safe_str(T("All"), buff, bp); |
|---|
| 492 | safe_chr('\n', buff, bp); |
|---|
| 493 | if (ap->cant & ACS_GUEST) |
|---|
| 494 | safe_str(T("Guest connections are NOT allowed"), buff, bp); |
|---|
| 495 | else |
|---|
| 496 | safe_str(T("Guest connections are allowed"), buff, bp); |
|---|
| 497 | safe_chr('\n', buff, bp); |
|---|
| 498 | if (ap->cant & ACS_CREATE) |
|---|
| 499 | safe_str(T("Creation is NOT allowed"), buff, bp); |
|---|
| 500 | else |
|---|
| 501 | safe_str(T("Creation is allowed"), buff, bp); |
|---|
| 502 | safe_chr('\n', buff, bp); |
|---|
| 503 | if (ap->can & ACS_REGISTER) |
|---|
| 504 | safe_str(T("Email registration is allowed"), buff, bp); |
|---|
| 505 | if (ap->can & ACS_SUSPECT) |
|---|
| 506 | safe_str(T("Players connecting are set SUSPECT"), buff, bp); |
|---|
| 507 | if (ap->can & ACS_DENY_SILENT) |
|---|
| 508 | safe_str(T("Denied connections are not logged"), buff, bp); |
|---|
| 509 | } else { |
|---|
| 510 | safe_str(T("No matching access rule"), buff, bp); |
|---|
| 511 | } |
|---|
| 512 | } |
|---|
| 513 | |
|---|
| 514 | |
|---|
| 515 | /** Add an access rule to the linked list. |
|---|
| 516 | * \param player enactor. |
|---|
| 517 | * \param host host pattern to add. |
|---|
| 518 | * \param who player to which rule applies, or AMBIGUOUS. |
|---|
| 519 | * \param can flags of allowed actions. |
|---|
| 520 | * \param cant flags of disallowed actions. |
|---|
| 521 | * \retval 1 success. |
|---|
| 522 | * \retval 0 failure. |
|---|
| 523 | * \verbatim |
|---|
| 524 | * This function adds an access rule after the @sitelock entry. |
|---|
| 525 | * If there is no @sitelock entry, add one to the end of the list |
|---|
| 526 | * and then add the entry. |
|---|
| 527 | * Build an appropriate comment based on the player and date |
|---|
| 528 | * \endverbatim |
|---|
| 529 | */ |
|---|
| 530 | bool |
|---|
| 531 | add_access_sitelock(dbref player, const char *host, dbref who, uint32_t can, |
|---|
| 532 | uint32_t cant) |
|---|
| 533 | { |
|---|
| 534 | struct access *end; |
|---|
| 535 | struct access *tmp; |
|---|
| 536 | const char *errptr = NULL; |
|---|
| 537 | |
|---|
| 538 | |
|---|
| 539 | tmp = sitelock_alloc(host, who, can, cant, "", &errptr); |
|---|
| 540 | |
|---|
| 541 | if (!tmp) { |
|---|
| 542 | notify_format(player, T("Unable to add sitelock entry: %s"), errptr); |
|---|
| 543 | return false; |
|---|
| 544 | } |
|---|
| 545 | |
|---|
| 546 | if (!access_top) { |
|---|
| 547 | /* Add to the beginning, but first add a sitelock marker */ |
|---|
| 548 | if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", &errptr)) { |
|---|
| 549 | notify_format(player, T("Unable to add @sitelock separator: %s"), errptr); |
|---|
| 550 | return 0; |
|---|
| 551 | } |
|---|
| 552 | access_top->next = tmp; |
|---|
| 553 | } else { |
|---|
| 554 | end = access_top; |
|---|
| 555 | while (end->next && end->can != ACS_SITELOCK) |
|---|
| 556 | end = end->next; |
|---|
| 557 | /* Now, either we're at the sitelock or the end */ |
|---|
| 558 | if (end->can != ACS_SITELOCK) { |
|---|
| 559 | /* We're at the end and there's no sitelock marker. Add one */ |
|---|
| 560 | if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", |
|---|
| 561 | &errptr)) { |
|---|
| 562 | notify_format(player, T("Unable to add @sitelock separator: %s"), |
|---|
| 563 | errptr); |
|---|
| 564 | return 0; |
|---|
| 565 | } |
|---|
| 566 | end = end->next; |
|---|
| 567 | } else { |
|---|
| 568 | /* We're in the middle, so be sure we keep the list linked */ |
|---|
| 569 | tmp->next = end->next; |
|---|
| 570 | } |
|---|
| 571 | end->next = tmp; |
|---|
| 572 | } |
|---|
| 573 | return 1; |
|---|
| 574 | } |
|---|
| 575 | |
|---|
| 576 | /** Remove an access rule from the linked list. |
|---|
| 577 | * \param pattern access rule host pattern to match. |
|---|
| 578 | * \return number of rule removed. |
|---|
| 579 | * This function removes an access rule from the list. |
|---|
| 580 | * Only rules that appear after the "@sitelock" rule can be |
|---|
| 581 | * removed with this function. |
|---|
| 582 | */ |
|---|
| 583 | int |
|---|
| 584 | remove_access_sitelock(const char *pattern) |
|---|
| 585 | { |
|---|
| 586 | struct access *ap, *next, *prev = NULL; |
|---|
| 587 | int n = 0; |
|---|
| 588 | |
|---|
| 589 | /* We only want to be able to delete entries added with @sitelock */ |
|---|
| 590 | for (ap = access_top; ap; ap = ap->next) |
|---|
| 591 | if (strcmp(ap->host, "@sitelock") == 0) { |
|---|
| 592 | prev = ap; |
|---|
| 593 | ap = ap->next; |
|---|
| 594 | break; |
|---|
| 595 | } |
|---|
| 596 | |
|---|
| 597 | while (ap) { |
|---|
| 598 | next = ap->next; |
|---|
| 599 | if (strcasecmp(pattern, ap->host) == 0) { |
|---|
| 600 | n++; |
|---|
| 601 | if (ap->re) |
|---|
| 602 | free(ap->re); |
|---|
| 603 | mush_free(ap, "sitelock.rule"); |
|---|
| 604 | if (prev) |
|---|
| 605 | prev->next = next; |
|---|
| 606 | else |
|---|
| 607 | access_top = next; |
|---|
| 608 | } else { |
|---|
| 609 | prev = ap; |
|---|
| 610 | } |
|---|
| 611 | ap = next; |
|---|
| 612 | } |
|---|
| 613 | |
|---|
| 614 | return n; |
|---|
| 615 | } |
|---|
| 616 | |
|---|
| 617 | /* Free the entire access list */ |
|---|
| 618 | static void |
|---|
| 619 | free_access_list(void) |
|---|
| 620 | { |
|---|
| 621 | struct access *ap, *next; |
|---|
| 622 | ap = access_top; |
|---|
| 623 | while (ap) { |
|---|
| 624 | next = ap->next; |
|---|
| 625 | if (ap->re) |
|---|
| 626 | free(ap->re); |
|---|
| 627 | mush_free(ap, "sitelock.rule"); |
|---|
| 628 | ap = next; |
|---|
| 629 | } |
|---|
| 630 | access_top = NULL; |
|---|
| 631 | } |
|---|
| 632 | |
|---|
| 633 | |
|---|
| 634 | /** Display the access list. |
|---|
| 635 | * \param player enactor. |
|---|
| 636 | * Sends the complete access list to the player. |
|---|
| 637 | */ |
|---|
| 638 | void |
|---|
| 639 | do_list_access(dbref player) |
|---|
| 640 | { |
|---|
| 641 | struct access *ap; |
|---|
| 642 | acsflag *c; |
|---|
| 643 | char flaglist[BUFFER_LEN]; |
|---|
| 644 | int rulenum = 0; |
|---|
| 645 | char *bp; |
|---|
| 646 | |
|---|
| 647 | for (ap = access_top; ap; ap = ap->next) { |
|---|
| 648 | rulenum++; |
|---|
| 649 | if (ap->can != ACS_SITELOCK) { |
|---|
| 650 | bp = flaglist; |
|---|
| 651 | for (c = acslist; c->name; c++) { |
|---|
| 652 | if (c->flag == ACS_DEFAULT) |
|---|
| 653 | continue; |
|---|
| 654 | if (ap->can & c->flag) { |
|---|
| 655 | safe_chr(' ', flaglist, &bp); |
|---|
| 656 | safe_str(c->name, flaglist, &bp); |
|---|
| 657 | } |
|---|
| 658 | if (c->toggle && (ap->cant & c->flag)) { |
|---|
| 659 | safe_chr(' ', flaglist, &bp); |
|---|
| 660 | safe_chr('!', flaglist, &bp); |
|---|
| 661 | safe_str(c->name, flaglist, &bp); |
|---|
| 662 | } |
|---|
| 663 | } |
|---|
| 664 | *bp = '\0'; |
|---|
| 665 | notify_format(player, |
|---|
| 666 | "%3d SITE: %-20s DBREF: %-6s FLAGS:%s", rulenum, |
|---|
| 667 | ap->host, unparse_dbref(ap->who), flaglist); |
|---|
| 668 | notify_format(player, " COMMENT: %s", ap->comment); |
|---|
| 669 | } else { |
|---|
| 670 | notify(player, |
|---|
| 671 | T |
|---|
| 672 | ("---- @sitelock will add sites immediately below this line ----")); |
|---|
| 673 | } |
|---|
| 674 | |
|---|
| 675 | } |
|---|
| 676 | if (rulenum == 0) { |
|---|
| 677 | notify(player, T("There are no access rules.")); |
|---|
| 678 | } |
|---|
| 679 | } |
|---|
| 680 | |
|---|
| 681 | /** Parse access options into fields. |
|---|
| 682 | * \param opts access options to read from. |
|---|
| 683 | * \param who pointer to player to whom rule applies, or AMBIGUOUS. |
|---|
| 684 | * \param can pointer to flags of allowed actions. |
|---|
| 685 | * \param cant pointer to flags of disallowed actions. |
|---|
| 686 | * \param player enactor. |
|---|
| 687 | * \return number of options successfully parsed. |
|---|
| 688 | * Parse options and return the appropriate can and cant bits. |
|---|
| 689 | * Return the number of options successfully parsed. |
|---|
| 690 | * This makes a copy of the options string, so it's not modified. |
|---|
| 691 | */ |
|---|
| 692 | int |
|---|
| 693 | parse_access_options(const char *opts, dbref *who, uint32_t * can, |
|---|
| 694 | uint32_t * cant, dbref player) |
|---|
| 695 | { |
|---|
| 696 | char myopts[BUFFER_LEN]; |
|---|
| 697 | char *p; |
|---|
| 698 | char *w; |
|---|
| 699 | acsflag *c; |
|---|
| 700 | int found, totalfound, first; |
|---|
| 701 | |
|---|
| 702 | if (!opts || !*opts) |
|---|
| 703 | return 0; |
|---|
| 704 | strcpy(myopts, opts); |
|---|
| 705 | totalfound = 0; |
|---|
| 706 | first = 1; |
|---|
| 707 | if (who) |
|---|
| 708 | *who = AMBIGUOUS; |
|---|
| 709 | p = trim_space_sep(myopts, ' '); |
|---|
| 710 | while ((w = split_token(&p, ' '))) { |
|---|
| 711 | found = 0; |
|---|
| 712 | |
|---|
| 713 | if (first && who) { /* Check for a character */ |
|---|
| 714 | first = 0; |
|---|
| 715 | if (is_integer(w)) { /* We have a dbref */ |
|---|
| 716 | *who = parse_integer(w); |
|---|
| 717 | if (*who != AMBIGUOUS && !GoodObject(*who)) |
|---|
| 718 | *who = AMBIGUOUS; |
|---|
| 719 | continue; |
|---|
| 720 | } |
|---|
| 721 | } |
|---|
| 722 | |
|---|
| 723 | if (*w == '!') { |
|---|
| 724 | /* Found a negated warning */ |
|---|
| 725 | w++; |
|---|
| 726 | for (c = acslist; c->name; c++) { |
|---|
| 727 | if (c->toggle && !strncasecmp(w, c->name, strlen(c->name))) { |
|---|
| 728 | *cant |= c->flag; |
|---|
| 729 | found++; |
|---|
| 730 | } |
|---|
| 731 | } |
|---|
| 732 | } else { |
|---|
| 733 | /* None is special */ |
|---|
| 734 | if (!strncasecmp(w, "NONE", 4)) { |
|---|
| 735 | *cant = ACS_DEFAULT; |
|---|
| 736 | found++; |
|---|
| 737 | } else { |
|---|
| 738 | for (c = acslist; c->name; c++) { |
|---|
| 739 | if (!strncasecmp(w, c->name, strlen(c->name))) { |
|---|
| 740 | *can |= c->flag; |
|---|
| 741 | found++; |
|---|
| 742 | } |
|---|
| 743 | } |
|---|
| 744 | } |
|---|
| 745 | } |
|---|
| 746 | /* At this point, we haven't matched any warnings. */ |
|---|
| 747 | if (!found) { |
|---|
| 748 | if (GoodObject(player)) |
|---|
| 749 | notify_format(player, T("Unknown access option: %s"), w); |
|---|
| 750 | else |
|---|
| 751 | do_log(LT_ERR, GOD, GOD, T("Unknown access flag: %s"), w); |
|---|
| 752 | } else { |
|---|
| 753 | totalfound += found; |
|---|
| 754 | } |
|---|
| 755 | } |
|---|
| 756 | return totalfound; |
|---|
| 757 | } |
|---|