Generally speaking, you need to identify a combination of components and understand that components can and will change over time. You need tolerance algorithms to make an informed guess about when a change represents an update to a machine you previously identified, or a new machine you have not seen before.
A simple approach would be to enumerate all of the components you listed when you need to determine which machine you're dealing with and compare to previous snapshots of machines you have previously seen. If anything with a serial number matches, you can pretty safely assume you're dealing with the same machine (though of course it's possible that someone transferred a hard drive to a new machine... but then, this is the simple approach. Commercial grade heuristics are much more complicated.).
Use of this approach specifically for software activation is covered by a patent that is actively enforced, so be careful about what you're doing. If you do want to do this to protect your software, it may be better to use a commercial solution. Some are quite affordable. Google "software activation" for options.
Here are some references for obtaining the specific system information (not all are specific C cookbooks, but C can be used in each case).
HDD Windows
http://www.codeproject.com/KB/cs/hard_disk_serialno.aspx
HDD Linux
http://www.webmasterworld.com/forum40/957.htm
BIOS Windows
http://msdn.microsoft.com/en-us/library/aa394077(v=vs.85).aspx
BIOS Linux
http://www.dufault.info/blog/a-better-way-to-find-your-bios-version-in-linux/
MAC Address Windows
C++: Get MAC address of network adapters on Vista?
MAC Address Linux
http://www.linuxquestions.org/questions/programming-9/linux-determining-mac-address-from-c-38217/