FreeBSD自带的FTPD没有修改密码的功能,每次用户要修改密码时都比较麻烦,需要管理员来操作,在看了FTPD的源代码之后,打算自己动手为FTPD增加用户自己修改密码的功能。

主要是修改/usr/src/libexec/ftpd/ftpcmd.y,为其增加一条修改密码的命令:

SITE PASSWD old-passwd new-passwd

这样用户就可以使用site passwd来修改自己的密码了。

在FreeBSD 5.4 Release上带的FTPD(Version 6.00LS)的基础上,做了一个ftpcmd.y的patch,使用这个patch就可以实现用户修改密码的功能,但需要注意的是,这里的用户是指系统用户,另外,这个patch还没有实现当用户被chroot时修改密码的功能,在chroot环境下,因为无法打开/etc/passwd.master,所以造成无法修改用户密码。如果有哪位朋友知道如何解决这个问题,还希望来信告知(matthew@cnfug.org),感谢!

patch内容如下:freebsd_ftpcmd.y.diff

--- ftpcmd.y.orig    Wed Jun  1 23:39:19 2005
+++ ftpcmd.y    Wed Jun  1 23:42:02 2005
@@ -75,6 +75,20 @@
 #include "extern.h"
 #include "pathnames.h"
 
+/* add by matthew@cnfug.org */
+#include <fcntl.h>
+#include <stdarg.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+
+#define UPD_DELETE  -1
+#define UPD_CREATE   0
+#define UPD_REPLACE  1
+#define PWBUFSZ 1024
+#define PWF_PASSWD 0 
+#define PWF_MASTER 1
+/* add by matthew@cnfug.org */
+
 extern    union sockunion data_dest, his_addr;
 extern    int hostinfo;
 extern    int logged_in;
@@ -136,7 +150,7 @@
     CDUP    STOU    SMNT    SYST    SIZE    MDTM
     LPRT    LPSV    EPRT    EPSV
 
-    UMASK    IDLE    CHMOD    MDFIVE
+    UMASK    IDLE    CHMOD    MDFIVE  PASSWD
 
     LEXERR    NOTIMPL
 
@@ -618,6 +632,12 @@
             if ($6)
                 free($6);
         }
+    | SITE SP PASSWD check_login SP STRING CRLF
+        {
+            chpasswd($6);
+            if ($6 != NULL)
+                free($6);
+        }
     | SITE SP UMASK check_login CRLF
         {
             int oldmask;
@@ -1134,6 +1154,7 @@
     { "IDLE", IDLE, ARGS, 1,    "[ <sp> maximum-idle-time ]" },
     { "CHMOD", CHMOD, NSTR, 1,    "<sp> mode <sp> file-name" },
     { "HELP", HELP, OSTR, 1,    "[ <sp> <string> ]" },
+    { "PASSWD", PASSWD, OSTR, 1,    "[ <sp> old-password <sp> new-password ]" },
     { NULL,   0,    0,    0,    0 }
 };
 
@@ -1141,6 +1162,7 @@
 static char    *expglob(char *);
 static char    *exptilde(char *);
 static void     help(struct tab *, char *);
+static void     chpasswd(char *);
 static struct tab *
          lookup(struct tab *, char *);
 static int     port_check(const char *);
@@ -1564,6 +1586,363 @@
         reply(214, "%s%-*s\t%s; unimplemented.", type, width,
             c->name, c->help);
 }
+
+/* add by matthew@cnfug.org */
+
+static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
+
+/*
+ * Crypt password with MD5 crypt algorithm
+ */
+char *
+pw_pwcrypt(char *password)
+{
+        int             i;
+        char            salt[12];
+
+        static char     buf[256];
+
+        /*
+         * Calculate a salt value
+         */
+     /* use MD5 algorithm; matthew@cnfug.org */
+     crypt_set_format("md5");
+        for (i = 0; i < 8; i++)
+                salt[i] = chars[arc4random() % 63];
+        salt[i] = '\0';
+
+        return strncpy(buf, crypt(password, salt), 255);
+}
+
+/*
+ * verify the old password.
+ */
+int
+pw_verify(char *password, char *plain)
+{
+        char            salt[12];
+        return strncmp(password, crypt(plain, strncpy(salt, password, 11)), 255);
+}
+
+
+int
+extendline(char **buf, int * buflen, int needed)
+{
+    if (needed > *buflen) {
+        char    *tmp = realloc(*buf, needed);
+        if (tmp == NULL)
+            return -1;
+        *buf = tmp;
+        *buflen = needed;
+    }
+    return *buflen;
+}
+
+int
+extendarray(char ***buf, int * buflen, int needed)
+{
+    if (needed > *buflen) {
+        char    **tmp = realloc(*buf, needed * sizeof(char *));
+        if (tmp == NULL)
+            return -1;
+        *buf = tmp;
+        *buflen = needed;
+    }
+    return *buflen;
+}
+
+
+int
+fileupdate(char const * filename, mode_t fmode, char const * newline, char const * prefix, int pfxlen, int updmode)
+{
+    int     rc = 0;
+
+    if (pfxlen <= 1)
+        rc = EINVAL;
+    else {
+        int    infd = open(filename, O_RDWR | O_CREAT | O_EXLOCK, fmode);
+
+        if (infd == -1)
+            rc = errno;
+        else {
+            FILE   *infp = fdopen(infd, "r+");
+
+            if (infp == NULL) {
+                rc = errno;        /* Assumes fopen(3) sets errno from open(2) */
+                close(infd);
+            } else {
+                int       outfd;
+                char      file[MAXPATHLEN];
+
+                strcpy(file, filename);
+                strcat(file, ".new");
+                outfd = open(file, O_RDWR | O_CREAT | O_TRUNC, fmode);
+                if (outfd == -1)
+                    rc = errno;
+                else {
+                    FILE    *outfp = fdopen(outfd, "w+");
+
+                    if (outfp == NULL) {
+                        rc = errno;
+                        close(outfd);
+                    } else {
+                        int   updated = UPD_CREATE;
+                        int        linesize = PWBUFSZ;
+                        char  *line = malloc(linesize);
+
+                    nextline:
+                        while (fgets(line, linesize, infp) != NULL) {
+                            char  *p = strchr(line, '\n');
+
+                            while ((p = strchr(line, '\n')) == NULL) {
+                                int    l;
+                                if (extendline(&line, &linesize, linesize + PWBUFSZ) == -1) {
+                                    int    ch;
+                                    fputs(line, outfp);
+                                    while ((ch = fgetc(infp)) != EOF) {
+                                        fputc(ch, outfp);
+                                        if (ch == '\n')
+                                            break;
+                                    }
+                                    goto nextline;
+                                }
+                                l = strlen(line);
+                                if (fgets(line + l, linesize - l, infp) == NULL)
+                                    break;
+                            }
+                            if (*line != '#' && *line != '\n') {
+                                if (!updated && strncmp(line, prefix, pfxlen) == 0) {
+                                    updated = updmode == UPD_REPLACE ? UPD_REPLACE : UPD_DELETE;
+
+                                    /*
+                                     * Only actually write changes if updating
+                                     */
+                                    if (updmode == UPD_REPLACE)
+                                        strcpy(line, newline);
+                                    else if (updmode == UPD_DELETE)
+                                        continue;
+                                }
+                            }
+                            fputs(line, outfp);
+                        }
+
+                        /*
+                         * Now, we need to decide what to do: If we are in
+                         * update mode, and no record was updated, then error If
+                         * we are in insert mode, and record already exists,
+                         * then error
+                         */
+                        if (updmode != updated)
+                            /* -1 return means:
+                             * update,delete=no user entry
+                             * create=entry exists
+                             */
+                            rc = -1;
+                        else {
+
+                            /*
+                             * If adding a new record, append it to the end
+                             */
+                            if (updmode == UPD_CREATE)
+                                fputs(newline, outfp);
+
+                            /*
+                             * Flush the file and check for the result
+                             */
+                            if (fflush(outfp) == EOF)
+                                rc = errno;    /* Failed to update */
+                            else {
+                                /*
+                                 * Copy data back into the
+                                 * original file and truncate
+                                 */
+                                rewind(infp);
+                                rewind(outfp);
+                                while (fgets(line, linesize, outfp) != NULL)
+                                    fputs(line, infp);
+
+                                /*
+                                 * If there was a problem with copying
+                                 * we will just rename 'file.new' 
+                                 * to 'file'.
+                                 * This is a gross hack, but we may have
+                                 * corrupted the original file
+                                 */
+                                if (fflush(infp) == EOF || ferror(infp))
+                                    rename(file, filename);
+                                else
+                                    ftruncate(infd, ftell(infp));
+                            }
+                        }
+                        free(line);
+                        fclose(outfp);
+                    }
+                    remove(file);
+                }
+                fclose(infp);
+            }
+        }
+    }
+    return rc;
+}
+
+int
+fmtpwentry(char *buf, struct passwd * pwd, int type)
+{
+        int             l;
+        char           *pw;
+
+        pw = (type == PWF_MASTER) ?
+            ((pwd->pw_passwd == NULL) ? "" : pwd->pw_passwd) : "*";
+
+        if (type == PWF_PASSWD)
+                l = sprintf(buf, "%s:*:%ld:%ld:%s:%s:%s\n",
+                       pwd->pw_name, (long) pwd->pw_uid, (long) pwd->pw_gid,
+                            pwd->pw_gecos ? pwd->pw_gecos : "User &",
+                            pwd->pw_dir, pwd->pw_shell);
+        else
+                l = sprintf(buf, "%s:%s:%ld:%ld:%s:%lu:%lu:%s:%s:%s\n",
+                   pwd->pw_name, pw, (long) pwd->pw_uid, (long) pwd->pw_gid,
+                            pwd->pw_class ? pwd->pw_class : "",
+                            (unsigned long) pwd->pw_change,
+                            (unsigned long) pwd->pw_expire,
+                            pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
+        return l;
+}
+
+static char pathpwd[] = _PATH_PWD;
+static char * pwpath = pathpwd;
+
+/*
+ * Update the password db
+ */
+int
+pwdb(char *arg,...)
+{
+        int             i = 0;
+        pid_t           pid;
+        va_list         ap;
+        char           *args[10];
+
+        args[i++] = _PATH_PWD_MKDB;
+        va_start(ap, arg);
+        while (i < 6 && arg != NULL) {
+                args[i++] = arg;
+                arg = va_arg(ap, char *);
+        }
+        if (pwpath != pathpwd) {
+                args[i++] = "-d";
+                args[i++] = pwpath;
+        }
+        args[i++] = _PATH_MASTERPASSWD;
+        args[i] = NULL;
+
+        if ((pid = fork()) == -1)       /* Error (errno set) */
+                i = errno;
+        else if (pid == 0) {    /* Child */
+                execv(args[0], args);
+                _exit(1);
+        } else {                /* Parent */
+                waitpid(pid, &i, 0);
+                if (WEXITSTATUS(i))
+                        i = EIO;
+        }
+        return i;
+}
+
+int
+pw_update(struct passwd * pwd, char const * user)
+{
+        int             rc = 0;
+
+                char            pfx[PWBUFSZ];
+                char            pwbuf[PWBUFSZ];
+                int             l = snprintf(pfx, PWBUFSZ, "%s:", user);
+
+                /*
+                 * Update the passwd file first
+                 */
+                if (pwd == NULL)
+                        *pwbuf = '\0';
+                else
+                        fmtpwentry(pwbuf, pwd, PWF_PASSWD);
+
+                if (l < 0)
+                        l = 0;
+                rc = fileupdate(_PATH_PASSWD, 0644, pwbuf, pfx, l, UPD_REPLACE);
+                if (rc == 0) {
+
+                        /*
+                         * Then the master.passwd file
+                         */
+                        if (pwd != NULL)
+                                fmtpwentry(pwbuf, pwd, PWF_MASTER);
+                        rc = fileupdate(_PATH_MASTERPASSWD, 0600, pwbuf, pfx, l, UPD_REPLACE);
+                }
+
+    rc = pwdb("-u", user, NULL);
+
+        return rc;
+}
+
+static void
+chpasswd(char *s)
+{
+    struct  passwd *ppw;
+    char    old[256], new[256], *p = NULL;    // MAX pass length is 255
+    int    rc = 0;
+
+    bzero(old, 256);
+    bzero(new, 256);
+
+    if ( (p = strchr(s, ' ')) == NULL || strchr(strchr(s, ' ') + 1, ' ') != NULL )
+    {
+        reply(223, "Usage: site passwd old-password new-password");
+    }
+    else
+    {
+        if ( (p - s) > 255 )
+            reply(223, "The password is too long.");
+        else
+        {
+            strncpy(old, s, p - s);
+            p++;
+            if ( strlen(p) > 255 )
+                reply(223, "The password is too long.");
+            else
+            {
+                strncpy(new, p, 255);
+
+                if ( (ppw = getpwnam(pw->pw_name)) == NULL )
+                {
+                    reply(223, "Server failed: %s", strerror(errno));
+                }
+                else
+                {
+            
+                    if ( pw_verify(ppw->pw_passwd, old) != 0 )
+                    {
+                        reply(223, "The old password for %s is wrong.", ppw->pw_name);
+                    }
+                    else
+                    {
+                        ppw->pw_passwd = pw_pwcrypt(new);
+                        // be root for change password
+                        seteuid(0);
+                        rc = pw_update(ppw, ppw->pw_name);
+                        // change to my self
+                        seteuid(ppw->pw_uid);
+                        if ( rc != 0 )
+                            reply(223, "Error: %s.", strerror(rc));
+                        else
+                            reply(220, "Change password for %s successed.", ppw->pw_name);
+                    }
+                }
+            }
+        }
+    }
+}
+/* add by matthew@cnfug.org */
 
 static void
 sizecmd(char *filename)

使用方法:

# cd /usr/src/libexec/ftpd
# patch < /where/you/freebsd_ftpcmd.y.diff
# make && make install

现在你就可以使用系统用户登录FTPD,使用SITE PASSWD来更改你的密码了。