I think using the WHERE IN clause will do what you're looking for:
... WHERE site_id IN (SELECT site_id FROM locations ...)
Untested but I think that might get what you're looking for.
But, I think you have a chance to clean up a potentially ugly query.
You have query within a query. They're good when you need them, but for what you're doing, I think a simple INNER JOIN will work just as nicely and be a lot faster (it'll reduce the amount of table/index scans):
(also, do you really need the *? I doubt it - in the query I use below, it'll return EVERYTHING. Instead, add each column, this will help improve query performance)
SELECT * FROM companies INNER JOIN locations ON locations.site_id = companies.site_id WHERE locations.city_id = [city ID]
Now, if a city ID is not provided, you just remove the 'WHERE locations.city_id = [city ID]' (or in coding terms, don't add the 'WHERE locations.city_id = [city ID]' clause unless a city ID has been provided).
Now, this query assumes each company has a location. If you want all the companies, even if they don't have a location, then a LEFT JOIN might be what you need. Check out inner and outer/left joins. They're a key to relating data between tables.