\n";
		print &ui_form_start("save_user.cgi");
		print &ui_hidden("dom", $in{'dom'});
		print &ui_hidden("old", $in{'old'});
		print &ui_hidden("delete", 1);
		# Count up home directory size
		local ($mailsz) = &mail_file_size($user);
		local ($msg, $homesz);
		if ($user->{'nocreatehome'} || !$user->{'home'}) {
			$msg = 'user_rusurew';
			}
		else {
			($homesz) = &disk_usage_kb($user->{'home'});
			$msg = $mailsz && $homesz && !&mail_under_home() ?
				'user_rusure' :'user_rusureh';
			}
		print "",&text($msg, "$in{'old'}",
			  	  &nice_size($mailsz),
				  &nice_size($homesz*1024),
				  "$user->{'home'}"),"
\n";
		# Check for home directory clash
		if (!$user->{'nocreatehome'} && $user->{'home'} &&
		    !$user->{'webowner'}) {
			local @hclash = grep {
				(&same_file($_->{'home'}, $user->{'home'}) ||
				 &is_under_directory($user->{'home'},
						     $_->{'home'})) &&
				$_ ne $user } @users;
			if (@hclash) {
				print "",&text('user_hclash',
				    join(" ", map { "$_->{'user'}" }
						  @hclash)),"
\n";
				}
			}
		print &ui_form_end([ [ "confirm", $text{'user_deleteok'} ] ]);
		print "
\n";
		if ($d) {
			&ui_print_footer("list_users.cgi?dom=$in{'dom'}",
				$text{'users_return'});
			}
		else {
			&ui_print_footer("", $text{'index_return'});
			}
		exit;
		}
	}
else {
	# Saving or creating, so verify inputs
	if ($in{'new'} && $d) {
		($mleft, $mreason, $mmax) = &count_feature("mailboxes");
		$mleft == 0 && &error($text{'user_emailboxlimit'});
		}
	if (!$mailbox) {
		if (!$config{'allow_upper'}) {
			$in{'mailuser'} = lc($in{'mailuser'});
			}
		$in{'mailuser'} =~ s/^\s+//;
		$in{'mailuser'} =~ s/\s+$//;
		$err = &valid_mailbox_name($in{'mailuser'});
		$olderr = $in{'new'} ? undef
				     : &valid_mailbox_name($user->{'user'});
		&error($err) if ($err && !$olderr);
		if (&supports_firstname()) {
			$in{'firstname'} =~ /^[^:\r\n]*$/ ||
				&error($text{'user_efirstname'});
			$user->{'firstname'} = $in{'firstname'};
			$in{'surname'} =~ /^[^:\r\n]*$/ ||
				&error($text{'user_esurname'});
			$user->{'surname'} = $in{'surname'};
			}
		$in{'real'} =~ /^[^:\r\n]*$/ ||
			&error($text{'user_ereal'});
		$user->{'real'} = $in{'real'};
		if (!$in{'new'} && $in{'mailpass_def'} == 1) {
			# Password not being changed
			$user->{'passmode'} = 4;
			}
		elsif ($in{'mailpass_def'} == 2) {
			# No passwored accepted
			$need_password_check = 0;
			delete($user->{'plainpass'});
			$user->{'pass'} = '*';
			$user->{'passmode'} = 1;
			}
		else {
			# Either password is being changed, or this is new user
			$user->{'plainpass'} =
				&parse_new_password("mailpass", 1);
			$need_password_check = 1;
			$user->{'pass'} = &encrypt_user_password(
					$user, $user->{'plainpass'});
			$user->{'passmode'} = 3;
			&set_pass_change($user);
			}
		if (!$user->{'alwaysplain'}) {
			# Disable account if requested
			&set_pass_disable($user, $in{'disable'});
			}
		if (!$user->{'noquota'}) {
			# Check and save quota inputs
			$qedit = &can_mailbox_quota();
			@defmquota = split (/ /, $tmpl->{'defmquota'});
			$pd = $d->{'parent'} ? &get_domain($d->{'parent'}) : $d;
			if (&has_home_quotas() && $qedit) {
				# Use entered quota
				if ( $in{'quota'} eq -1 ) {
					$in{'quota'} = $in{'otherquota'};
					}
				$in{'quota_def'} ||
				    $in{'quota'} =~ /^[0-9\.]+$/ ||
					&error($text{'user_equota'});
				$user->{'quota'} = $in{'quota_def'} ? 0 :
					"a_parse("quota", "home");
				!$user->{'quota'} || !$pd->{'quota'} ||
				  $user->{'quota'} <= $pd->{'quota'} ||
				  &error(&text('user_eoverquota',
					&nice_size($pd->{'quota'}*
						   "a_bsize("home"))));
				}
			elsif (&has_home_quotas() && $in{'new'}) {
				# Use default
				$user->{'quota'} = $defmquota[0];
				}
			if (&has_mail_quotas() && $qedit) {
				if ( $in{'mquota'} eq -1 ) {
					$in{'mquota'} = $in{'othermquota'};
					}
				$in{'mquota_def'} ||
				    $in{'mquota'} =~ /^[0-9\.]+$/ ||
					&error($text{'user_equota'});
				$user->{'mquota'} = $in{'mquota_def'} ? 0 :
					"a_parse("mquota", "mail");
				!$user->{'mquota'} || !$pd->{'mquota'} ||
				  $user->{'mquota'} <= $pd->{'mquota'} ||
				  &error(&text('user_eovermquota',
					&nice_size($pd->{'mquota'}*
						   "a_bsize("mail"))));
				}
			elsif (&has_mail_quotas() && $in{'new'}) {
				# Use default
				$user->{'mquota'} = $defmquota[0];
				}
			}
		if ($d) {
			# Save list of allowed databases
			local ($db, @dbs);
			foreach $db (split(/\r?\n/, $in{'dbs'})) {
				local ($type, $name) = split(/_/, $db, 2);
				push(@dbs, { 'type' => $type,
					     'name' => $name });
				}
			$user->{'dbs'} = \@dbs;
			}
		}
	else {
		# For a domain owner, the password is never changed here
		$user->{'passmode'} = 4;
		}
	# Save extra email addresses
	%oldextra = ( );
	if (!$in{'new'}) {
		%oldextra = map { $_, 1 } @{$old{'extraemail'}};
		}
	$eu = $mailbox ? $d->{'user'} : $in{'mailuser'};
	@extra = split(/\s+/, $in{'extra'});
	%donextra = ( );
	foreach $e (@extra) {
		$e = lc($e);
		if ($d && $e =~ /^([^\@ \t]+$)$/) {
			$e = "$e\@$d->{'dom'}";
			}
		if ($e !~ /^(\S*)\@(\S+)$/) {
			&error(&text('user_eextra1', $e));
			}
		if ($e eq $eu."\@".$d->{'dom'}) {
			&error(&text('user_eextra5', $e));
			}
		local ($eu, $ed) = ($1, $2);
		$ed = &parse_domain_name($ed);
		local $edom = &get_domain_by("dom", $ed);
		$edom && $edom->{'mail'} || &error(&text('user_eextra2', $ed));
		&can_edit_domain($edom) || $oldextra{$e} ||
			&error(&text('user_eextra3', $ed));
		!$edom->{'alias'} || !$edom->{'aliascopy'} ||
			&error(&text('user_eextra7', $ed));
		$e = $eu."\@".$ed;
		$donextra{lc($e)}++ && &error(&text('user_eextra6', $e));
		}
	$user->{'extraemail'} = \@extra;
	# Check if extras would exceed limit
	($mleft, $mreason, $mmax) = &count_feature("aliases");
	if ($mleft >= 0 &&
	    $mleft - @extra + (%old ? @{$old{'extraemail'}} : 0) < 0) {
		&error($text{'alias_ealiaslimit'});
		}
	# Save primary email address
	if ($d && !$user->{'noprimary'}) {
		$user->{'email'} = $in{'mailbox'} ? $eu."\@".$d->{'dom'}
						  : undef;
		}
	# Save recovery address
	if (!$mailbox && ($in{'recovery_def'} || $in{'recovery'})) {
		$in{'recovery_def'} || $in{'recovery'} =~ /^\S+\@\S+$/ ||
			&error($text{'user_erecovery'});
		$user->{'recovery'} = $in{'recovery_def'} ? ""
							  : $in{'recovery'};
		}
	# Get the email address to send new/updated mailbox, for the mailbox
	# itself. Email may also be sent to the reseller and domain owner
	if ($in{'new'} && &will_send_user_email($d, 1) &&
	    !$in{'newmail_def'} && $in{'newmail'}) {
		$in{'newmail'} =~ /^\S+$/ || &error($text{'user_enewmail'});
		$newmailto = $in{'newmail'};
		}
	elsif (!$in{'new'} && &will_send_user_email($d, 0) &&
	       !$in{'remail_def'} && $in{'remail'}) {
		$in{'remail'} =~ /^\S+$/ || &error($text{'user_eremail'});
		$newmailto = $in{'remail'};
		}
	if (!$mailbox && !$user->{'fixedhome'} && !$user->{'brokenhome'}) {
		# Find home
		if (&can_mailbox_home($user) &&
		    $d && $d->{'home'} && !$in{'home_def'}) {
			$in{'home'} =~ /\.\.\// && &error($text{'user_ehome'});
			$in{'home'} =~ /^\// && &error($text{'user_ehome2'});
			if ($user->{'webowner'}) {
				# Custom home directory for web FTP user
				if (!$in{'home'}) {
					$home = $d->{'home'};
					}
				else {
					$home = &public_html_dir($d)."/".
							$in{'home'};
					}
				}
			else {
				# Custom home directory for mailbox user
				&error($text{'user_ehome'})
					if ($in{'home'} =~ /^\s*$/);
				$home = "$d->{'home'}/$in{'home'}";
				}
			$user->{'maybecreatehome'} = 1;
			}
		elsif ($d) {
			if ($user->{'webowner'}) {
				# Auto home directory for web FTP user
				$home = &public_html_dir($d);
				}
			else {
				# Auto home directory for mailbox user
				$home = "$d->{'home'}/$config{'homes_dir'}/".
					$in{'mailuser'};
				}
			}
		else {
			# Auto home directory for local user
			$home = &useradmin::auto_home_dir(
				$home_base, $in{'mailuser'},
				$config{'localgroup'});
			}
		# Make sure home exists, for web owner user
		if ($user->{'webowner'} && !-d $home) {
			&error(&text('user_ehomeexists', &html_escape($home)));
			}
		}
	# Update secondary groups
	%cangroups = map { $_, 1 } (&allowed_secondary_groups($d),
				    @{$user->{'secs'}});
	@secs = split(/\0/, $in{'groups'});
	foreach my $g (@secs) {
		$cangroups{$g} || &error(&text('user_egroup', $g));
		}
	$user->{'secs'} = [ @secs ];
	# Update no-spam flag
	if ($config{'spam'} && $d->{'spam'}) {
		$user->{'nospam'} = $in{'nospam'};
		}
	# Work out full email address, perhaps with real name
	if ($user->{'real'}) {
		$fullemail = '"'.$user->{'real'}.'" <'.$user->{'email'}.'>';
		}
	else {
		$fullemail = $user->{'email'};
		}
	# Create or update the user
	my $sshkey_mode = $in{'sshkey_mode'} == 2 ? 1 : 0;
	$emailmailbox = 0;
	if ($in{'new'}) {
		# Set new user parameters
		if (!$user->{'webowner'}) {
			# UID needs to be unique
			$user->{'uid'} = &allocate_uid(\%taken);
			}
		else {
			# UID is same as domain for Qmail users and web owners
			$user->{'uid'} = $d->{'uid'};
			}
		$user->{'gid'} = $d ? $d->{'gid'} :
				      getgrnam($config{'localgroup'});
		# Check for clash within this domain
		($clash) = grep { $_->{'user'} eq $in{'mailuser'} } @users;
		$clash && &error($text{'user_eclash2'});
		if (&can_mailbox_ftp()) {
			# Shell can be set to one that's allowed for FTP
			&check_available_shell($in{'shell'}, 'mailbox')
				|| &error($text{'user_eshell'});
			$user->{'shell'} = $in{'shell'};
			}
		else {
			# If the shell cannot be edited, always use
			# the default.
			$user->{'shell'} =
				&default_available_shell('mailbox');
			}
		if (!$user->{'fixedhome'} && !$user->{'brokenhome'}) {
			$user->{'home'} = $home;
			}
		if (($utaken{$in{'mailuser'}} || ($d && $config{'append'})) &&
		    !$user->{'noappend'}) {
			# Need to append domain name
			if ($d) {
				# Add group name
				$user->{'user'} = &userdom_name(
					$in{'mailuser'},$d);
				}
			else {
				# No domain to add, so give up!
				&error($text{'user_eclash2'});
				}
			}
		else {
			# Username is as entered
			$user->{'user'} = $in{'mailuser'};
			}
		if ($d) {
			# Check for a Unix clash
			$mclash = &check_clash($in{'mailuser'}, $d->{'dom'});
			if ($utaken{$user->{'user'}} ||
			    $user->{'email'} && $mclash ||
			    !$user->{'email'} && $mclash == 2) {
				&error($text{'user_eclash'});
				}
			}
		# Check if any extras clash
		foreach $e (@extra) {
			$e =~ /^(\S*)\@(\S+)$/;
			if (&check_clash($1, $2)) {
				&error(&text('user_eextra4', $e));
				}
			}
		# Check if the name is too long
		if ($lerr = &too_long($user->{'user'})) {
			&error($lerr);
			}
		# Check if home directory already exists
		if (-e $home && !$user->{'nocreatehome'} &&
		    !$user->{'maybecreatehome'}) {
			&error(&text('user_emkhome', $home));
			}
		if (!$user->{'noalias'}) {
			# Save alias
			if ($in{'simplemode'} eq 'simple') {
				# From simple form
				$simple = &get_simple_alias($d, $user, 1);
				&parse_simple_form($simple, \%in, $d, 1, 1, 1,
						   $user->{'user'});
				$simple->{'from'} = $fullemail;
				$user->{'user_extra'} = &replace_atsign(
				  $user->{'user'}) if (&need_extra_user($user));
				&save_simple_alias($d, $user, $simple);
				if (@{$user->{'to'}} == 1 &&
				    $simple->{'tome'}) {
					# If forwarding is just to the user's
					# mailbox, then that is like no
					# forwarding at all
					$user->{'to'} = undef;
					}
				}
			else {
				# From complex form
				@values = &parse_alias(undef, $user->{'user'},
						       undef, "user", $d);
				$user->{'to'} = @values ? \@values : undef;
				}
			# Check for alias loop
			&check_email_to_loop();
			}
		# Now we have the username, check the password
		if ($need_password_check) {
			$user->{'plainpass'} eq '' &&
				&error($text{'setup_eemptypass'});
			$perr = &check_password_restrictions($user, 0, $d);
			&error($perr) if ($perr);
			}
		# Validate plugins
		foreach $f (&list_mail_plugins()) {
			$err = &plugin_call($f, "mailbox_validate", $user,
					    \%old, \%in, $in{'new'}, $d);
			&error($err) if ($err);
			}
		# Validate if extra database user exists
		# which can be merged with Unix user
		my $extra_db_user = &get_extra_db_user($d, $user->{'user'});
		my $extra_web_user = &get_extra_web_user($d, $user->{'user'});
		if (($extra_db_user || $extra_web_user) && !$in{'confirm'}) {
			# Confirm suppression first
			&ui_print_header(&domain_in($d), $text{'user_createovertitle'}, "");
			my @save_user_hids;
			foreach my $key (keys %in) {
				push(@save_user_hids, [$key, $in{$key}]);
				}
			my $createoverdesc = $extra_db_user && $extra_web_user ?
				"dbweb" : $extra_db_user ? "db" : "web";
			my $createoverdescm = $extra_db_user && $extra_web_user ? 2 : 1;
			print &ui_confirmation_form(
				"save_user.cgi",
				&text("user_createoverdesc$createoverdesc",
					"