Wednesday, July 7, 2010

HTTP Error 401.1 - Unauthorized: Access is denied due to invalid credentials

I get a call this morning that one of my applications isn't working. I go to check it and sure enough I'm getting the following: "HTTP Error 401.1 - Unauthorized: Access is denied due to invalid credentials". This application impersonates a specific Windows account so I check the usual stuff...

  • Password change : Nope
  • Security on the filesystem: All set properly
  • Long username problem : Nope, not the problem here
  • Account Disabled: nope

So, I'm running out of ideas.. so, I try to recycle the App pool (smart me, created a separate application pool just for this particular application). The pool comes back online and still no go.

I start looking through the log files and I'm not getting any more information except for the "invalid credentials". I take a look at the account again and chuck it into domain admins just to get this up and running. Recycle IIS this time and poof... same error

So, I've now ruled out any possible unauthorized access issue. So I take one final look th the user properties and I notice that even though the account isn't locked out, the password has lo and behold expired. Then I notice that the checkbox for "Password never expires" is not checked. I check the checkbox, apply the changes, and poof.. everything works

So, the lesson here, boys and girls, is to make sure you make sure your service accounts passwords do not expire

Wednesday, May 5, 2010

Sending Mail in SQL 2000

When our fleet division gets a new vehicle, they spend a few days (or weeks) prepping it and getting it ready. They assign the vehicle number, hourly rate, and a bunch of other stuff. After they're done, our finance department needs to track this in their inventory. The current fleet maintenance software can generate a report, but it doesn't contain everything that finance needs, so, the current practice is:

  • Enter the data into the fleet software
  • Re-enter it into an old access database (which has a report that has all the info)
  • Print off the report from Access
  • Make 3 copies
  • Interoffice them to Finance.

I've been tasked with coming up with a better solution. After a few minutes of deliberation, a trigger seemed like a logical solution. The trigger will just fire off an email to all the parties when a vehicle is added. No problemo.

Sending mail in SQL Server is nice and easy. Just use sp_send_dbmail and you're good to go. That is, along as you are using SQL 2005 or better. Back in the dark ages (SQL 2000 and 7.0), however, you didn't have sp_send_dbmail. Instead, you had xp_sendmail.

Now, you ask, what is the problem with xp_sendmail. A bunch, but here's the highlights :

  1. It's deprecated. I'm surprised it's still around in 2k8, but I bet it won’t be in the next version
  2. You need to run it as a db_owner or a sysadmin. Now, you can grant others permissions to it, but that means mucking with permissions in the master database and that's generally frowned upon.
  3. You can only send mail as the profile that's set up. This may not seem like a big deal, but I send out emails from the database and I want the recipients to be able to respond to different people based on the message. That means I'll have to set up a separate mail profile for each respondent.

Of course, the SQL Server development team saw these problems (and probably oodles more) and came up with sp_send_dbmail which addresses all of these

Now, since I’m using SQL 2000, I don’t have that option. After much googling, I was able to find xp_smtp_sendmail. The only issue was that most of the links were broken. So, I had to do a little more sleuthing and eventually found the right page in SQLDev.Net. After following their step by step instructions, I was able to send mail as the appropriate person and cut down about 10-15 hours of redundant work from the process.

Yay me.

Of course, we won’t mention that I forgot that the deleted table in a trigger is aptly named deleted and not updated. D’Oh

Friday, April 23, 2010

IIS Username too long causes 401.1 error

So, I'm making a new ASP.Net webpage that is accessing a database through Integrated Security and it's testing fine on my local PC running under my user credentials. All is working fine and dandy.

I go to publish it to my live server and remember that I have to set the security of the directory to be other than the IUSR account in order to access my database. No problemo. I just fire up IIS and hop over to the directory tab.

I hop back over to my browser and I get this lovely message:

HTTP Error 401.1 - Unauthorized: Access is denied due to invalid credentials.

I recheck my username and password and sure enough they are valid. I check the directory permissions and they're set properly as well. Then I remember.. the ASP.Net temporary file location (c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files) and sure enough that's set right too. So I check the IIS log file and it's as helpful as the error message.

So. Off to Google. First article mentions separating the Application Pool for separate user accounts. Seems unlikely, but I give it a whirl. Create a new App Pool. Set the Security, recycle IIS jsut for good measure and ... still the same error message.

It's getting to be quitting time.. so ... off to sleep on it.

I get back to work and check other ASP.Net apps that use security and they're all set in the Default App Pool (Which I really need to separate when I have some free time) and the security is set how I'd expect it.

I then notice something peculiar about the account that i'm using. It's a wee bit long. See, we used to use a common domain account for all our ASP.Net apps (ASPNet) but someone typed it in wrong once and it got disabled and all my apps failed. So, a year or so ago, I started creating new accounts for each ASP.Net application (ASPNetCalendar, ASPNetAgenda ... ). This account was named ASPNetContractorServiceRequest. Now, when I was creating the account I got some goofy error message about the name being too long, but I just ignored it figuring it was some old legacy error message. Turns out that the error message WAS important. When I created the account it actually truncated the name so 20 characters (ASPNetContractorServ) So, I changed the username and poof everything worked.

Thursday, November 19, 2009

Insert Triggers always run

Who'da thunk it.
I have an insert trigger that does some data validation. no big deal. The validation that it's performing is this:

if not exists (
select 1
from <My other Database>
where column =(select columndata from inserted)
) raiserror (whatever)


looks simple enough, but there are times when I will have an insert statement that isn't inserting any rows. Apparently triggers get fired whether or not there is any data actually being inserted. They get fired anytime there is an insert statement. So, I had to modify the code to have the following

if (exists (select 1 from inserted) begin
if not exists (
select 1
from <My other Database>
where column =(select columndata from inserted)
) raiserror (whatever)
end


Happy SQLing.. (;

Wednesday, November 18, 2009

Transaction Log Backup script

So, Like a good boy, I have my SQL Server databases in full recovery model. Because of this, I also employ an aggressive backup strategy. The databases get a full backup weekly to tape, differential backup daily to tape, and transaction log backup to disk every 15 minutes.
We have a few robust SQL Server boxes each of which house lots of databases. I want the same backup strategy applied to each one. For the tape backups, we're employing HP OpenView Storage DataProtector which has a nice interface for doing just such a task.
There isn't a nice little tool (that I've found) in SQL Server to do this however. So, I crafted my own.

I originally had the transaction log files all going to the same file for a particular database. Since transaction logs are based on the last full or differential backup, this made the restores really complicated. I wanted to make sure that there was only 1 transaction log backup file per differential or full backup. So I baked that into the script as well.

Now, since I'm backing up the transaction logs to disk and I want those stored onto tape, there is a job in our tape backup that will also backup all the Transaction Log Backup Files. Finally, I wanted to delete the transaction log backup file has been archived to tape.

Hopefully all this makes sense. And now... the code:



/*
Transaction Log Maintenance Script.sql
Rob Bittner
August 5, 2009

Script to back up all the transaction logs on a database and warn if there
are file backups instead of tape backups

1) For Each Database:
- If the last backup was not a tape backup email the help desk.
- Otherwise, back up the transaction log.
2) Go through all the existing Transaction Log Backup files and delete those
that have been backed up to tape through the nightly file backup.

Modifications:

November 18, 2009 (RB) : Changed Email to go to the help desk instead of me

*/
declare cur cursor for
select name
from sys.databases
where state_desc='online' and recovery_model_desc='full' and name <> 'model'
open cur
declare @backuppath varchar(255)
declare @name varchar(255)
declare @filename varchar(255)
declare @lastbackupdate datetime
set @backuppath='e:\sqldata\Backup\'

fetch next from cur into @name

while @@fetch_status = 0 begin
print 'Backing up ' + @name

select @lastbackupdate=max(backup_start_date) from msdb.dbo.backupset where type in ('i','d') and database_name=@name
set @filename= @backuppath + @name + ' Transaction Log Backup ' + replace(convert(varchar,@lastbackupdate,120),':','') + '.bak'

if not exists ( -- see if there was no tape backup the last time is was backed up
select 1
from msdb.dbo.backupset bs
JOIN msdb.dbo.backupmediafamily bmf ON bmf.media_set_id = bs.media_set_id
where database_name=@name and backup_start_date=@lastbackupdate
and device_type = 7) begin
if not exists (
select 1 -- See if there already was a backup attempt (to prevent multiple emails)
from msdb..sysjobhistory
where job_id = (select job_id from msdb..sysjobs where name='Transaction Log Maintenance')
and step_id=0 -- job finished
and CONVERT -- date started
( DATETIME, RTRIM(run_date) ) +
( run_time * 9 + run_time % 10000 * 6 + run_time % 100 * 10 ) / 216e4
> @lastbackupdate
) begin


declare @body varchar(max)
declare @path varchar(max)
declare @subject varchar(255)

select @path=bmf.physical_device_name
from msdb.dbo.backupset bs
JOIN msdb.dbo.backupmediafamily bmf ON bmf.media_set_id = bs.media_set_id
where database_name=@name and backup_start_date=@lastbackupdate
order by backup_start_date

set @subject='Transaction log backup failed on ' + @@servername

set @body=
'<html><head><style type="text/css">body{font-family: calibri;} h1,h2,h3 { margin:0}</style></head>'+
'<body>'+
'<h1>Error when backing up transaction log</h1>' +
'<h2>Most recent backup on the database is a file backup</h2><hr />' +
'<h3>Server: ' + @@servername + '</h3>' +
'<h3>Last Backup Path: ' + @path + '</h3>' +
'<h3>Last Backup Time: ' + convert(varchar,@lastbackupdate,109) + '</h3>' +
'<p style="font-weight:bold">To preserve the integrity of our database backup process, a full backup of ' + @name + ' must be performed utilizing Dataprotector ASAP!</p>'+
'<h2>Failure to back up this database to tape will cause the transaction log to continue to grow</h2>' +
'<p> - SQL Server Database Mailer</p>' +
'</body>' +
'</html>'
EXEC msdb.dbo.sp_send_dbmail
@recipients='email address here',
@subject = @subject,
@body = @body,
@body_format = 'HTML',
@profile_name='HelpDesk' ;
end -- Only email once if statement
end else begin
backup log @name to disk=@filename
end


fetch next from cur into @name
end

close cur

deallocate cur

declare @deletestatement varchar(255)
set @deletestatement = 'del ' + @backuppath + '* /a-a /q'
print @deletestatement
EXEC master..xp_cmdshell @deletestatement

Thursday, November 12, 2009

Custom LINQ orderby expression

LINQ expressions are so slick that I don't know how I lived without them for so long. I had a little issue with a custom orderby, however, and found a pretty neat solution.

Here's the objects:


Festival{ String FestivalName, IEnumerable Events ...}
Event { String Type, DateTime Date ...}

A festival has many different Events: Start, Finish, Performances, etc. I want a list of festivals in order according to the start date. I was going to expose a specific property in Festival that returned the date of the Start event if it existed, but I wanted to generalize it. I knew about FirstOrDefault and came up with the following:



List Festival=
(from d in FestivalList
orderby s.Events.FirstOrDefault(q => q.Type=="Start").Date
select d)

I ended up getting a NullReferenceException because not all festivals were properly entered and some didn't have Start events. I decided that these should show up first. So I had to use a different approach with the orderby by using another inline LINQ query.


List Festival=
(from d in FestivalList
orderby
(from e in s.Events
where e.Type == "Start"
select e
).DefaultIfEmpty(new Event()).First().Date
select d)

The DefaultIfEmpty specifies how we should handle when there isn't an Event. In this case, I'm returning an empty event which will return DateTime.MinValue (This is part of the Event.Date definition). Slicker than snot.

Sunday, February 15, 2009

Old Projects

Revisiting old code always amazes me. How I could think that inline SQL statements would actually be a good idea is beyond me. I know that i knew how to sprocify that stuff before. Anyway, Now I'm running into to Schema lock errors. fun times had by all...