为FreeBSD自带的FTPD增加修改密码的功能
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来更改你的密码了。