The primary choice for a FTP server fell onto the shoulders of Pure FTPd given its flexibility in managing MySQL based SQL backend, and because (honestly) ProFTPd was SEG-FAULTing with the MySQL backend.
The patch has been tested with version 1.0.20 of Pure FTPd.
As with Apache, we'll need to patch the Pure FTPd sources to support the SHA-512 encryption algorithm used by Jira, so that FTP users can use the passwords configured for Jira and Confluence.
To apply the patch and compile, follow these steps:
# tar -jxvf pure-ftpd-1.0.xx.tar.bz2 # patch -p0 < patch.txt # cd pure-ftpd-1.0.xx # automake # autoconf # ./configure --with-mysql=/opt/mysql # make clean # make # make install
Note that I like daemons rooted in the /opt, so my MySQL installation is in /opt/mysql but your mileage might vary.
After patching, compiling and installing, we need to create a simple configuration file for enabling lookups of users and passwords: a simple /etc/pure-ftpd.conf configuration file migh look like this:
# SQL Connection details #MYSQLServer localhost #MYSQLPort 3306 MYSQLSocket /tmp/mysql.sock # Login to the Jira Database MYSQLDatabase jira MYSQLUser jira MYSQLPassword jiraPassword # Make sure that SHA-512 is used and user/group ID is Apache MYSQLCrypt sha512 # Straight select for user and group ID. My "ftp" user is UID=21 GID=21 MYSQLGetUID SELECT 21 MYSQLGetGID SELECT 21 # Query to return the hashed password. For all users the home directory # is "/export/ftp" MYSQLGetPW SELECT password_hash FROM userbase WHERE user_name='\L' MYSQLGetDir SELECT '/export/ftp' # If users should have their individual directories in "/export/ftp/username" then #MYSQLGetDir SELECT CONCAT('/export/ftp/',user_name) FROM userbase WHERE user_name='\L' # Alternatively, if you want to use group management, call your FTP # groups like "ftp-something". and create directories in "/export/ftp" # called "something" (where "something" is the name of the group) # then simply use these queries # The "/export/ftp/something" at this point can be a symlink to another # directory, though... #MYSQLGetPW SELECT password_hash FROM userbase,membershipbase WHERE user_name='\L' AND user_name=username AND group_name like 'ftp-%' #MYSQLGetDir SELECT CONCAT('/export/ftp/',SUBSTRING(group_name,5)) FROM userbase,membershipbase WHERE user_name='\L' AND user_name=username AND group_name like 'ftp-%'
To start the daemon, look at the Pure FTPd documentation, but I use this:
#/opt/pureftpd/bin/pure-ftpd -A -l mysql:./pure-ftpd.conf
This will chroot every Jira user in its home directory (depending on the configuration above), so that the rest of the filesystem is basically hidden from FTP access. Remove the -A option to give users the ability to see all the filesystem.
The SHA-512 Authentication Patch
diff -U3 -r pure-ftpd-1.0.20-orig/src/Makefile.am pure-ftpd-1.0.20/src/Makefile.am
--- pure-ftpd-1.0.20-orig/src/Makefile.am 2004-02-29 21:15:37.000000000 +0000
+++ pure-ftpd-1.0.20/src/Makefile.am 2005-01-18 18:16:46.248811352 +0000
@@ -103,6 +103,8 @@
crypto-md5.h \
crypto-sha1.c \
crypto-sha1.h \
+ crypto-sha512.c \
+ crypto-sha512.h \
quotas.h \
quotas.c \
fakechroot.h \
diff -U3 -r pure-ftpd-1.0.20-orig/src/crypto-sha512.c pure-ftpd-1.0.20/src/crypto-sha512.c
--- pure-ftpd-1.0.20-orig/src/crypto-sha512.c 2005-01-18 19:41:07.277418056 +0000
+++ pure-ftpd-1.0.20/src/crypto-sha512.c 2005-01-18 18:40:21.589646840 +0000
@@ -0,0 +1,251 @@
+/*
+ * SHA-512 code by Jean-Luc Cooke <jlcooke@certainkey.com>
+ *
+ * Copyright (c) Jean-Luc Cooke <jlcooke@certainkey.com>
+ * Copyright (c) Andrew McDonald <andrew@mcdonald.org.uk>
+ * Copyright (c) 2003 Kyle McMartin <kyle@debian.org>
+ */
+
+//#include "crypto.h"
+#include "crypto-sha512.h"
+
+#define H0 0x6a09e667f3bcc908ULL
+#define H1 0xbb67ae8584caa73bULL
+#define H2 0x3c6ef372fe94f82bULL
+#define H3 0xa54ff53a5f1d36f1ULL
+#define H4 0x510e527fade682d1ULL
+#define H5 0x9b05688c2b3e6c1fULL
+#define H6 0x1f83d9abfb41bd6bULL
+#define H7 0x5be0cd19137e2179ULL
+
+#define e0(x) (ROR(x,28) ^ ROR(x,34) ^ ROR(x,39))
+#define e1(x) (ROR(x,14) ^ ROR(x,18) ^ ROR(x,41))
+#define s0(x) (ROR(x, 1) ^ ROR(x, 8) ^ (x >> 7))
+#define s1(x) (ROR(x,19) ^ ROR(x,61) ^ (x >> 6))
+
+static inline uint64_t CH(uint64_t x, uint64_t y, uint64_t z) {
+ return ((x & y) ^ (~x & z));
+}
+
+static inline uint64_t MAJ(uint64_t x, uint64_t y, uint64_t z) {
+ return ((x & y) ^ (x & z) ^ (y & z));
+}
+
+static inline uint64_t ROR(uint64_t x, uint64_t y) {
+ return (x >> y) | (x << (64 - y));
+}
+
+static inline void LOA(int I, uint64_t *W, const uint8_t *input) {
+ uint64_t t1 = input[(8*I) ] & 0xff;
+ t1 <<= 8;
+ t1 |= input[(8*I)+1] & 0xff;
+ t1 <<= 8;
+ t1 |= input[(8*I)+2] & 0xff;
+ t1 <<= 8;
+ t1 |= input[(8*I)+3] & 0xff;
+ t1 <<= 8;
+ t1 |= input[(8*I)+4] & 0xff;
+ t1 <<= 8;
+ t1 |= input[(8*I)+5] & 0xff;
+ t1 <<= 8;
+ t1 |= input[(8*I)+6] & 0xff;
+ t1 <<= 8;
+ t1 |= input[(8*I)+7] & 0xff;
+ W[I] = t1;
+}
+
+static inline void MIX(int I, uint64_t *W) {
+ W[I] = s1(W[I-2]) + W[I-7] + s0(W[I-15]) + W[I-16];
+}
+
+static const uint64_t sha512_K[80] = {
+ 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL,
+ 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+ 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL,
+ 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+ 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL,
+ 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+ 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL,
+ 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+ 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
+ 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+ 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL,
+ 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+ 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL,
+ 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+ 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
+ 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+ 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL,
+ 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+ 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL,
+ 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+ 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
+ 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+ 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL,
+ 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+ 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL,
+ 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+ 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL,
+};
+
+
+static void _transform(uint64_t *state, const uint8_t *input) {
+ uint64_t a, b, c, d, e, f, g, h, t1, t2;
+ uint64_t W[80];
+ int i;
+
+ /* load the input */
+ for (i = 0; i < 16; i++) LOA(i, W, input);
+ for (i = 16; i < 80; i++) MIX(i, W);
+
+ /* load the state into our registers */
+ a=state[0]; b=state[1]; c=state[2]; d=state[3];
+ e=state[4]; f=state[5]; g=state[6]; h=state[7];
+
+ /* now iterate */
+ for (i=0; i<80; i+=8) {
+ t1 = h + e1(e) + CH(e,f,g) + sha512_K[i ] + W[i ];
+ t2 = e0(a) + MAJ(a,b,c); d+=t1; h=t1+t2;
+ t1 = g + e1(d) + CH(d,e,f) + sha512_K[i+1] + W[i+1];
+ t2 = e0(h) + MAJ(h,a,b); c+=t1; g=t1+t2;
+ t1 = f + e1(c) + CH(c,d,e) + sha512_K[i+2] + W[i+2];
+ t2 = e0(g) + MAJ(g,h,a); b+=t1; f=t1+t2;
+ t1 = e + e1(b) + CH(b,c,d) + sha512_K[i+3] + W[i+3];
+ t2 = e0(f) + MAJ(f,g,h); a+=t1; e=t1+t2;
+ t1 = d + e1(a) + CH(a,b,c) + sha512_K[i+4] + W[i+4];
+ t2 = e0(e) + MAJ(e,f,g); h+=t1; d=t1+t2;
+ t1 = c + e1(h) + CH(h,a,b) + sha512_K[i+5] + W[i+5];
+ t2 = e0(d) + MAJ(d,e,f); g+=t1; c=t1+t2;
+ t1 = b + e1(g) + CH(g,h,a) + sha512_K[i+6] + W[i+6];
+ t2 = e0(c) + MAJ(c,d,e); f+=t1; b=t1+t2;
+ t1 = a + e1(f) + CH(f,g,h) + sha512_K[i+7] + W[i+7];
+ t2 = e0(b) + MAJ(b,c,d); e+=t1; a=t1+t2;
+ }
+
+ state[0] += a; state[1] += b; state[2] += c; state[3] += d;
+ state[4] += e; state[5] += f; state[6] += g; state[7] += h;
+
+ /* erase our data */
+ a = b = c = d = e = f = g = h = t1 = t2 = 0;
+ memset(W, 0, 80 * sizeof(uint64_t));
+}
+
+void SHA512_Init(SHA512_CTX *sctx)
+{
+ sctx->state[0] = H0;
+ sctx->state[1] = H1;
+ sctx->state[2] = H2;
+ sctx->state[3] = H3;
+ sctx->state[4] = H4;
+ sctx->state[5] = H5;
+ sctx->state[6] = H6;
+ sctx->state[7] = H7;
+ sctx->count[0] = sctx->count[1] = sctx->count[2] = sctx->count[3] = 0;
+ memset(sctx->buf, 0, sizeof(sctx->buf));
+}
+
+void SHA512_Update(SHA512_CTX* sctx, const uint8_t *data, unsigned int len) {
+ unsigned int i, index, part_len;
+
+ /* Compute number of bytes mod 128 */
+ index = (unsigned int)((sctx->count[0] >> 3) & 0x7F);
+
+ /* Update number of bits */
+ if ((sctx->count[0] += (len << 3)) < (len << 3)) {
+ if ((sctx->count[1] += 1) < 1)
+ if ((sctx->count[2] += 1) < 1)
+ sctx->count[3]++;
+ sctx->count[1] += (len >> 29);
+ }
+
+ part_len = 128 - index;
+
+ /* Transform as many times as possible. */
+ if (len >= part_len) {
+ memcpy(&sctx->buf[index], data, part_len);
+ _transform(sctx->state, sctx->buf);
+
+ for (i = part_len; i + 127 < len; i+=128)
+ _transform(sctx->state, &data[i]);
+
+ index = 0;
+ } else {
+ i = 0;
+ }
+
+ /* Buffer remaining input */
+ memcpy(&sctx->buf[index], &data[i], len - i);
+}
+
+void SHA512_Final(SHA512_CTX *sctx, uint8_t *hash) {
+ const static uint8_t padding[128] = { 0x80, };
+
+ uint32_t t;
+ uint64_t t2;
+ uint8_t bits[128];
+ unsigned int index, pad_len;
+ int i, j;
+
+ index = pad_len = t = i = j = 0;
+ t2 = 0;
+
+ /* Save number of bits */
+ t = sctx->count[0];
+ bits[15] = t; t>>=8;
+ bits[14] = t; t>>=8;
+ bits[13] = t; t>>=8;
+ bits[12] = t;
+ t = sctx->count[1];
+ bits[11] = t; t>>=8;
+ bits[10] = t; t>>=8;
+ bits[9 ] = t; t>>=8;
+ bits[8 ] = t;
+ t = sctx->count[2];
+ bits[7 ] = t; t>>=8;
+ bits[6 ] = t; t>>=8;
+ bits[5 ] = t; t>>=8;
+ bits[4 ] = t;
+ t = sctx->count[3];
+ bits[3 ] = t; t>>=8;
+ bits[2 ] = t; t>>=8;
+ bits[1 ] = t; t>>=8;
+ bits[0 ] = t;
+
+ /* Pad out to 112 mod 128. */
+ index = (sctx->count[0] >> 3) & 0x7f;
+ pad_len = (index < 112) ? (112 - index) : ((128+112) - index);
+ SHA512_Update(sctx, padding, pad_len);
+
+ /* Append length (before padding) */
+ SHA512_Update(sctx, bits, 16);
+
+ /* Store state in digest */
+ for (i = j = 0; i < 8; i++, j += 8) {
+ t2 = sctx->state[i];
+ hash[j+7] = (char)t2 & 0xff; t2>>=8;
+ hash[j+6] = (char)t2 & 0xff; t2>>=8;
+ hash[j+5] = (char)t2 & 0xff; t2>>=8;
+ hash[j+4] = (char)t2 & 0xff; t2>>=8;
+ hash[j+3] = (char)t2 & 0xff; t2>>=8;
+ hash[j+2] = (char)t2 & 0xff; t2>>=8;
+ hash[j+1] = (char)t2 & 0xff; t2>>=8;
+ hash[j ] = (char)t2 & 0xff;
+ }
+
+ /* Zeroize sensitive information. */
+ memset(sctx, 0, sizeof(struct SHA512_CTX));
+}
+
+/*
+static int sha512_hash(uint8_t *orig, int origlen, uint8_t *hash, int hashlen) {
+ SHA512_CTX context;
+
+ if (hash == NULL) return -1;
+ if (hashlen < SHA512_DIGEST_SIZE) return -1;
+
+ sha512_init(&context);
+ sha512_update(&context, orig, origlen);
+ sha512_final(&context, hash);
+ return(SHA512_DIGEST_SIZE);
+}
+*/
diff -U3 -r pure-ftpd-1.0.20-orig/src/crypto-sha512.h pure-ftpd-1.0.20/src/crypto-sha512.h
--- pure-ftpd-1.0.20-orig/src/crypto-sha512.h 2005-01-18 19:41:11.740739528 +0000
+++ pure-ftpd-1.0.20/src/crypto-sha512.h 2005-01-18 19:23:13.063723360 +0000
@@ -0,0 +1,26 @@
+/*
+ * SHA-512 in C
+ * by Jean-Luc Cooke <jlcooke@certainkey.com>
+ */
+
+#ifndef __SHA512_H__
+#define __SHA512_H__ 1
+
+#define SHA512_DIGEST_SIZE 64
+#define SHA512_BASE64_SIZE 89
+
+typedef unsigned char uint8_t;
+typedef unsigned int uint32_t;
+typedef unsigned long long uint64_t;
+
+typedef struct SHA512_CTX {
+ uint64_t state[8];
+ uint32_t count[4];
+ uint8_t buf[128];
+} SHA512_CTX;
+
+void SHA512_Init(SHA512_CTX *sctx);
+void SHA512_Update(SHA512_CTX* sctx, const uint8_t *data, unsigned int len);
+void SHA512_Final(SHA512_CTX *sctx, uint8_t *hash);
+
+#endif
diff -U3 -r pure-ftpd-1.0.20-orig/src/crypto.c pure-ftpd-1.0.20/src/crypto.c
--- pure-ftpd-1.0.20-orig/src/crypto.c 2004-02-29 21:49:27.000000000 +0000
+++ pure-ftpd-1.0.20/src/crypto.c 2005-01-18 19:30:39.197900656 +0000
@@ -14,6 +14,7 @@
#else
# include <md5.h>
#endif
+# include "crypto-sha512.h"
#ifdef WITH_DMALLOC
# include <dmalloc.h>
@@ -189,6 +190,21 @@
return hexify(result, digest, sizeof result, sizeof digest);
}
+/* Compute a simple base64 SHA512 digest of a C-string */
+
+char *crypto_hash_sha512(const char *string)
+{
+ SHA512_CTX context;
+ unsigned char digest[SHA512_DIGEST_SIZE];
+ static char result[SHA512_BASE64_SIZE];
+
+ if (string == NULL) return -1;
+ SHA512_Init(&context);
+ SHA512_Update(&context, string, strlen(string));
+ SHA512_Final(&context, digest);
+
+ return base64ify(result, digest, sizeof result, sizeof digest);
+}
/* Compute a salted SHA1 digest of a C-string */
diff -U3 -r pure-ftpd-1.0.20-orig/src/crypto.h pure-ftpd-1.0.20/src/crypto.h
--- pure-ftpd-1.0.20-orig/src/crypto.h 2004-02-29 21:49:27.000000000 +0000
+++ pure-ftpd-1.0.20/src/crypto.h 2005-01-18 18:04:23.620707984 +0000
@@ -25,6 +25,7 @@
char *crypto_hash_sha1(const char *string, const int hex);
char *crypto_hash_ssha1(const char *string, const char *stored);
+char *crypto_hash_sha512(const char *string);
char *crypto_hash_md5(const char *string, const int hex);
char *crypto_hash_smd5(const char *string, const char *stored);
diff -U3 -r pure-ftpd-1.0.20-orig/src/log_mysql.c pure-ftpd-1.0.20/src/log_mysql.c
--- pure-ftpd-1.0.20-orig/src/log_mysql.c 2004-05-15 22:14:06.000000000 +0100
+++ pure-ftpd-1.0.20/src/log_mysql.c 2005-01-18 19:31:36.037259760 +0000
@@ -306,7 +306,7 @@
char *escaped_peer_ip = NULL;
char *escaped_decimal_ip = NULL;
int committed = 1;
- int crypto_crypt = 0, crypto_mysql = 0, crypto_md5 = 0, crypto_plain = 0;
+ int crypto_crypt = 0, crypto_mysql = 0, crypto_md5 = 0, crypto_sha512 = 0, crypto_plain = 0;
unsigned long decimal_ip_num = 0UL;
char decimal_ip[42];
char hbuf[NI_MAXHOST];
@@ -400,12 +400,15 @@
crypto_crypt++;
crypto_mysql++;
crypto_md5++;
+ crypto_sha512++;
} else if (strcasecmp(crypto, PASSWD_SQL_CRYPT) == 0) {
crypto_crypt++;
} else if (strcasecmp(crypto, PASSWD_SQL_MYSQL) == 0) {
crypto_mysql++;
} else if (strcasecmp(crypto, PASSWD_SQL_MD5) == 0) {
crypto_md5++;
+ } else if (strcasecmp(crypto, PASSWD_SQL_SHA512) == 0) {
+ crypto_sha512++;
} else { /* default to plaintext */
crypto_plain++;
}
@@ -438,6 +441,16 @@
goto auth_ok;
}
}
+ if (crypto_sha512 != 0) {
+ const char *crypted;
+
+ crypted = (const char *) crypto_hash_sha512(password);
+ if (crypted != NULL) {
+ if (strcmp(crypted, spwd) == 0) {
+ goto auth_ok;
+ }
+ }
+ }
if (crypto_plain != 0) {
if (*password != 0 && /* refuse null cleartext passwords */
strcmp(password, spwd) == 0) {
diff -U3 -r pure-ftpd-1.0.20-orig/src/log_mysql.h pure-ftpd-1.0.20/src/log_mysql.h
--- pure-ftpd-1.0.20-orig/src/log_mysql.h 2004-02-29 21:49:28.000000000 +0000
+++ pure-ftpd-1.0.20/src/log_mysql.h 2005-01-18 18:44:12.286575576 +0000
@@ -5,6 +5,7 @@
#define PASSWD_SQL_CLEARTEXT "cleartext"
#define PASSWD_SQL_MYSQL "password"
#define PASSWD_SQL_MD5 "md5"
+#define PASSWD_SQL_SHA512 "sha512"
#define PASSWD_SQL_ANY "any"
#define MYSQL_DEFAULT_SERVER "localhost"
#define MYSQL_DEFAULT_PORT 3306
