PennMUSH Community

root/1.8.3/trunk/src/access.c

Revision 1167, 20.9 kB (checked in by shawnw, 8 months ago)

Merge devel into trunk for p6 release

Line 
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, a