Integration of Jira User Management with FTP

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
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.