All of lore.kernel.org
 help / color / mirror / Atom feed
* port of procps patch to acahalan procps
@ 2003-07-09 18:55 Colin Walters
       [not found] ` <1057805552.751.1243.camel@cube>
  0 siblings, 1 reply; 2+ messages in thread
From: Colin Walters @ 2003-07-09 18:55 UTC (permalink / raw)
  To: SELinux; +Cc: albert

[-- Attachment #1: Type: text/plain, Size: 372 bytes --]

Hi,

I've ported the SELinux 2.5 procps patch to the acahalan procps; this
may be useful for other systems like Debian which also use acalahan's
procps (I think Gentoo does).

It's been lightly tested.

Albert: this is an update to the new SELinux API for Linux 2.5; the old
API is deprecated now, and will likely be obsoleted once the new API is
backported to Linux 2.4.

[-- Attachment #2: acalahan-procps.patch --]
[-- Type: text/plain, Size: 16430 bytes --]

--- procps-3.1.9.orig/Makefile
+++ procps-3.1.9/Makefile
@@ -63,9 +63,9 @@
 
 CURSES := -I/usr/include/ncurses -lncurses
 
-LDFLAGS := -Wl,-warn-common
+LDFLAGS := -Wl,-warn-common -lselinux -lattr
 
-CFLAGS := -D_GNU_SOURCE -O2 -g3 -fno-common -ffast-math -I proc \
+CFLAGS := -D_GNU_SOURCE -DWITH_SELINUX -g -fno-common -ffast-math -I proc \
   -W -Wall -Wshadow -Wcast-align -Wredundant-decls \
   -Wbad-function-cast -Wcast-qual -Wwrite-strings -Waggregate-return \
 #  -Wpadded -Wunreachable-code -Wdisabled-optimization \
@@ -128,7 +128,7 @@
 ###### install
 
 $(BINFILES) : all
-	$(install) --mode a=rx --strip $(notdir $@) $@
+	$(install) --mode a=rx $(notdir $@) $@
 
 $(MANFILES) : all
 	$(install) --mode a=r $(notdir $@) $@
--- procps-3.1.9.orig/proc/readproc.c
+++ procps-3.1.9/proc/readproc.c
@@ -27,8 +27,8 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
-#ifdef FLASK_LINUX
-#include <fs_secure.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
 #endif
 
 #ifdef PROF
@@ -82,6 +82,10 @@
 	free((void*)*p->cmdline);
     if (p->environ)
 	free((void*)*p->environ);
+#ifdef WITH_SELINUX
+    if (p->scontext)
+        freecon(p->scontext);
+#endif
     free(p);
 }
 
@@ -519,10 +523,10 @@
     static struct direct *ent;		/* dirent handle */
     static struct stat sb;		/* stat buffer */
     static char path[32], sbuf[1024];	/* bufs for stat,statm */
-#ifdef FLASK_LINUX
-    security_id_t secsid;
-#endif
     pid_t pid;  // saved until we have a proc_t allocated for sure
+#ifdef WITH_SELINUX
+    int selinux_enabled = is_selinux_enabled();
+#endif
 
     /* loop until a proc matching restrictions is found or no more processes */
     /* I know this could be a while loop -- this way is easier to indent ;-) */
@@ -546,23 +550,18 @@
 	strcpy(path+6, ent->d_name);  // trust /proc to not contain evil top-level entries
 //	snprintf(path, sizeof path, "/proc/%s", ent->d_name);
     }
-#ifdef FLASK_LINUX
-    if ( stat_secure(path, &sb, &secsid) == -1 ) /* no such dirent (anymore) */
-#else
     if (unlikely(stat(path, &sb) == -1))	/* no such dirent (anymore) */
-#endif
 	goto next_proc;
 
     if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid))
 	goto next_proc;			/* not one of the requested uids */
 
-    if (!p)
+    if (!p) {
 	p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */
+	memset(p, 0, sizeof(p));
+    }
 
     p->euid = sb.st_uid;			/* need a way to get real uid */
-#ifdef FLASK_LINUX
-    p->secsid = secsid;
-#endif
     p->pid  = pid;
 
     if (flags & PROC_FILLSTAT) {         /* read, parse /proc/#/stat */
@@ -571,6 +570,30 @@
 	stat2proc(sbuf, p);				/* parse /proc/#/stat */
     }
 
+#ifdef WITH_SELINUX
+    if (selinux_enabled) {
+	if (p->scontext) {
+	    freecon(p->scontext);
+	    p->scontext=NULL;
+	}
+	if (getpidcon(p->pid, &p->scontext) < 0 )
+	    goto next_proc;
+	
+	if (unlikely(flags & PROC_CONTEXT)) {
+	    int found=0;
+	    int i=0;
+	    for (i=0; PT->scontexts[i]; i++) {
+		if (strcmp(p->scontext, PT->scontexts[i]) ==0) {
+		    found=1;
+		    break;
+		}
+	    }
+	    if (! found)
+		goto next_proc;			/* not one of the requested CONTEXTS */
+	}
+    }
+#endif
+
     if (unlikely(flags & PROC_FILLMEM)) {				/* read, parse /proc/#/statm */
 	if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 ))
 	    statm2proc(sbuf, p);		/* ignore statm errors here */
@@ -630,10 +653,10 @@
     static struct direct *ent;		/* dirent handle */
     static struct stat sb;		/* stat buffer */
     static char path[32], sbuf[1024];	/* bufs for stat,statm */
-#ifdef FLASK_LINUX
-    security_id_t secsid;
-#endif
     pid_t pid;  // saved until we have a proc_t allocated for sure
+#ifdef WITH_SELINUX
+    int selinux_enabled = is_selinux_enabled();
+#endif
 
     /* loop until a proc matching restrictions is found or no more processes */
     /* I know this could be a while loop -- this way is easier to indent ;-) */
@@ -652,26 +675,30 @@
     strcpy(path+6, ent->d_name);  // trust /proc to not contain evil top-level entries
 //  snprintf(path, sizeof path, "/proc/%s", ent->d_name);
 
-#ifdef FLASK_LINUX
-    if (stat_secure(path, &sb, &secsid) == -1) /* no such dirent (anymore) */
-#else
     if (stat(path, &sb) == -1)		/* no such dirent (anymore) */
-#endif
 	goto next_proc;
 
     if (!p)
 	p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */
 
     p->euid = sb.st_uid;			/* need a way to get real uid */
-#ifdef FLASK_LINUX
-    p->secsid = secsid;
-#endif
     p->pid  = pid;
 
     if ((file2str(path, "stat", sbuf, sizeof sbuf)) == -1)
 	goto next_proc;			/* error reading /proc/#/stat */
     stat2proc(sbuf, p);				/* parse /proc/#/stat */
 
+#ifdef WITH_SELINUX
+    if (selinux_enabled) {
+	if (p->scontext) {
+	    freecon(p->scontext);
+	    p->scontext=NULL;
+	}
+	if (getpidcon(p->pid, &p->scontext) < 0 )
+	    goto next_proc;
+    }
+#endif
+
     if (flags & PROC_FILLMEM) {				/* read, parse /proc/#/statm */
 	if ((file2str(path, "statm", sbuf, sizeof sbuf)) != -1 )
 	    statm2proc(sbuf, p);		/* ignore statm errors here */
@@ -755,9 +782,15 @@
     }
     else if (flags & PROC_PID)
 	PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */
+#ifdef WITH_SELINUX
+    else if (flags & PROC_CONTEXT)
+	PT = openproc(flags, va_arg(ap, security_context_t*));
+#endif
     else
 	PT = openproc(flags);
     va_end(ap);
+    if (!PT)
+      return 0;
     do {					/* read table: */
 	tab = xrealloc(tab, (n+1)*sizeof(proc_t*));/* realloc as we go, using */
 	tab[n] = readproc_direct(PT, NULL);     /* final null to terminate */
--- procps-3.1.9.orig/proc/readproc.h
+++ procps-3.1.9/proc/readproc.h
@@ -14,8 +14,8 @@
 
 #define SIGNAL_STRING
 
-#ifdef FLASK_LINUX
-#include <fs_secure.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
 #endif
 
 EXTERN_C_BEGIN
@@ -136,8 +136,8 @@
 	tgid,		/* thread group ID */
 	exit_signal,	/* might not be SIGCHLD */
 	processor;      /* current (or most recent?) CPU */
-#ifdef FLASK_LINUX
-	security_id_t secsid;
+#ifdef WITH_SELINUX
+	security_context_t scontext;
 #endif
 } proc_t;
 
@@ -154,8 +154,8 @@
     pid_t*	pids;	/* pids of the procs */
     uid_t*	uids;	/* uids of procs */
     int		nuid;	/* cannot really sentinel-terminate unsigned short[] */
-#ifdef FLASK_LINUX
-    security_id_t* sids; /* SIDs of the procs */
+#ifdef WITH_SELINUX
+    security_context_t* scontexts; /* security contexts of the procs */
 #endif
 } PROCTAB;
 
@@ -218,6 +218,9 @@
 /* Obsolete, consider only processes with one of the passed: */
 #define PROC_PID     0x1000  /* process id numbers ( 0   terminated) */
 #define PROC_UID     0x4000  /* user id numbers    ( length needed ) */
+#ifdef WITH_SELINUX
+#define PROC_CONTEXT 0x8000 
+#endif
 
 // it helps to give app code a few spare bits
 #define PROC_SPARE_1 0x01000000
--- procps-3.1.9.orig/ps/display.c
+++ procps-3.1.9/ps/display.c
@@ -246,7 +246,7 @@
     fprintf(stderr, "Error: can not access /proc.\n");
     exit(1);
   }
-  memset(&buf, '#', sizeof(proc_t));
+  memset(&buf, 0, sizeof(proc_t));
   while(ps_readproc(ptp,&buf)){
     if(want_this_proc(&buf)) show_one_proc(&buf);
     if(buf.cmdline) free((void*)*buf.cmdline); // ought to reuse
--- procps-3.1.9.orig/ps/help.c
+++ procps-3.1.9/ps/help.c
@@ -35,8 +35,8 @@
 "-O,O preloaded -o  v  virtual memory  --cumulative --format --deselect\n"
 "-l,l long          u  user-oriented   --sort --tty --forest --version\n"
 "-F   extra full    X  registers       --heading --no-heading\n"
-#ifdef FLASK_LINUX
-"                                      --context --SID   (Flask only)\n"
+#ifdef WITH_SELINUX
+"                                      --context (SELinux only)\n"
 #endif
 "                    ********* misc options *********\n"
 "-V,V show version       L  list format codes  f  ASCII art forest\n"
--- procps-3.1.9.orig/ps/output.c
+++ procps-3.1.9/ps/output.c
@@ -68,11 +68,9 @@
 #include "../proc/escape.h"
 #include "common.h"
 
-#ifdef FLASK_LINUX
+#ifdef WITH_SELINUX
 #include <errno.h>
-#include <fs_secure.h>
-#include <ss.h>
-#define DEF_CTXTLEN 255
+#include <selinux/selinux.h>
 #endif
 
 
@@ -208,6 +206,9 @@
 CMP_SMALL(pcpu)
 
 CMP_SMALL(state)
+#ifdef WITH_SELINUX
+CMP_STR(scontext)
+#endif
 
 /* approximation to: kB of address space that could end up in swap */
 static int sr_swapable(const proc_t* P, const proc_t* Q) {
@@ -957,7 +958,7 @@
 
 
 /****************** FLASK security stuff **********************/
-#ifdef FLASK_LINUX
+#ifdef WITH_SELINUX
 
 /*
  * The sr_fn() calls -- for sorting -- don't return errors because
@@ -965,128 +966,26 @@
  * is called, at which point the error goes onscreen.
  */
 
-/* as above, creates sr_secsid function */
-CMP_INT(secsid)  /* FLASK security ID, **NOT** a session ID -- ugh */
-
-static int pr_secsid(char *restrict const outbuf, const proc_t *restrict const pp){
-  return sprintf(outbuf, "%d", (int) pp->secsid);
-}
-
 static int pr_context(char *restrict const outbuf, const proc_t *restrict const pp){
-  char *ctxt; /* should be security_context_t */
-  unsigned int len;
-  int rv;
-
-  len = DEF_CTXTLEN;
-  ctxt = (char *) calloc(1, len);
-  if ( ctxt != NULL )
-    rv = security_sid_to_context(pp->secsid, (security_context_t) ctxt, &len);
-  else
-    return sprintf(outbuf, "-");
-
-  if ( rv ) {
-    if ( errno != ENOSPC ) {
-      free(ctxt);
-      return sprintf(outbuf, "-");
-    } else {
-      free(ctxt);
-      ctxt = (char *) calloc(1, len);
-      if ( ctxt != NULL ) {
-	rv = security_sid_to_context(pp->secsid, (security_context_t) ctxt, &len);
-	if ( rv ) {
-	  free(ctxt);
-	  return sprintf(outbuf, "-");
-	} else {
-	  rv = sprintf(outbuf, "%s", ctxt);
-	  free(ctxt);
-	  return rv;
-	}
-      } else {           /* calloc() failed */
-	return sprintf(outbuf, "-");
-      }
-    }
+  if (pp->scontext) {
+    sprintf(outbuf, pp->scontext);
   } else {
-    rv = sprintf(outbuf, "%s", ctxt);
-    free(ctxt);
-    return rv;
+    sprintf(outbuf, "-");
   }
 }
 
-
 static int sr_context ( const proc_t* P, const proc_t* Q ) {
-  char *ctxt_P, *ctxt_Q; /* type should be security_context_t */
-  unsigned int len;
-  int rv;
-
-  len = DEF_CTXTLEN;
-  ctxt_P = (char *) calloc(1, len);
-  ctxt_Q = (char *) calloc(1, len);
-
-  rv = security_sid_to_context(P->secsid, (security_context_t) ctxt_P, &len);
-  if ( rv ) {
-    if ( errno != ENOSPC ) {
-      free(ctxt_P);
-      /* error should resurface during printing */
-      return( 0 );
-    } else {
-      free(ctxt_P);
-      ctxt_P = (char *) calloc(1, len);
-      if ( ctxt_P != NULL ) {
-	rv = security_sid_to_context(P->secsid, (security_context_t) ctxt_P, &len);
-	if ( rv ) {
-	  free(ctxt_P);
-	  /* error should resurface during printing */
-	  return( 0 );
-	}
-      } else {       /* calloc() failed */
-	/* error should resurface during printing */
-	return( 0 );
-      }
-    }
-  }
-
-  len = DEF_CTXTLEN;
-
-  rv = security_sid_to_context(Q->secsid, (security_context_t) ctxt_Q, &len);
-  if ( rv ) {
-    if ( errno != ENOSPC ) {
-      free(ctxt_P);
-      free(ctxt_Q);
-      /* error should resurface during printing */
-      return( 0 );
-    } else {
-      free(ctxt_Q);
-      ctxt_Q = (char *) calloc(1, len);
-      if ( ctxt_Q != NULL ) {
-	rv = security_sid_to_context(Q->secsid, (security_context_t) ctxt_Q, &len);
-	if ( rv ) {
-	  free(ctxt_P);
-	  free(ctxt_Q);
-	  /* error should resurface during printing */
-	  return( 0 );
-	}
-      } else {      /* calloc() failed */
-	/* error should resurface during printing */
-	free(ctxt_P);
-	return( 0 );
-      }
-    }
-  }
-
-  rv = strcmp(ctxt_P, ctxt_Q);
-
-  free(ctxt_P);
-  free(ctxt_Q);
-
-  return( rv );
+  if ((P->scontext==NULL) && (Q->scontext==NULL))
+    return 0;
+  if ((P->scontext==NULL) || (Q->scontext==NULL))
+    return 1;
+  return strcmp(P->scontext, Q->scontext);
 }
 
 #else
 
 /****** dummy functions ******/
 
-#define pr_secsid pr_nop
-#define sr_secsid sr_nop
 #define pr_context pr_nop
 #define sr_context sr_nop
 
@@ -1274,7 +1173,7 @@
 {"sched",     "SCH",     pr_sched,    sr_sched,   3,   0,    AIX, RIGHT},
 {"scnt",      "SCNT",    pr_nop,      sr_nop,     4,   0,    DEC, RIGHT},  /* man page misspelling of scount? */
 {"scount",    "SC",      pr_nop,      sr_nop,     4,   0,    AIX, RIGHT},  /* scnt==scount, DEC claims both */
-{"secsid",    "SID",     pr_secsid,   sr_secsid,  6,   0,    LNX, RIGHT}, /* Flask Linux */
+{"context",   "CONTEXT", pr_context,  sr_context,40,   0,    LNX, RIGHT}, /* SE Linux */
 {"sess",      "SESS",    pr_sess,     sr_session, 5,   0,    XXX, RIGHT},
 {"session",   "SESS",    pr_sess,     sr_session, 5,   0,    LNX, RIGHT},
 {"sgi_p",     "P",       pr_sgi_p,    sr_nop,     1,   0,    LNX, RIGHT}, /* "cpu" number */
@@ -1384,9 +1283,6 @@
 
 {"FL5FMT",   "f,state,uid,pid,ppid,pcpu,pri,nice,rss,wchan,start,time,command"},  /* Digital -fl */
 
-{"FLASK_context",   "pid,secsid,context,command"},  /* Flask Linux context, --context */
-{"FLASK_sid",       "pid,secsid,command"},          /* Flask Linux SID,     --SID */
-
 {"HP_",      "pid,tty,time,comm"},  /* HP default */
 {"HP_f",     "user,pid,ppid,cpu,stime,tty,time,args"},  /* HP -f */
 {"HP_fl",    "flags,state,user,pid,ppid,cpu,intpri,nice,addr,sz,wchan,stime,tty,time,args"},  /* HP -fl */
@@ -1414,6 +1310,9 @@
 
 {"RUSAGE",   "minflt,majflt,nswap,inblock,oublock,msgsnd,msgrcv,nsigs,nvcsw,nivcsw"}, /* Digital -o "RUSAGE" */
 {"SCHED",    "user,pcpu,pri,usrpri,nice,psxpri,psr,policy,pset"},                /* Digital -o "SCHED" */
+#ifdef WITH_SELINUX
+{"SELINUX_context",   "pid,context,command"},  /* SELinux Linux context, --context */
+#endif
 {"SFMT",     "uid,pid,cursig,sig,sigmask,sigignore,sigcatch,stat,tname,command"},  /* Digital s */
 
 {"Std_f",    "uid_hack,pid,ppid,c,stime,tname,time,cmd"},                     /* new -f */
--- procps-3.1.9.orig/ps/parser.c
+++ procps-3.1.9/ps/parser.c
@@ -727,7 +727,6 @@
   gnu_table_struct *found;
   static const gnu_table_struct gnu_table[] = {
   {"Group",         &&case_Group},       /* rgid */
-  {"SID",           &&case_secsid},
   {"User",          &&case_User},        /* ruid */
   {"cols",          &&case_cols},
   {"columns",       &&case_columns},
@@ -755,7 +754,6 @@
   {"pid",           &&case_pid},
   {"ppid",          &&case_ppid},
   {"rows",          &&case_rows},
-  {"secsid",        &&case_secsid},
   {"sid",           &&case_sid},
   {"sort",          &&case_sort},
   {"tty",           &&case_tty},
@@ -941,13 +939,14 @@
     exit(0);
     return NULL;
   case_context:
-    trace("--context\n");
-    format_flags |= FF_Fc;
+    if (is_selinux_enabled()) {
+	trace("--context\n");
+	format_flags |= FF_Fc;
+    } else {
+	  fprintf(stderr,
+		  "Warning: --context ignored. Requires a SELinux enabled kernel\n");
+    }
     return NULL;
-  case_secsid:
-     trace("--secsid\n");
-     format_flags |= FF_Fs;
-     return NULL;
 }
 
 /*************** process trailing PIDs  **********************/
--- procps-3.1.9.orig/ps/sortformat.c
+++ procps-3.1.9/ps/sortformat.c
@@ -803,9 +803,8 @@
     case FF_LX:          spec="OL_X";         break;
     case FF_Lm:          spec="OL_m";         break;
 
-    /* These are FLASK security options. */
-    case FF_Fc:          spec="FLASK_context"; break;
-    case FF_Fs:          spec="FLASK_sid";     break;
+    /* These are SELinux security options. */
+    case FF_Fc:          spec="SELINUX_context"; break;
 
     }  /* end switch(format_flags) */
 
--- procps-3.1.9.orig/ps/common.h
+++ procps-3.1.9/ps/common.h
@@ -103,7 +103,6 @@
 #define FF_LX 0x0100 /* X */
 #define FF_Lm 0x0200 /* m */  /* overloaded: threads, sort, format */
 #define FF_Fc 0x0400 /* --context */  /* Flask security context format */
-#define FF_Fs 0x0800 /* --SID */      /* Flask SID format */
 
 /* predefined format modifier flags such as:  -l -f l u s -j */
 #define FM_c 0x0001 /* -c */

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: port of procps patch to acahalan procps
       [not found] ` <1057805552.751.1243.camel@cube>
@ 2003-07-11 19:14   ` Colin Walters
  0 siblings, 0 replies; 2+ messages in thread
From: Colin Walters @ 2003-07-11 19:14 UTC (permalink / raw)
  To: Albert Cahalan; +Cc: SELinux

On Wed, 2003-07-09 at 22:52, Albert Cahalan wrote:

> OK. I'll get most of that into the 3.1.10 release.

Cool.  Note however that it won't currently run on 2.4 SELinux, so you
should probably warn your users about this.

> I need to know more about a security context.
> I need to know how big it is today (variable?)
> and how, if at all, you intend to keep the size
> from ever changing. I'm working toward a stable
> ABI for procps, meaning that compile-time choices
> can't make the library incompatible with the apps.

It's basically just a char *, and I don't think that will ever change.

> +#ifdef WITH_SELINUX
> +    int selinux_enabled = is_selinux_enabled();
> +#endif
> 
> Is that going to be slow? You're making this call
> for every process.

It shouldn't be too slow, no; basically it is implemented by reading
/proc/self/attr/current and seeing if it works.  But it would be a good
idea to factor the code out into an initialization section.

> -    if (!p)
> +    if (!p) {
>         p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */
> +       memset(p, 0, sizeof(p));
> +    }
> 
> Why? I notice elsewhere that you seem to expect
> readproc() to get called with a security context
> already allocated. Hmmm...

The reason I did this was because the patch for the other procps does:
	if (p->scontext) {
	    freecon(p->scontext);
	    p->scontext=NULL;
	}

But this might be something that was only needed for the other procps. 
It does look like when readproc is called, the proc_t * is always
freshly allocated, and is later freed with freeproc, which will
correctly free the context.  So you could probably just delete both the
freecon code and the memset code in readproc.

> +#ifdef WITH_SELINUX
> +#define PROC_CONTEXT 0x8000 
> +#endif
> 
> no need to #ifdef that

Ok.

> 
> -  memset(&buf, '#', sizeof(proc_t));
> +  memset(&buf, 0, sizeof(proc_t));
> 
> This is debug code. It's used to help catch
> uninitialized memory. (did it catch you?)

Yes, because of the above code that checked whether p->scontext was
nonzero, and freed it if not.

This is unrelated to the SELinux patch, but: I personally find valgrind
to be a far more effective way to debug memory allocation errors.  Also,
I think it would be better to memset it to 0 in released code, but have
a debugging mode which uses '#'.  This is a kind of defensive
programming; if a bug could be avoided by memsetting a buffer to 0, then
it's not really even a bug in the first place in my opinion.  You don't
want procps to segfault on the user's computer just because they hit a
strange codepath.


--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2003-07-11 19:14 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2003-07-09 18:55 port of procps patch to acahalan procps Colin Walters
     [not found] ` <1057805552.751.1243.camel@cube>
2003-07-11 19:14   ` Colin Walters

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.