diff -urN trac.orig/templates/header.cs trac/templates/header.cs
--- trac.orig/templates/header.cs	2004-10-12 01:31:18.000000000 +0200
+++ trac/templates/header.cs	2004-10-11 23:37:03.000000000 +0200
@@ -53,6 +53,7 @@
     logged in as <?cs var:trac.authname ?> </li>
     <li><a href="<?cs var:trac.href.logout ?>">Logout</a>
   <?cs /if ?></li>
+  <li><a href="<?cs var:trac.href.registration ?>">Registration</a></li>
   <li><a href="<?cs var:trac.href.settings ?>">Settings</a></li>
   <li><a accesskey="6" href="<?cs var:trac.href.wiki ?>/TracGuide">Help/Guide</a></li>
   <li style="display: none"><a accesskey="5" href="http://projects.edgewall.com/trac/wiki/TracFaq">FAQ</a></li>
diff -urN trac.orig/templates/registration.cs trac/templates/registration.cs
--- trac.orig/templates/registration.cs	1970-01-01 01:00:00.000000000 +0100
+++ trac/templates/registration.cs	2004-10-11 23:37:27.000000000 +0200
@@ -0,0 +1,92 @@
+<?cs include "header.cs"?>
+<?cs include "macros.cs"?>
+
+<div id="contextnav" class="nav"></div>
+
+<div id="content" class="registration">
+ <h1>Registration</h1>
+<?cs if:registration_success ?>
+ <p>Operation successful.</p>
+<?cs else ?>
+<?cs if:trac.authname == "anonymous" || !trac.authname ?>
+
+ <h2>Create new user</h2>
+ <p>
+ By filling this form, you will create a new unpriviliged user which you
+ can use to identify your changes in the wiki and tickets.
+ </p>
+ <p>
+ The login must contain only lowercase letters and numbers.
+ </p>
+ <?cs if:registration_error ?>
+  <div class="system-message"><?cs var:registration_error ?></div>
+ <?cs /if ?>
+ <form method="post" action="<?cs var:cgi_location?>/registration">
+ <div>
+  <div class="field">
+   <input type="hidden" name="action" value="create" />
+   <label for="login">Login:</label><br />
+   <input type="text" id="login" name="login" class="textwidget" size="30"
+          value="<?cs var:login ?>" />
+  </div>
+  <div class="field">
+   <label for="password">Password:</label><br />
+   <input type="password" id="password" name="password" class="textwidget"
+          size="30" />
+  </div>
+  <div class="field">
+   <label for="confirm_password">Confirm password:</label><br />
+   <input type="password" id="confirm_password" name="confirm_password"
+          class="textwidget" size="30" />
+  </div>
+
+  <div>
+   <br />
+   <input type="submit" value="Register" />
+  </div>
+ </div>
+ </form>
+<?cs else ?>
+ <h2>Change password</h2>
+ <form method="post" action="<?cs var:cgi_location?>/registration">
+ <div>
+  <div class="field">
+   <input type="hidden" name="action" value="change_password" />
+   <label for="password">New password:</label><br />
+   <input type="password" id="password" name="password" class="textwidget"
+          size="30" />
+  </div>
+  <div class="field">
+   <label for="confirm_password">Confirm password:</label><br />
+   <input type="password" id="confirm_password" name="confirm_password"
+          class="textwidget" size="30" />
+  </div>
+
+  <div>
+   <br />
+   <input type="submit" value="Update" />
+  </div>
+ </div>
+ </form>
+
+ <hr />
+
+ <h2>Delete account</h2>
+ <form method="post" action="<?cs var:cgi_location?>/registration">
+ <div>
+  <div class="field">
+   <input type="hidden" name="action" value="delete" />
+   <input type="checkbox" id="confirm" name="confirm" />
+   <label for="confirm">I am really sure.</label><br />
+  </div>
+
+  <div>
+   <br />
+   <input type="submit" value="Delete my account" />
+  </div>
+ </div>
+ </form>
+<?cs /if ?>
+<?cs /if ?>
+</div>
+<?cs include:"footer.cs"?>
diff -urN trac.orig/trac/Href.py trac/trac/Href.py
--- trac.orig/trac/Href.py	2004-10-12 01:31:18.000000000 +0200
+++ trac/trac/Href.py	2004-10-11 23:27:25.000000000 +0200
@@ -57,6 +57,9 @@
     def login(self):
         return href_join(self.base, 'login')
 
+    def registration(self):
+        return href_join(self.base, 'registration')
+
     def logout(self):
         return href_join(self.base, 'logout')
 
diff -urN trac.orig/trac/Registration.py trac/trac/Registration.py
--- trac.orig/trac/Registration.py	1970-01-01 01:00:00.000000000 +0100
+++ trac/trac/Registration.py	2004-10-12 01:22:42.000000000 +0200
@@ -0,0 +1,144 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2004 Edgewall Software
+# Copyright (C) 2004 Daniel Lundin <daniel@edgewall.com>
+#
+# Trac is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# Trac is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Author: Lunar <lunar@anargeek.net>
+
+import fcntl
+import os
+import os.path
+import re
+from tempfile import mkstemp
+
+from util import TracError
+from Module import Module
+
+class Registration(Module):
+    template_name = 'registration.cs'
+
+    _form_fields = ['login','password']
+
+    def render(self):
+        self.passwd_file = self.env.get_config('registration', 'passwd_file')
+        self.htpasswd_bin = self.env.get_config('registration', 'htpasswd_bin')
+        self.use_md5 = self.env.get_config('registration', 'use_md5')
+        self.req.hdf.setValue('title', 'Registration')
+        action = self.args.get('action')
+        self.env.log.debug('Registration action: %s' % action)
+        if action == 'create':
+            self.create_user()
+        elif action == 'change_password':
+            self.change_password()
+        elif action == 'delete':
+            self.delete_user()
+
+    def create_user(self):
+        login = self.args.get('login')
+        self.req.hdf.setValue('login', login)
+        if not self.is_login_valid(login):
+            self.req.hdf.setValue('registration_error', 'Invalid login.')
+            return
+        if self.user_exists(login):
+            self.req.hdf.setValue('registration_error', 'Login already used.')
+            return
+        password = self.args.get('password')
+        if password != self.args.get('confirm_password'):
+            self.req.hdf.setValue('registration_error', 'Password mismatch.')
+            return
+        self.htpasswd(login, password)
+        self.req.hdf.setValue('registration_success', 'true')
+
+    def change_password(self):
+        login = self.req.authname
+        if not login or login == "anonymous":
+            self.req.hdf.setValue('registration_error', 'Not logged on.')
+            return
+        if not self.user_exists(login):
+            self.req.hdf.setValue('registration_error', 'Unknown user.')
+            return
+        password = self.args.get('password')
+        if password != self.args.get('confirm_password'):
+            self.req.hdf.setValue('registration_error', 'Password mismatch.')
+            return
+        self.htpasswd(login, password)
+        self.req.hdf.setValue('registration_success', 'true')
+
+    def delete_user(self):
+        if not self.args.get('confirm'):
+            return
+        login = self.req.authname
+        if not login or login == "anonymous":
+            self.req.hdf.setValue('registration_error', 'Not logged on.')
+            return
+        if not self.user_exists(login):
+            self.req.hdf.setValue('registration_error', 'Unknown user.')
+            return
+        self.remove_from_passwd(login)
+        self.req.hdf.setValue('registration_success', 'true')
+             
+    def htpasswd(self, login, password):
+        if self.use_md5 == 'true':
+	    flags = '-b -m'
+	else:
+	    flags = '-b'
+        cmd = ('"%s" %s "%s" "%s" "%s"'
+               % (self.htpasswd_bin, flags, self.passwd_file, login, password))
+        ret = os.system(cmd)
+        if ret == 0:
+             return True
+        elif ret == 1:
+             raise TracError, 'htpasswd: unable to access password file'
+        elif ret == 2:
+             raise TracError, 'htpasswd: bad command line'
+        elif ret == 3:
+             # Should not happen
+             raise TracError, 'htpasswd: password mismatch'
+        elif ret == 4:
+             raise TracError, 'htpasswd: interrupted'
+        elif ret == 5:
+             # Should not happen
+             raise TracError, 'htpasswd: value too long'
+        elif ret == 6:
+             # Should not happen
+             raise TracError, 'htpasswd: username contains illegal characters'
+        else:
+             raise TracError, 'htpasswd: unknown error (%d)' % ret
+
+    def remove_from_passwd(self, login):
+        fd = os.open(self.passwd_file, os.O_RDWR)
+        try:
+            fcntl.flock(fd, fcntl.LOCK_EX)
+            passwd = os.fdopen(fd, 'r') 
+            new_passwd = ''
+            for line in passwd:
+                if not (line.strip() == '' or line.startswith("%s:" % login)):
+                    new_passwd = new_passwd + ("%s" % line)
+            os.lseek(fd, 0, 0)
+            os.ftruncate(fd, 0)
+            os.write(fd, new_passwd)
+        finally:
+            os.close(fd)
+
+    def user_exists(self, login):
+        for line in open(self.passwd_file, 'r'):
+            if line.startswith(login + ':'):
+                return True
+        return False
+        
+    def is_login_valid(self, login):
+        return None != re.match('[a-z0-9]+', login)
diff -urN trac.orig/trac/core.py trac/trac/core.py
--- trac.orig/trac/core.py	2004-10-12 01:31:17.000000000 +0200
+++ trac/trac/core.py	2004-10-11 23:26:05.000000000 +0200
@@ -59,7 +59,8 @@
     'attachment'  : ('File', 'Attachment', 0),
     'roadmap'     : ('Roadmap', 'Roadmap', 0),
     'settings'    : ('Settings', 'Settings', 0),
-    'milestone'   : ('Milestone', 'Milestone', 0)
+    'milestone'   : ('Milestone', 'Milestone', 0),
+    'registration': ('Registration', 'Registration', 0),
     }
 
 class TracFieldStorage(cgi.FieldStorage):
@@ -93,7 +94,7 @@
         if match.group(2):
             set_if_missing(args, 'page', match.group(2))
         return args
-    match = re.search('^/(newticket|timeline|search|roadmap|settings|query)/?', path_info)
+    match = re.search('^/(newticket|timeline|search|roadmap|settings|query|registration)/?', path_info)
     if match:
         set_if_missing(args, 'mode', match.group(1))
         return args
@@ -227,6 +228,7 @@
     hdf.setValue('trac.href.about', env.href.about())
     hdf.setValue('trac.href.about_config', env.href.about('config'))
     hdf.setValue('trac.href.login', env.href.login())
+    hdf.setValue('trac.href.registration', env.href.registration())
     hdf.setValue('trac.href.logout', env.href.logout())
     hdf.setValue('trac.href.settings', env.href.settings())
     hdf.setValue('trac.href.homepage', 'http://trac.edgewall.com/')
diff -urN trac.orig/trac/db_default.py trac/trac/db_default.py
--- trac.orig/trac/db_default.py	2004-10-12 01:31:17.000000000 +0200
+++ trac/trac/db_default.py	2004-10-12 13:29:12.000000000 +0200
@@ -445,5 +445,8 @@
   ('notification', 'smtp_from', 'trac@localhost'),
   ('notification', 'smtp_replyto', 'trac@localhost'),
   ('timeline', 'changeset_show_files', 'false'),
-  ('timeline', 'changeset_files_count', 3))
+  ('timeline', 'changeset_files_count', 3),
+  ('registration', 'htpasswd_bin', '/usr/bin/htpasswd'),
+  ('registration', 'passwd_file', '/path/to/htaccess-passwd'),
+  ('registration', 'use_md5', 'true'))
    

