[PATCH] GSSAPI authentication for wget (resend)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I forgot to send the patch. Here it is.

------------------------------------------------------------------------

Hello,

I have always missed the GSSAPI authentication feature of the KRB5 FTP
command line tool included in Fedora Core 2 krb5-workstation package, so
I have implemented it for wget. The attached patch adds automatic GSSAPI
authentication in wget 1,9.1.

The only change in behavior is that, before performing an anonymous
plain-text login, the AUTH GSSAPI command is sent to the remote FTP
server. If the remote FTP server supports GSSAPI authentication, via the
ADAT command, GSSAPI authentication is negotiated between the wget
client and the remote FTP server.

Please, feel free to use and review this patch. I have used code from
krb5-workstation to implement the GSSAPI authentication for wget. There
will be probably many bugs. I have tested it against the FTP server
available in Fedora Core 2 krb5-server package, and two remote servers:
HEANET.ie and Rediris.es.

Thank you very much.
diff -uNr wget-1.9.1/src/ftp-basic.c wget-1.9.1.NEW/src/ftp-basic.c
--- wget-1.9.1/src/ftp-basic.c	2004-06-20 22:17:48.835120372 +0200
+++ wget-1.9.1.NEW/src/ftp-basic.c	2004-06-20 13:53:49.000000000 +0200
@@ -64,10 +64,98 @@
 #include "connect.h"
 #include "host.h"
 #include "ftp.h"
+#include "url.h"
+
+
+#define PROT_C 1 /* in the clear */
+#define PROT_S 2 /* safe */
+#define PROT_P 3 /* private */
+
+#define FTP_BUFSIZ 10240
+
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+
+
+gss_ctx_id_t gcontext;
+char *auth_type;
+int clevel = PROT_S;
+
 
 char ftp_last_respline[128];
 
-
+#define ftp_radix_decode(i,o,l) radix_encode((i),(o),(l),1)
+#define ftp_radix_encode(i,o,l) radix_encode((i),(o),(l),0)
+
+/*
+   Reads a single response line from a FTP control stream.
+
+   A response has the following fomat:
+   <code>[-]<blank><response message>\r\n
+
+   Where <code> is a three digit response code, with a well
+   defined meaning in the RFC, and <response message> is the
+   plain-text explanation or additional text. The response
+   may span additional lines, which must be read together to
+   form a complete response message. This is stated by appending
+   a "-" symbol to the numerical response code.
+
+   If the response spans additional lines, ftp_read_line must
+   be invoked in a loop until a line is read which does not
+   flag @slit.
+
+   @rbuf   - Control stream
+   @code   - Where <code> will be left
+   @obuf   - Where <response message> will be left
+   @bufsiz - Maximum allowable size for obuf
+   @i      - Number of characters of the response message
+   @split  - Whether this response spans additional lines
+*/
+uerr_t
+ftp_read_line (struct rbuf *rbuf, int *code, char *obuf,
+               size_t bufsiz, int *i, int *split)
+{
+  int res;
+  char c;
+
+  /* Extract the FTP three-digit result code */
+  res = RBUF_READCHAR (rbuf, &c);
+  *code = 0;
+  while (res ==1 && c != '\n' && ISDIGIT(c))
+  {
+	*code *= 10;
+	*code += c - '0';
+  	res = RBUF_READCHAR (rbuf, &c);
+  }
+
+  /* The result code could have a dash, noting this is a split
+     response, or a blank to delimit the plain-text additional
+     message. */
+  if (c != ' ' && c != '-')
+    return FTPRERR;
+
+  *split = (c == '-');
+  if(*split)
+	res = RBUF_READCHAR (rbuf, &c);
+
+  /* Parse the plain-text additional message, which must be
+     terminated by the "\r\n" character sequence. */
+  *i = 0;
+  res = RBUF_READCHAR (rbuf, &c);
+  while(res == 1 && c != '\n' && *i < bufsiz - 1)
+  {
+	if(c != '\r')
+        {
+		(*i)++;
+		*obuf++ = c;
+	}
+	res = RBUF_READCHAR (rbuf, &c);
+  }
+  *obuf = 0;
+  (*i)++;
+  return FTPOK;
+}   
+ 
 /* Get the response of FTP server and allocate enough room to handle
    it.  <CR> and <LF> characters are stripped from the line, and the
    line is 0-terminated.  All the response lines but the last one are
@@ -75,42 +163,66 @@
 uerr_t
 ftp_response (struct rbuf *rbuf, char **line)
 {
-  int i;
-  int bufsize = 40;
+  char c;
+  int res, i = 0, code = 0, split, dummy;
+  char obuf[FTP_BUFSIZ];
+
+  /* Read the response, taking care of splitted response messages
+     which may span several lines. */
+  res = ftp_read_line (rbuf, &code, obuf, sizeof(obuf), &i, &split);
+  while (res == FTPOK && split)
+    res = ftp_read_line (rbuf, &dummy, obuf, sizeof(obuf), &i, &split);
 
-  *line = (char *)xmalloc (bufsize);
-  do
+  if(res != FTPOK)
+    return res;
+
+  if (!auth_type)
+  {
+    /* This is a simple response message */
+	  *line = (char *) xmalloc (i + 4);
+	  sprintf (*line, "%03d %s", code, obuf);
+  }
+  else
+  {
+    if (strcmp (auth_type, "GSSAPI") == 0)
     {
-      for (i = 0; 1; i++)
-        {
-          int res;
-          if (i > bufsize - 1)
-            *line = (char *)xrealloc (*line, (bufsize <<= 1));
-          res = RBUF_READCHAR (rbuf, *line + i);
-          /* RES is number of bytes read.  */
-          if (res == 1)
-            {
-              if ((*line)[i] == '\n')
-                {
-                  (*line)[i] = '\0';
-                  /* Get rid of \r.  */
-                  if (i > 0 && (*line)[i - 1] == '\r')
-                    (*line)[i - 1] = '\0';
-                  break;
-                }
-            }
-          else
-            return FTPRERR;
-        }
-      if (opt.server_response)
-        logprintf (LOG_ALWAYS, "%s\n", *line);
+      /* This is a GSSAPI-protected response message */
+      int conf_state;
+      char ibuf[FTP_BUFSIZ];
+      gss_buffer_desc xmit_buf, msg_buf;
+      gss_qop_t maj_stat, min_stat;
+
+      /* BASE64-decode the response */
+      res = ftp_radix_decode (obuf, ibuf, &i);
+      if (res)
+      {
+        printf("Can't base 64 decode reply %d (%s)\n\"%s\"\n", 
+               code, radix_error(res), obuf);
+        return FTPRERR;
+      }
+
+      /* Unseal the GSSAPI-protected response */
+      xmit_buf.value = ibuf;
+      xmit_buf.length = i;
+      conf_state = (code == 631);
+      maj_stat = gss_unseal(&min_stat, gcontext, &xmit_buf, &msg_buf,
+                            &conf_state, NULL);
+
+      if (maj_stat != GSS_S_COMPLETE)
+      {
+        printf("failed unsealing reply\n");
+        return FTPRERR;
+      }
       else
-        DEBUGP (("%s\n", *line));
+      {
+        *line = xmalloc (msg_buf.length + 3);
+        memcpy (*line, msg_buf.value, msg_buf.length);
+        strcpy (*line+msg_buf.length, "\r\n");
+        gss_release_buffer (&min_stat, &msg_buf);
+      }
     }
-  while (!(i >= 3 && ISDIGIT (**line) && ISDIGIT ((*line)[1]) &&
-           ISDIGIT ((*line)[2]) && (*line)[3] == ' '));
-  strncpy (ftp_last_respline, *line, sizeof (ftp_last_respline));
-  ftp_last_respline[sizeof (ftp_last_respline) - 1] = '\0';
+  }
+
   return FTPOK;
 }
 
@@ -120,20 +232,75 @@
 static char *
 ftp_request (const char *command, const char *value)
 {
+  int length, conf_state;
+  char in[FTP_BUFSIZ];
+  char *out = NULL;
+  gss_buffer_desc in_buf, out_buf;
+  gss_qop_t maj_stat, min_stat;
   char *res = (char *)xmalloc (strlen (command)
                                + (value ? (1 + strlen (value)) : 0)
                                + 2 + 1);
   sprintf (res, "%s%s%s\r\n", command, value ? " " : "", value ? value : "");
   if (opt.server_response)
-    {
-      /* Hack: don't print out password.  */
-      if (strncmp (res, "PASS", 4) != 0)
-        logprintf (LOG_ALWAYS, "--> %s\n", res);
-      else
-        logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n");
-    }
+  {
+    /* Hack: don't print out password.  */
+    if (strncmp (res, "PASS", 4) != 0)
+      logprintf (LOG_ALWAYS, "--> %s\n", res);
+    else
+      logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n");
+  }
   else
     DEBUGP (("\n--> %s\n", res));
+
+  if (auth_type)
+  {
+    /* The session is protected */
+    DEBUGP(("Command before sealing is %s", res));
+    if (strcmp(auth_type, "GSSAPI") == 0)
+    {
+      /* The session is protected with GSSAPI, so the command
+         must be sealed properly */
+      in_buf.value = res;
+      in_buf.length = strlen (res) + 1;
+      maj_stat = gss_seal (&min_stat, gcontext, (clevel == PROT_P),
+                           GSS_C_QOP_DEFAULT, &in_buf, &conf_state, &out_buf);
+      xfree (res);
+      res = NULL;
+
+      if (maj_stat != GSS_S_COMPLETE)
+      {
+        printf ("Error sealing the command %s\n", res);
+        goto request_end;
+      }
+      else if ((clevel == PROT_P) && !conf_state)
+      {
+        printf("GSSAPI didn't encrypt the message\n");
+        goto request_end;
+      }
+      else
+      {
+        length = out_buf.length;
+        out = xmalloc (length);
+        memcpy (out, out_buf.value, length);
+        gss_release_buffer (&min_stat, &out_buf);
+      }
+    }
+
+    /* Encode the command in BASE64 */
+    int kerror = ftp_radix_encode (out, in, &length);
+    if (kerror)
+    {
+      printf("Error while base 64 decoding: %s\n", radix_error(kerror));
+      goto request_end;
+    }
+
+    res = xmalloc (4 + length + 2);
+    sprintf (res, "%s %s\r\n", (clevel == PROT_P) ? "ENC" : "MIC", in);
+  }
+
+  DEBUGP(("GSSAPI encoded command is: %s", res));
+
+request_end:
   return res;
 }
 
@@ -144,7 +311,7 @@
 /* Sends the USER and PASS commands to the server, to control
    connection socket csock.  */
 uerr_t
-ftp_login (struct rbuf *rbuf, const char *acc, const char *pass)
+ftp_login (struct rbuf *rbuf, const char *acc, const char *pass, struct url *u)
 {
   uerr_t err;
   char *request, *respline;
@@ -153,16 +320,166 @@
   /* Get greeting.  */
   err = ftp_response (rbuf, &respline);
   if (err != FTPOK)
-    {
-      xfree (respline);
-      return err;
-    }
+  {
+    xfree (respline);
+    return err;
+  }
+
   if (*respline != '2')
-    {
-      xfree (respline);
-      return FTPSRVERR;
-    }
+  {
+    xfree (respline);
+    return FTPSRVERR;
+  }
+
   xfree (respline);
+
+  /* Try to authenticate via the AUTH GSSAPI command */
+  auth_type = NULL;
+  request = ftp_request ("AUTH", "GSSAPI");
+  nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
+  xfree (request);
+  if (nwritten < 0)
+      return WRITEFAILED;
+
+  err = ftp_response (rbuf, &respline);
+  if (err != FTPOK)
+  {
+    xfree (respline);
+    return err;
+  }
+
+  DEBUGP(("%s\n", respline));
+
+  /* If AUTH GSSAPI is not supported, proceed with plain-text login */
+  if (respline[0] != '3' || respline[1] != '3' || respline[2] != '4')
+    goto login_proceed;
+
+  int i, len, kerror;
+  char stbuf[FTP_BUFSIZ];
+  unsigned char out_buf[FTP_BUFSIZ];
+  char *reply_parse;
+  struct sockaddr_in hisctladdr;
+  struct sockaddr_in myctladdr;
+  struct gss_channel_bindings_struct chan;
+  gss_buffer_desc send_tok, recv_tok, *token_ptr;
+  gss_qop_t maj_stat, min_stat;
+  gss_name_t target_name;
+
+  /* Initialize the out-of-band GSSAPI channel */
+  memset (&hisctladdr, 0, sizeof (hisctladdr));
+  hisctladdr.sin_addr.s_addr = inet_addr (u->host);
+  len = sizeof (myctladdr);
+  if (getsockname( RBUF_FD (rbuf), &myctladdr, &len))
+    goto login_proceed;
+
+  chan.initiator_addrtype = GSS_C_AF_INET; /* OM_uint32  */
+  chan.initiator_address.length = 4;
+  chan.initiator_address.value = &myctladdr.sin_addr.s_addr;
+  chan.acceptor_addrtype = GSS_C_AF_INET; /* OM_uint32 */
+  chan.acceptor_address.length = 4;
+  chan.acceptor_address.value = &hisctladdr.sin_addr.s_addr;
+  chan.application_data.length = 0;
+  chan.application_data.value = 0;
+
+  /* Authenticate against the Kerberos "host@hostname" service.
+     GSSAPI will canonicalize the hostname automagically */
+  snprintf (stbuf, sizeof (stbuf), "host@%s", u->host);
+  send_tok.value = stbuf;
+  send_tok.length = strlen (stbuf) + 1;
+  maj_stat = gss_import_name (&min_stat, &send_tok, gss_nt_service_name,
+                              &target_name);
+
+  if (maj_stat != GSS_S_COMPLETE)
+  {
+    printf("gss_import_name failed!\n");
+    goto gssapi_end;
+  }
+
+  token_ptr = GSS_C_NO_BUFFER;
+  gcontext = GSS_C_NO_CONTEXT;
+
+  /* The GSSAPI authentication process usually needs to complete
+     in several stages, so we need to loop until all stages are
+     completed in turn. */
+  do
+  {
+    maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &gcontext,
+                                    target_name, GSS_C_NO_OID,
+                                    GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
+                                    0, &chan, token_ptr, NULL, &send_tok,
+                                    NULL, NULL);
+
+    if (maj_stat == GSS_S_CONTINUE_NEEDED)
+      DEBUGP(("gss_init_sec_context needs to continue\n"));
+    else if (maj_stat == GSS_S_COMPLETE)
+      DEBUGP(("gss_init_sec_context is complete\n"));
+    else
+    {
+      printf("Error while initializing GSSAPI context\n");
+      goto gssapi_end;
+    }
+
+    if ((len = send_tok.length) != 0)
+    {
+      /* Encode the GSSAPI-generated token which will be sent
+         to the remote server in order to perform authentication. */
+      kerror = ftp_radix_encode(send_tok.value, out_buf, &len);
+      if(kerror)
+      {
+        printf("Error when base 64 encoding ADAT for server: %s\n",
+               radix_error(kerror));
+        goto gssapi_end;
+      }
+
+      /* Sent the BASE64-encoded, GSSAPI-generated authentication
+         token to the remote server via the ADAT command. */
+      DEBUGP(("GSSAPI ADAT= command is %s\n", out_buf));
+      request = ftp_request ("ADAT", out_buf);
+      nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
+      if (nwritten < 0)
+      {
+        xfree(request);
+        return WRITEFAILED;
+      }
+
+      xfree(request);
+      err = ftp_response (rbuf, &respline);
+      if (err != FTPOK)
+      {
+        xfree (respline);
+        return err;
+      }
+
+      /* If the remote server does not support the ADAT command,
+         proceed with plain-text login. */
+      DEBUGP(("Response to ADAT= is %s\n", &respline[9]));
+      if(respline[0] != '2' || respline[1] != '3' || respline[2] != '5')
+        goto login_proceed;
+
+      kerror = ftp_radix_decode(respline + 9, out_buf, &i);
+      xfree(respline);
+      if (kerror)
+      {
+        printf("Error when base 64 decoding server reply\n");
+        goto gssapi_end;
+      }
+
+      token_ptr = &recv_tok;
+      recv_tok.value = out_buf;
+      recv_tok.length = i;
+    }
+  } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+
+  DEBUGP(("GSSAPI authentication succeeded\n"));
+  auth_type = "GSSAPI";
+  acc = getenv("USER");
+
+gssapi_end:
+
+  gss_release_buffer(&min_stat, &send_tok);
+  gss_release_name(&min_stat, &target_name);
+
+login_proceed:
   /* Send USER username.  */
   request = ftp_request ("USER", acc);
   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
@@ -252,6 +569,9 @@
       return FTPLOGINC;
     }
   xfree (respline);
+
+login_end:
+
   /* All OK.  */
   return FTPOK;
 }
diff -uNr wget-1.9.1/src/ftp.c wget-1.9.1.NEW/src/ftp.c
--- wget-1.9.1/src/ftp.c	2004-06-20 22:17:48.790135746 +0200
+++ wget-1.9.1.NEW/src/ftp.c	2004-06-19 11:55:47.000000000 +0200
@@ -204,7 +204,7 @@
       logprintf (LOG_VERBOSE, _("Logging in as %s ... "), user);
       if (opt.server_response)
 	logputs (LOG_ALWAYS, "\n");
-      err = ftp_login (&con->rbuf, logname, passwd);
+      err = ftp_login (&con->rbuf, logname, passwd, u);
 
       if (con->proxy)
 	xfree (logname);
diff -uNr wget-1.9.1/src/ftp.h wget-1.9.1.NEW/src/ftp.h
--- wget-1.9.1/src/ftp.h	2003-09-18 15:46:17.000000000 +0200
+++ wget-1.9.1.NEW/src/ftp.h	2004-06-19 11:56:10.000000000 +0200
@@ -47,7 +47,7 @@
 };
   
 uerr_t ftp_response PARAMS ((struct rbuf *, char **));
-uerr_t ftp_login PARAMS ((struct rbuf *, const char *, const char *));
+uerr_t ftp_login PARAMS ((struct rbuf *, const char *, const char *, struct url *));
 uerr_t ftp_port PARAMS ((struct rbuf *));
 uerr_t ftp_pasv PARAMS ((struct rbuf *, ip_address *, unsigned short *));
 #ifdef ENABLE_IPV6
diff -uNr wget-1.9.1/src/Makefile.in wget-1.9.1.NEW/src/Makefile.in
--- wget-1.9.1/src/Makefile.in	2003-10-08 01:53:31.000000000 +0200
+++ wget-1.9.1.NEW/src/Makefile.in	2004-06-20 22:18:49.740305456 +0200
@@ -77,7 +77,7 @@
       headers$o host$o html-parse$o html-url$o http$o init$o      \
       log$o main$o $(MD5_OBJ) netrc$o progress$o rbuf$o recur$o   \
       res$o retr$o safe-ctype$o snprintf$o $(SSL_OBJ) url$o       \
-      utils$o version$o
+      utils$o version$o radix$o
 
 .SUFFIXES:
 .SUFFIXES: .c .o ._c ._o
diff -uNr wget-1.9.1/src/radix.c wget-1.9.1.NEW/src/radix.c
--- wget-1.9.1/src/radix.c	1970-01-01 01:00:00.000000000 +0100
+++ wget-1.9.1.NEW/src/radix.c	2004-06-19 12:30:23.000000000 +0200
@@ -0,0 +1,164 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+static char *radixN =
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static char pad = '=';
+
+int radix_encode(inbuf, outbuf, len, decode)
+unsigned char inbuf[], outbuf[];
+int *len, decode;
+{
+	int i,j,D = 0;
+	char *p;
+	unsigned char c = 0;
+
+	if (decode) {
+		for (i=0,j=0; inbuf[i] && inbuf[i] != pad; i++) {
+		    if ((p = strchr(radixN, inbuf[i])) == NULL) return(1);
+		    D = p - radixN;
+		    switch (i&3) {
+			case 0:
+			    c = D<<2;
+			    break;
+			case 1:
+			    outbuf[j++] = c | D>>4;
+			    c = (D&15)<<4;
+			    break;
+			case 2:
+			    outbuf[j++] = c | D>>2;
+			    c = (D&3)<<6;
+			    break;
+			case 3:
+			    outbuf[j++] = c | D;
+		    }
+		}
+		switch (i&3) {
+			case 1: return(3);
+			case 2: if (D&15) return(3);
+				if (strcmp((char *)&inbuf[i], "==")) return(2);
+				break;
+			case 3: if (D&3) return(3);
+				if (strcmp((char *)&inbuf[i], "="))  return(2);
+		}
+		*len = j;
+	} else {
+		for (i=0,j=0; i < *len; i++)
+		    switch (i%3) {
+			case 0:
+			    outbuf[j++] = radixN[inbuf[i]>>2];
+			    c = (inbuf[i]&3)<<4;
+			    break;
+			case 1:
+			    outbuf[j++] = radixN[c|inbuf[i]>>4];
+			    c = (inbuf[i]&15)<<2;
+			    break;
+			case 2:
+			    outbuf[j++] = radixN[c|inbuf[i]>>6];
+			    outbuf[j++] = radixN[inbuf[i]&63];
+			    c = 0;
+		    }
+		if (i%3) outbuf[j++] = radixN[c];
+		switch (i%3) {
+			case 1: outbuf[j++] = pad;
+			case 2: outbuf[j++] = pad;
+		}
+		outbuf[*len = j] = '\0';
+	}
+	return(0);
+}
+
+char *
+radix_error(e)
+int e;
+{
+	switch (e) {
+	    case 0:  return("Success");
+	    case 1:  return("Bad character in encoding");
+	    case 2:  return("Encoding not properly padded");
+	    case 3:  return("Decoded # of bits not a multiple of 8");
+	    default: return("Unknown error");
+	}
+}
+
+#ifdef STANDALONE
+usage(s)
+char *s;
+{
+	fprintf(stderr, "Usage: %s [ -d ] [ string ]\n", s);
+	exit(2);
+}
+
+static int n;
+
+putbuf(inbuf, outbuf, len, decode)
+unsigned char inbuf[], outbuf[];
+int len, decode;
+{
+	int c;
+
+	if (c = radix_encode(inbuf, outbuf, &len, decode)) {
+		fprintf(stderr, "Couldn't %scode input: %s\n",
+				decode ? "de" : "en", radix_error(c));
+		exit(1);
+	}
+	if (decode)
+		write(1, outbuf, len);
+	else
+		for (c = 0; c < len;) {
+			putchar(outbuf[c++]);
+			if (++n%76 == 0) putchar('\n');
+		}
+}
+
+main(argc,argv)
+int argc;
+char *argv[];
+{
+	unsigned char *inbuf, *outbuf;
+	int c, len = 0, decode = 0;
+	extern int optind;
+
+	while ((c = getopt(argc, argv, "d")) != -1)
+		switch(c) {
+			default:
+				usage(argv[0]);
+			case 'd':
+				decode++;
+		}
+
+	switch (argc - optind) {
+		case 0:
+			inbuf  = (unsigned char *) malloc(5);
+			outbuf = (unsigned char *) malloc(5);
+			while ((c = getchar()) != EOF)
+			    if (c != '\n') {
+				inbuf[len++] = c;
+				if (len == (decode ? 4 : 3)) {
+					inbuf[len] = '\0';
+					putbuf(inbuf, outbuf, len, decode);
+					len=0;
+				}
+			    }
+			if (len) {
+				inbuf[len] = '\0';
+				putbuf(inbuf, outbuf, len, decode);
+			}
+			break;
+		case 1:
+			inbuf = (unsigned char *)argv[optind];
+			len = strlen(inbuf);
+			outbuf = (unsigned char *)
+				malloc((len * (decode?3:4)) / (decode?4:3) + 1);
+			putbuf(inbuf, outbuf, len, decode);
+			break;
+		default:
+			fprintf(stderr, "Only one argument allowed\n");
+			usage(argv[0]);
+	}
+	if (n%76) putchar('\n');
+	exit(0);
+}
+#endif /* STANDALONE */

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Fedora Announce]     [Fedora Kernel]     [Fedora Testing]     [Fedora Formulas]     [Fedora PHP Devel]     [Kernel Development]     [Fedora Legacy]     [Fedora Maintainers]     [Fedora Desktop]     [PAM]     [Red Hat Development]     [Gimp]     [Yosemite News]
  Powered by Linux