Index: trunk/doc/soju.1.scd
===================================================================
--- trunk/doc/soju.1.scd	(revision 624)
+++ trunk/doc/soju.1.scd	(revision 625)
@@ -323,5 +323,5 @@
 	Other options are:
 
-	*-admin*
+	*-admin* true|false
 		Make the new user an administrator.
 
@@ -330,7 +330,15 @@
 		realname set for a network.
 
-*user update* [-password <password>] [-realname <realname>]
-	Update the current user. The options are the same as the _user create_
-	command.
+*user update* [username] [options...]
+	Update a user. The options are the same as the _user create_ command.
+
+	If _username_ is omitted, the current user is updated. Only admins can
+	update other users.
+
+	Not all flags are valid in all contexts:
+
+	- The _-username_ flag is never valid, usernames are immutable.
+	- The _-realname_ flag is only valid when updating the current user.
+	- The _-admin_ flag is only valid when updating another user.
 
 *user delete* <username>
Index: trunk/service.go
===================================================================
--- trunk/service.go	(revision 624)
+++ trunk/service.go	(revision 625)
@@ -126,5 +126,5 @@
 	}
 	if cmd.admin && !dc.user.Admin {
-		sendServicePRIVMSG(dc, fmt.Sprintf(`error: you must be an admin to use this command`))
+		sendServicePRIVMSG(dc, "error: you must be an admin to use this command")
 		return
 	}
@@ -767,33 +767,82 @@
 }
 
+func popArg(params []string) (string, []string) {
+	if len(params) > 0 && !strings.HasPrefix(params[0], "-") {
+		return params[0], params[1:]
+	}
+	return "", params
+}
+
 func handleUserUpdate(dc *downstreamConn, params []string) error {
 	var password, realname *string
+	var admin *bool
 	fs := newFlagSet()
 	fs.Var(stringPtrFlag{&password}, "password", "")
 	fs.Var(stringPtrFlag{&realname}, "realname", "")
-
+	fs.Var(boolPtrFlag{&admin}, "admin", "")
+
+	username, params := popArg(params)
 	if err := fs.Parse(params); err != nil {
 		return err
 	}
-
-	// copy the user record because we'll mutate it
-	record := dc.user.User
-
+	if len(fs.Args()) > 0 {
+		return fmt.Errorf("unexpected argument")
+	}
+
+	var hashed *string
 	if password != nil {
-		hashed, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
+		hashedBytes, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
 		if err != nil {
 			return fmt.Errorf("failed to hash password: %v", err)
 		}
-		record.Password = string(hashed)
-	}
-	if realname != nil {
-		record.Realname = *realname
-	}
-
-	if err := dc.user.updateUser(&record); err != nil {
-		return err
-	}
-
-	sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", dc.user.Username))
+		hashedStr := string(hashedBytes)
+		hashed = &hashedStr
+	}
+
+	if username != "" && username != dc.user.Username {
+		if !dc.user.Admin {
+			return fmt.Errorf("you must be an admin to update other users")
+		}
+		if realname != nil {
+			return fmt.Errorf("cannot update -realname of other user")
+		}
+
+		u := dc.srv.getUser(username)
+		if u == nil {
+			return fmt.Errorf("unknown username %q", username)
+		}
+
+		done := make(chan error, 1)
+		u.events <- eventUserUpdate{
+			password: hashed,
+			admin:    admin,
+			done:     done,
+		}
+		if err := <-done; err != nil {
+			return err
+		}
+
+		sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", username))
+	} else {
+		// copy the user record because we'll mutate it
+		record := dc.user.User
+
+		if hashed != nil {
+			record.Password = *hashed
+		}
+		if realname != nil {
+			record.Realname = *realname
+		}
+		if admin != nil {
+			return fmt.Errorf("cannot update -admin of own user")
+		}
+
+		if err := dc.user.updateUser(&record); err != nil {
+			return err
+		}
+
+		sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", dc.user.Username))
+	}
+
 	return nil
 }
Index: trunk/user.go
===================================================================
--- trunk/user.go	(revision 624)
+++ trunk/user.go	(revision 625)
@@ -59,4 +59,10 @@
 
 type eventStop struct{}
+
+type eventUserUpdate struct {
+	password *string
+	admin    *bool
+	done     chan error
+}
 
 type deliveredClientMap map[string]string // client name -> msg ID
@@ -643,4 +649,24 @@
 				dc.SendMessage(msg)
 			})
+		case eventUserUpdate:
+			// copy the user record because we'll mutate it
+			record := u.User
+
+			if e.password != nil {
+				record.Password = *e.password
+			}
+			if e.admin != nil {
+				record.Admin = *e.admin
+			}
+
+			e.done <- u.updateUser(&record)
+
+			// If the password was updated, kill all downstream connections to
+			// force them to re-authenticate with the new credentials.
+			if e.password != nil {
+				u.forEachDownstream(func(dc *downstreamConn) {
+					dc.Close()
+				})
+			}
 		case eventStop:
 			u.forEachDownstream(func(dc *downstreamConn) {
