Перемещение имен пользователей и паролей между экземплярами SQL Server

Применимо к: Microsoft SQL Server 2005 Standard EditionMicrosoft SQL Server 2005 Workgroup EditionMicrosoft SQL Server 2005 Developer Edition Больше

Аннотация


После перемещения баз данных на новый сервер пользователи могут лишиться возможности войти в него. При этом появится следующее сообщение об ошибке:
Msg 18456, Level 16, State 1
Login failed for user '%ls'. (Ошибка входа для пользователя "%ls".)
Переместите имена пользователей и пароли на новый сервер. В этой статье описано, как это сделать.

Перемещение имен пользователей и паролей между серверами с работающей системой SQL Server 7.0

Компонент Object Transfer из состава служб Data Transformation Services (DTS) SQL Server 7.0 предназначен для перемещения между двумя серверами пользователей и их имен, однако не позволяет переносить пароли для зарегистрированных имен пользователей SQL Server. Чтобы перенести имена пользователей и пароли с одного сервера с SQL Server 7.0 на другой, выполните действия, описанные в разделе "Полное решение для перемещения имен пользователей и паролей между различными версиями SQL Server".

Перемещение имен пользователей и паролей с сервера с SQL Server 7.0 на сервер с SQL Server 2000 или между двумя серверами с запущенной службой SQL Server 2000

Чтобы переместить имена пользователей и пароли с сервера с SQL Server 7.0 на экземпляр SQL Server 2000 или между двумя экземплярами SQL Server 2000, можно воспользоваться новой задачей Transfer Logins Task ("Перемещение имен входа") из пакета DTS в SQL Server 2000. Для этого:
  1. Подключитесь к целевому серверу с SQL Server 2000, найдите папку Data Transformation Services в окне SQL Server Enterprise Manager, откройте ее, щелкните правой кнопкой мыши пункт Local Packages ("Локальные пакеты"), а затем выберите пункт New Package ("Создать пакет").
  2. Когда откроется конструктор DTS, из меню Task выберите команду Transfer Logins Task. Введите на вкладках Source, Destination и Logins необходимые данные.

    Важно. На сервере назначения не может быть запущена 64-разрядная версия SQL Server 2000, так как компоненты DTS для такой версии отсутствуют. Если вы импортируете имена пользователей из экземпляра SQL Server на другом компьютере, ваш экземпляр SQL Server необходимо запустить с помощью учетной записи домена.

    Примечание. С помощью метода DTS можно переместить пароли, но не исходные ИД безопасности. Если вы создадите имя пользователя не с помощью исходного ИД безопасности и базы данных пользователей будут перемещены на новый сервер, пользователи базы данных утратят связь с именем пользователя. Чтобы переместить исходный ИД безопасности и предупредить потерю связи, выполните действия, приведенные в разделе "Полное решение для перемещения имен пользователей и паролей между различными версиями SQL Server".

Перемещение имен пользователей и паролей между экземплярами SQL Server 2005

Дополнительные сведения о перемещении имен пользователей и паролей между экземплярами SQL Server 2005 см. в следующей статье базы знаний Майкрософт:

918992 Перемещение имен пользователей и паролей между экземплярами SQL Server 2005 (эта ссылка может указывать на содержимое полностью или частично на английском языке)

Полное решение для перемещения имен пользователей и паролей между различными версиями SQL Server

Для выполнения этой задачи воспользуйтесь одним из описанных ниже способов.
Примечания.
  • Приведенные ниже сценарии создают две хранимых процедуры (sp_hexadecimal и sp_help_revlogin) в базе данных master.
  • Эти сценарии зависят от системных таблиц сервера SQL Server. Структура этих таблиц может различаться в разных версиях SQL Server. Непосредственный выбор из системных таблиц не приветствуется.
  • Ознакомьтесь с поправками в конце этой статьи. Они содержат важные сведения о способах и их описании.
  • Способ 2 назначает имена пользователей ролям.

Способ 1

Этот способ применим в указанных ниже случаях.
  • Перемещение имен пользователей и паролей SQL Server 7.0 в SQL Server 7.0.
  • Перемещение имен пользователей и паролей SQL Server 7.0 в SQL Server 2000.
  • Перемещение имен пользователей и паролей SQL Server 2000 в SQL Server 2000.
Чтобы переместить имена пользователей и пароли между различными версиями SQL Server, выполните следующие действия:
  1. Запустите следующий сценарий на исходном экземпляре SQL Server. После создания хранимой процедуры
    sp_help_revlogin переходите к выполнению второго действия.
    ----- Begin Script, Create sp_help_revlogin procedure -----

    USE master
    GO
    IF OBJECT_ID ('sp_hexadecimal') IS NOT NULL
    DROP PROCEDURE sp_hexadecimal
    GO
    CREATE PROCEDURE sp_hexadecimal
    @binvalue varbinary(256),
    @hexvalue varchar(256) OUTPUT
    AS
    DECLARE @charvalue varchar(256)
    DECLARE @i int
    DECLARE @length int
    DECLARE @hexstring char(16)
    SELECT @charvalue = '0x'
    SELECT @i = 1
    SELECT @length = DATALENGTH (@binvalue)
    SELECT @hexstring = '0123456789ABCDEF'
    WHILE (@i <= @length)
    BEGIN
    DECLARE @tempint int
    DECLARE @firstint int
    DECLARE @secondint int
    SELECT @tempint = CONVERT(int, SUBSTRING(@binvalue,@i,1))
    SELECT @firstint = FLOOR(@tempint/16)
    SELECT @secondint = @tempint - (@firstint*16)
    SELECT @charvalue = @charvalue +
    SUBSTRING(@hexstring, @firstint+1, 1) +
    SUBSTRING(@hexstring, @secondint+1, 1)
    SELECT @i = @i + 1
    END
    SELECT @hexvalue = @charvalue
    GO

    IF OBJECT_ID ('sp_help_revlogin') IS NOT NULL
    DROP PROCEDURE sp_help_revlogin
    GO
    CREATE PROCEDURE sp_help_revlogin @login_name sysname = NULL AS
    DECLARE @name sysname
    DECLARE @xstatus int
    DECLARE @binpwd varbinary (256)
    DECLARE @txtpwd sysname
    DECLARE @tmpstr varchar (256)
    DECLARE @SID_varbinary varbinary(85)
    DECLARE @SID_string varchar(256)

    IF (@login_name IS NULL)
    DECLARE login_curs CURSOR FOR
    SELECT sid, name, xstatus, password FROM master..sysxlogins
    WHERE srvid IS NULL AND name <> 'sa'
    ELSE
    DECLARE login_curs CURSOR FOR
    SELECT sid, name, xstatus, password FROM master..sysxlogins
    WHERE srvid IS NULL AND name = @login_name
    OPEN login_curs
    FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd
    IF (@@fetch_status = -1)
    BEGIN
    PRINT 'No login(s) found.'
    CLOSE login_curs
    DEALLOCATE login_curs
    RETURN -1
    END
    SET @tmpstr = '/* sp_help_revlogin script '
    PRINT @tmpstr
    SET @tmpstr = '** Generated '
    + CONVERT (varchar, GETDATE()) + ' on ' + @@SERVERNAME + ' */'
    PRINT @tmpstr
    PRINT ''
    PRINT 'DECLARE @pwd sysname'
    WHILE (@@fetch_status <> -1)
    BEGIN
    IF (@@fetch_status <> -2)
    BEGIN
    PRINT ''
    SET @tmpstr = '-- Login: ' + @name
    PRINT @tmpstr
    IF (@xstatus & 4) = 4
    BEGIN -- NT authenticated account/group
    IF (@xstatus & 1) = 1
    BEGIN -- NT login is denied access
    SET @tmpstr = 'EXEC master..sp_denylogin ''' + @name + ''''
    PRINT @tmpstr
    END
    ELSE BEGIN -- NT login has access
    SET @tmpstr = 'EXEC master..sp_grantlogin ''' + @name + ''''
    PRINT @tmpstr
    END
    END
    ELSE BEGIN -- SQL Server authentication
    IF (@binpwd IS NOT NULL)
    BEGIN -- Non-null password
    EXEC sp_hexadecimal @binpwd, @txtpwd OUT
    IF (@xstatus & 2048) = 2048
    SET @tmpstr = 'SET @pwd = CONVERT (varchar(256), ' + @txtpwd + ')'
    ELSE
    SET @tmpstr = 'SET @pwd = CONVERT (varbinary(256), ' + @txtpwd + ')'
    PRINT @tmpstr
    EXEC sp_hexadecimal @SID_varbinary,@SID_string OUT
    SET @tmpstr = 'EXEC master..sp_addlogin ''' + @name
    + ''', @pwd, @sid = ' + @SID_string + ', @encryptopt = '
    END
    ELSE BEGIN
    -- Null password
    EXEC sp_hexadecimal @SID_varbinary,@SID_string OUT
    SET @tmpstr = 'EXEC master..sp_addlogin ''' + @name
    + ''', NULL, @sid = ' + @SID_string + ', @encryptopt = '
    END
    IF (@xstatus & 2048) = 2048
    -- login upgraded from 6.5
    SET @tmpstr = @tmpstr + '''skip_encryption_old'''
    ELSE
    SET @tmpstr = @tmpstr + '''skip_encryption'''
    PRINT @tmpstr
    END
    END
    FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd
    END
    CLOSE login_curs
    DEALLOCATE login_curs
    RETURN 0
    GO
    ----- End Script -----

  2. После создания хранимой процедуры sp_help_revlogin запустите ее на исходном сервере с помощью анализатора запросов. Хранимую процедуру sp_help_revlogin можно использовать на серверах с SQL Server 7.0 и SQL Server 2000. Результатом ее выполнения является сценарий, который служит для создания имен пользователей с исходными ИД безопасности и паролем. Сохраните полученный результат, а затем вставьте его в анализатор запросов на целевом сервере и запустите. Например:
    EXEC master..sp_help_revlogin

Способ 2

Этот способ применим в указанных ниже случаях.
  • Перемещение имен пользователей и паролей SQL Server 7.0 в SQL Server 2005.
  • Перемещение имен пользователей и паролей SQL Server 2000 в SQL Server 2005.
  • Назначение имен пользователей ролям.
Для переноса имен пользователей и паролей между различными версиями SQL Server и последующего назначения имен пользователей ролям выполните указанные ниже действия.
  1. Запустите приведенный ниже сценарий на исходном сервере SQL Server.
    USE master 
    GO
    IF OBJECT_ID ('sp_hexadecimal') IS NOT NULL
    DROP PROCEDURE sp_hexadecimal
    GO
    CREATE PROCEDURE sp_hexadecimal
    @binvalue varbinary(256),
    @hexvalue varchar(256) OUTPUT
    AS
    DECLARE @charvalue varchar(256)
    DECLARE @i int
    DECLARE @length int
    DECLARE @hexstring char(16)
    SELECT @charvalue = '0x'
    SELECT @i = 1
    SELECT @length = DATALENGTH (@binvalue)
    SELECT @hexstring = '0123456789ABCDEF'
    WHILE (@i <= @length)
    BEGIN
    DECLARE @tempint int
    DECLARE @firstint int
    DECLARE @secondint int
    SELECT @tempint = CONVERT(int, SUBSTRING(@binvalue,@i,1))
    SELECT @firstint = FLOOR(@tempint/16)
    SELECT @secondint = @tempint - (@firstint*16)
    SELECT @charvalue = @charvalue +
    SUBSTRING(@hexstring, @firstint+1, 1) +
    SUBSTRING(@hexstring, @secondint+1, 1)
    SELECT @i = @i + 1
    END
    SELECT @hexvalue = @charvalue
    GO

    IF OBJECT_ID ('sp_help_revlogin_2000_to_2005') IS NOT NULL
    DROP PROCEDURE sp_help_revlogin_2000_to_2005
    GO
    CREATE PROCEDURE sp_help_revlogin_2000_to_2005

    @login_name sysname = NULL,
    @include_db bit = 0,
    @include_role bit = 0

    AS
    DECLARE @name sysname
    DECLARE @xstatus int
    DECLARE @binpwd varbinary (256)
    DECLARE @dfltdb varchar (256)
    DECLARE @txtpwd sysname
    DECLARE @tmpstr varchar (256)
    DECLARE @SID_varbinary varbinary(85)
    DECLARE @SID_string varchar(256)

    IF (@login_name IS NULL)
    DECLARE login_curs CURSOR STATIC FOR
    SELECT sid, [name], xstatus, password, isnull(db_name(dbid), 'master')
    FROM master.dbo.sysxlogins
    WHERE srvid IS NULL AND
    [name] <> 'sa'
    ELSE
    DECLARE login_curs CURSOR FOR
    SELECT sid, [name], xstatus, password, isnull(db_name(dbid), 'master')
    FROM master.dbo.sysxlogins
    WHERE srvid IS NULL AND
    [name] = @login_name

    OPEN login_curs

    FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd, @dfltdb

    IF (@@fetch_status = -1)
    BEGIN
    PRINT 'No login(s) found.'
    CLOSE login_curs
    DEALLOCATE login_curs
    RETURN -1
    END

    SET @tmpstr = '/* sp_help_revlogin script '
    PRINT @tmpstr
    SET @tmpstr = '** Generated '
    + CONVERT (varchar, GETDATE()) + ' on ' + @@SERVERNAME + ' */'
    PRINT @tmpstr
    PRINT ''
    PRINT ''
    PRINT ''
    PRINT '/***** CREATE LOGINS *****/'

    WHILE @@fetch_status = 0
    BEGIN
    PRINT ''
    SET @tmpstr = '-- Login: ' + @name
    PRINT @tmpstr

    IF (@xstatus & 4) = 4
    BEGIN -- NT authenticated account/group
    IF (@xstatus & 1) = 1
    BEGIN -- NT login is denied access
    SET @tmpstr = '' --'EXEC master..sp_denylogin ''' + @name + ''''
    PRINT @tmpstr
    END
    ELSE
    BEGIN -- NT login has access
    SET @tmpstr = 'IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE [name] = ''' + @name + ''')'
    PRINT @tmpstr
    SET @tmpstr = CHAR(9) + 'CREATE LOGIN [' + @name + '] FROM WINDOWS'
    PRINT @tmpstr
    END
    END
    ELSE
    BEGIN -- SQL Server authentication
    EXEC sp_hexadecimal @SID_varbinary, @SID_string OUT

    IF (@binpwd IS NOT NULL)
    BEGIN -- Non-null password
    EXEC sp_hexadecimal @binpwd, @txtpwd OUT
    SET @tmpstr = 'CREATE LOGIN [' + @name + '] WITH PASSWORD=' + @txtpwd + ' HASHED'
    END
    ELSE
    BEGIN -- Null password
    SET @tmpstr = 'CREATE LOGIN [' + @name + '] WITH PASSWORD='''''
    END

    SET @tmpstr = @tmpstr + ', CHECK_POLICY=OFF, SID=' + @SID_string
    PRINT @tmpstr
    END

    FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd, @dfltdb
    END

    IF @include_db = 1
    BEGIN
    PRINT ''
    PRINT ''
    PRINT ''
    PRINT '/***** SET DEFAULT DATABASES *****/'

    FETCH FIRST FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd, @dfltdb

    WHILE @@fetch_status = 0
    BEGIN
    PRINT ''
    SET @tmpstr = '-- Login: ' + @name
    PRINT @tmpstr

    SET @tmpstr = 'ALTER LOGIN [' + @name + '] WITH DEFAULT_DATABASE=[' + @dfltdb + ']'
    PRINT @tmpstr

    FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd, @dfltdb
    END
    END

    IF @include_role = 1
    BEGIN
    PRINT ''
    PRINT ''
    PRINT ''
    PRINT '/***** SET SERVER ROLES *****/'

    FETCH FIRST FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd, @dfltdb

    WHILE @@fetch_status = 0
    BEGIN
    PRINT ''
    SET @tmpstr = '-- Login: ' + @name
    PRINT @tmpstr

    IF @xstatus &16 = 16 -- sysadmin
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''sysadmin'''
    PRINT @tmpstr
    END

    IF @xstatus &32 = 32 -- securityadmin
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''securityadmin'''
    PRINT @tmpstr
    END

    IF @xstatus &64 = 64 -- serveradmin
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''serveradmin'''
    PRINT @tmpstr
    END

    IF @xstatus &128 = 128 -- setupadmin
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''setupadmin'''
    PRINT @tmpstr
    END

    IF @xstatus &256 = 256 --processadmin
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''processadmin'''
    PRINT @tmpstr
    END

    IF @xstatus &512 = 512 -- diskadmin
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''diskadmin'''
    PRINT @tmpstr
    END

    IF @xstatus &1024 = 1024 -- dbcreator
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''dbcreator'''
    PRINT @tmpstr
    END

    IF @xstatus &4096 = 4096 -- bulkadmin
    BEGIN
    SET @tmpstr = 'exec master.dbo.sp_addsrvrolemember @loginame=''' + @name + ''', @rolename=''bulkadmin'''
    PRINT @tmpstr
    END

    FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @xstatus, @binpwd, @dfltdb
    END
    END

    CLOSE login_curs
    DEALLOCATE login_curs
    RETURN 0
    GO

    exec sp_help_revlogin_2000_to_2005 @login_name=NULL, @include_db=1, @include_role=1
    GO
  2. Сохраните вывод, а затем вставьте и выполните его в SQL Server Management Studio на целевом сервере SQL Server 2005.
Примечание. Если исходный сервер SQL Server содержит данные входа с пустым паролем, вывод будет содержать выражение, подобное приведенному ниже.
CREATE LOGIN LoginName WITH PASSWORD = '', CHECK_POLICY = OFF, SID = MySID

Заметки

  • Внимательно просмотрите полученный сценарий, прежде чем запустить его на целевом сервере с SQL Server. При перемещении имен пользователей на экземпляр SQL Server, который расположен в другом домене, внесите изменения в сценарий, созданный с помощью процедуры sp_help_revlogin, и подставьте новое имя домена в инструкциях sp_grantlogin. Так как интегрированные имена пользователей с правами доступа в новом домене будут иметь ИД безопасности, отличные от использовавшихся в исходном домене, связь пользователей базы данных с этими именами будет утеряна. Чтобы предупредить потерю связи, ознакомьтесь со сведениями из перечня ниже. Если вы перемещаете интегрированные имена пользователей между экземплярами SQL Server в рамках одного домена, используется один и тот же ИД безопасности, поэтому связь пользователей с именами сохранится.
  • После перемещения имен пользователей последние могут утратить разрешения на доступ к базам данных, которые также были перемещены. В результате пользователь теряет связь со своими учетными данными и становится "потерянным". При попытке предоставить права доступа к базе данных появляется сообщение о том, что такой пользователь уже существует:
    Microsoft SQL-DMO (ODBC SQLState: 42000) Error 15023: User or role '%s' already exists in the current database. ("Пользователь или роль «%s» уже существует в текущей базе данных".)
    Дополнительные сведения о сопоставлении имен пользователей и пользователей базы данных с целью устранения потерянных имен SQL Server и интегрированных имен пользователей см. в следующей статье базы знаний Майкрософт:
    240872 Устранение проблем, связанных с разрешениями, при перемещении базы данных на другой сервер Microsoft SQL Server

    Дополнительные сведения об использовании хранимой процедуры sp_change_users_login для поочередной корректировки потерянных пользователей (потерянных только от стандартных имен пользователей SQL) см. в следующей статье базы знаний Майкрософт:
    274188 В разделе «Troubleshooting Orphaned Users» руководства SQL Server Books Online приведены не все сведения (эта ссылка может указывать на содержимое полностью или частично на английском языке)

  • Если перемещение имен пользователей и паролей на новый сервер с SQL Server осуществляется одновременно с переносом на него баз данных, ознакомьтесь с инструкциями в следующей статье базы знаний Майкрософт :
    314546 Перемещение баз данных между компьютерами с SQL Server (эта ссылка может указывать на содержимое полностью или частично на английском языке)

  • Это возможно благодаря параметру @encryptopt в системной хранимой процедуре sp_addlogin, который позволяет создавать имена пользователей с помощью зашифрованных паролей. Дополнительные сведения об этой процедуре см. в разделе sp_addlogin (T-SQL) в электронной документации для SQL Server.
  • По умолчанию из таблицы sysxloginsмогут выбирать только члены роли сервера sysadminfixed. Конечные пользователи могут создать или запустить эти процедуры только в том случае, если член роли sysadmin предоставит им необходимые разрешения.
  • Так как база данных по умолчанию не всегда существует на целевом сервере, этот способ не предполагает перемещения ее сведений для определенного имени пользователя. Чтобы определить базу данных по умолчанию для имени пользователя, можно использовать системную хранимую процедуру sp_defaultdb, указав имя для входа и базу данных по умолчанию в качестве аргументов. Дополнительные сведения об этой процедуре см. в разделе sp_defaultdb в электронной документации для SQL Server.
  • Если на исходном сервере используется порядок сортировки без различения прописных и строчных букв, а на целевом сервере — с учетом регистра, то после перемещения имен пользователей между экземплярами SQL Server на целевом сервере все буквы в паролях необходимо вводить в верхнем регистре. Если на исходном сервере регистр учитывается, а на целевом — нет, вы сможете воспользоваться описанной выше процедурой при условии, что исходный пароль не содержит алфавитных символов или все буквы заданы в верхнем регистре. Если оба сервера одинаково обрабатывают регистр, подобных проблем не возникнет. Это побочный эффект метода, который SQL Server использует для обработки паролей. Дополнительные сведения см. в разделе Effect on Passwords of Changing Sort Orders ("Влияние порядка сортировки на перемещение паролей") в электронной документации по SQL Server 7.0.
  • Когда вы запускаете выходные данные сценария sp_help_revlogin на целевом сервере, который уже имеет имя пользователя, совпадающее с одним из созданных сценарием имен, появится следующее сообщение об ошибке:
    Server: Msg 15025, Level 16, State 1, Procedure sp_addlogin, Line 56
    The login 'test1' already exists. ("Имя пользователя test1 уже существует".)
    Кроме того, если ИД безопасности существующего имени пользователя совпадает с ИД безопасности имени пользователя, которое вы добавляете на сервер, появится следующее сообщение об ошибке:
    Server: Msg 15433, Level 16, State 1, Procedure sp_addlogin, Line 93
    Supplied parameter @sid is in use. ("Указанный параметр @sid уже используется".)
    Поэтому вы внимательно просмотрите результаты выполнения этих команд и изучите содержимое таблицы sysxlogins, чтобы устранить ошибки соответствующим образом.
  • На основании значения ИД безопасности определенному имени пользователя в SQL Server предоставляется доступ на уровне баз данных. Таким образом, если имя пользователя имеет два разных значения ИД безопасности на уровне баз данных (в двух различных базах данных на этом сервере), то с помощью этого имени можно получить доступ только к той базе данных, чей ИД безопасности соответствует значению в таблице syslogins для данного имени пользователя. Такая ситуация может возникнуть при консолидации двух отдельно взятых баз данных с двух разных серверов. Чтобы решить эту проблему, соответствующее имя пользователя необходимо вручную удалить из базы данных с несоответствующим ИД безопасности, используя хранимую процедуру sp_dropuser, а затем снова добавить это имя пользователя с помощью еще одной хранимой процедуры — sp_adduser.